diff --git a/src/StackHCI/Az.StackHCI.psd1 b/src/StackHCI/Az.StackHCI.psd1 index 4a5dc41f1e41..51227aa2e456 100644 --- a/src/StackHCI/Az.StackHCI.psd1 +++ b/src/StackHCI/Az.StackHCI.psd1 @@ -69,7 +69,7 @@ RequiredModules = @(@{ModuleName = 'Az.Accounts'; ModuleVersion = '2.1.2'; }) # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = 'Register-AzStackHCI', 'Unregister-AzStackHCI' +FunctionsToExport = 'Register-AzStackHCI', 'Unregister-AzStackHCI', 'Test-AzStackHCIConnection' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/src/StackHCI/Az.StackHCI.psm1 b/src/StackHCI/Az.StackHCI.psm1 index fb30e82c2925..90b74ff50825 100644 --- a/src/StackHCI/Az.StackHCI.psm1 +++ b/src/StackHCI/Az.StackHCI.psm1 @@ -1,9 +1,13 @@ # -# AzureStack HCI Registration and Unregistration Powershell Cmdlets. +# AzureStack HCI Registration and Unregistration Powershell Cmdlets. # $ErrorActionPreference = 'Stop' +# Todo: Update actual GA build number and UBR later +$GAOSBuildNumber = 17784 +$GAOSUBR = 1374 + #region User visible strings $AdminConsentWarning = "You need additional Azure Active Directory permissions to register in this Azure subscription. Contact your Azure AD administrator to grant consent to AAD application identity {0} at {1}. Then, run Register-AzStackHCI again with same parameters to complete registration." @@ -12,6 +16,18 @@ $CloudResourceDoesNotExist = "The Azure resource with ID {0} doesn't exist. Unre $RegisteredWithDifferentResourceId = "Azure Stack HCI is already registered with Azure resource ID {0}. To register or change registration, first unregister the cluster using Unregister-AzStackHCI, then try again." $RegistrationInfoNotFound = "Additional parameters are required to unregister. Run 'Get-Help Unregister-AzStackHCI -Full' for more information." $RegionNotSupported = "Azure Stack HCI is not yet available in region {0}. Please choose one of these regions: {1}." +$CertificateNotFoundOnNode = "Certificate with thumbprint {0} not found on node(s) {1}. Make sure the certificate has been added to the certificate store on every clustered node." +$SettingCertificateFailed = "Failed to register. Couldn't generate self-signed certificate on node(s) {0}. Couldn't set and verify registration certificate on node(s) {1}. Make sure every clustered node is up and has Internet connectivity (at least outbound to Azure)." +$InstallLatestVersionWarning = "Newer version of the Az.StackHCI module is available. Update from version {0} to version {1} using Update-Module." +$NotAllTheNodesInClusterAreGA = "Update the operating system on node(s) {0} to version $GAOSBuildNumber.$GAOSUBR or later to continue." +$NoExistingRegistrationExistsErrorMessage = "Can't repair registration because the cluster isn't registered yet. Register the cluster using Register-AzStackHCI without the -RepairRegistration option." +$UserCertValidationErrorMessage = "Can't use certificate with thumbprint {0} because it expires in less than 60 days, on {1}. Certificates must be valid for at least 60 days." +$FailedToRemoveRegistrationCertWarning = "Couldn't clean up Azure Stack HCI registration certificate from node(s) {0}. You can ignore this message or clean up the certificate yourself (optional)." +$UnregistrationSuccessDetailsMessage = "Azure Stack HCI is successfully unregistered. The Azure resource representing Azure Stack HCI has been deleted. Azure Stack HCI can’t sync with Azure until you register again." +$RegistrationSuccessDetailsMessage = "Azure Stack HCI is successfully registered. An Azure resource representing Azure Stack HCI has been created in your Azure subscription to enable an Azure-consistent monitoring, billing, and support experience." +$CouldNotGetLatestModuleInformationWarning = "Can't connect to the PowerShell Gallery to verify module version. Make sure you have the latest Az.StackHCI module with major version {0}.*." +$ConnectingToCloudBillingServiceFailed = "Can't reach Azure from node(s) {0}. Make sure every clustered node has network connectivity to Azure. Verify that your network firewall allows outbound HTTPS from port 443 to all the well-known Azure IP addresses and URLs required by Azure Stack HCI. Visit aka.ms/hcidocs for details." +$ResourceExistsInDifferentRegionError = "There is already an Azure Stack HCI resource with the same resource ID in region {0}, which is different from the input region {1}. Either specify the same region or delete the existing resource and try again." $FetchingRegistrationState = "Checking whether the cluster is already registered" $ValidatingParametersFetchClusterName = "Validating cmdlet parameters" @@ -20,7 +36,7 @@ $RegisterProgressActivityName = "Registering Azure Stack HCI with Azure..." $UnregisterProgressActivityName = "Unregistering Azure Stack HCI from Azure..." $InstallAzResourcesMessage = "Installing required PowerShell module: Az.Resources" $InstallAzureADMessage = "Installing required PowerShell module: AzureAD" -$InstallRSATHCIMessage = "Installing required Windows feature: RSAT-Azure-Stack-HCI" +$InstallRSATHCIMessage = "Installing required Windows feature: RSAT-Azure-Stack-HCI (if not already installed)" $InstallRSATClusteringMessage = "Installing required Windows feature: RSAT-Clustering-PowerShell" $LoggingInToAzureMessage = "Logging in to Azure" $ConnectingToAzureAD = "Connecting to Azure Active Directory" @@ -35,6 +51,8 @@ $RegisterAndSyncMetadataMessage = "Registering Azure Stack HCI cluster and synci $UnregisterHCIUsageMessage = "Unregistering Azure Stack HCI cluster and cleaning up registration state on the on-premises cluster" $DeletingAADApplicationMessage = "Deleting Azure AD application identity {0}" $DeletingCloudResourceMessage = "Deleting Azure resource with ID {0} representing the Azure Stack HCI cluster" +$DeletingCertificateFromAADApp = "Deleting certificate with KeyId {0} from Azure Active Directory" +$SkippingDeleteCertificateFromAADApp = "Certificate with KeyId {0} is still being used by Azure Active Directory and won't be deleted" #endregion @@ -59,23 +77,38 @@ $AzurePPE = "AzurePPE" $PortalAADAppPermissionUrl = '/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/{0}/isMSAApp/' $PortalHCIResourceUrl = '/#@{0}/resource/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.AzureStackHCI/clusters/{3}/overview' -$ClusterMetadataPermission = "AzureStackHCI.Census.Sync" -$ClusterUsagePermission = "AzureStackHCI.Billing.Sync" +# GUID's of the scopes generated in first party portal +$ClusterReadPermission = "2344a320-6a09-4530-bed7-c90485b5e5e2" +$ClusterReadWritePermission = "493bd689-9082-40db-a506-11f40b68128f" +$ClusterNodeReadPermission = "8fa5445e-80fb-4c71-a3b1-9a16a81a1966" +$ClusterNodeReadWritePermission = "bbe8afc9-f3ba-4955-bb5f-1cfb6960b242" + +# Deprecated scopes. Will be deleted during RepairRegistration +$BillingSyncPermission = "e4359fc6-82ee-4411-9a4d-edfc7812cf24" +$CensusSyncPermission = "8c83ab0a-0f96-40e9-940b-20cc5c5ecca9" + +$PermissionIds = New-Object System.Collections.Generic.List[string] + +$PermissionIds.Add($ClusterReadPermission) +$PermissionIds.Add($ClusterReadWritePermission) +$PermissionIds.Add($ClusterNodeReadPermission) +$PermissionIds.Add($ClusterNodeReadWritePermission) -$ServiceEndpointAzureCloud = [string]::Empty +$Region_EASTUSEUAP = 'eastus2euap' [hashtable] $ServiceEndpointsAzureCloud = @{ - 'eastus' = 'https://eus-azurestackhci-usage.azurewebsites.net'; - 'westeurope' = 'https://weu-azurestackhci-usage.azurewebsites.net'; - 'eastus2euap' = 'https://eus2euap-azurestackhci-usage.azurewebsites.net'; + $Region_EASTUSEUAP = 'https://eus2euap-azurestackhci-usage.azurewebsites.net'; } +$ServiceEndpointAzureCloudFrontDoor = "https://azurestackhci.azurefd.net" +$ServiceEndpointAzureCloud = $ServiceEndpointAzureCloudFrontDoor + $AuthorityAzureCloud = "https://login.microsoftonline.com" $BillingServiceApiScopeAzureCloud = "https://azurestackhci-usage.trafficmanager.net/.default" $GraphServiceApiScopeAzureCloud = "https://graph.microsoft.com/.default" $GraphEndpointResourceIdAzureCloud = "https://graph.windows.net/" -$ServiceEndpointAzurePPE = "https://wus-azurestackhci-usage-df.azurewebsites.net" +$ServiceEndpointAzurePPE = "https://azurestackhci-df.azurefd.net" $AuthorityAzurePPE = "https://login.windows-ppe.net" $BillingServiceApiScopeAzurePPE = "https://azurestackhci-usage-df.azurewebsites.net/.default" $GraphServiceApiScopeAzurePPE = "https://graph.ppe.windows.net/.default" @@ -99,12 +132,24 @@ $BillingServiceApiScopeAzureGermanCloud = "https://azurestackhci-usage.azurewebs $GraphServiceApiScopeAzureGermanCloud = "https://graph.cloudapi.de/.default" $GraphEndpointResourceIdAzureGermancloud = "https://graph.cloudapi.de/" -$RPAPIVersion = "2020-03-01-preview" +$RPAPIVersion = "2020-10-01" $OutputPropertyResult = "Result" -$OutputPropertyResourceId = "ResourceId" -$OutputPropertyPortalResourceURL = "PortalResourceURL" -$OutputPropertyPortalAADAppPermissionsURL = "PortalAADAppPermissionsURL" +$OutputPropertyResourceId = "AzureResourceId" +$OutputPropertyPortalResourceURL = "AzurePortalResourceURL" +$OutputPropertyPortalAADAppPermissionsURL = "AzurePortalAADAppPermissionsURL" +$OutputPropertyDetails = "Details" +$OutputPropertyTest = "Test" +$OutputPropertyEndpointTested = "EndpointTested" +$OutputPropertyIsRequired = "IsRequired" +$OutputPropertyFailedNodes = "FailedNodes" + +$ConnectionTestToAzureHCIServiceName = "Connect to Azure Stack HCI Service" + +$ResourceGroupCreatedByName = "CreatedBy" +$ResourceGroupCreatedByValue = "4C02703C-F5D0-44B0-ADC3-4ED5C2839E61" + +$HealthEndpointPath = "/health" enum RegistrationStatus { @@ -113,6 +158,13 @@ enum RegistrationStatus OutOfPolicy; } +enum CertificateManagedBy +{ + Invalid; + User; + Cluster; +} + $hciScript = { $hciPowershell = Get-WindowsFeature -Name RSAT-Azure-Stack-HCI; if ( $hciPowershell.Installed -eq $false) @@ -285,6 +337,83 @@ param( return "/Subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ResourceGroupName + "/providers/Microsoft.AzureStackHCI/clusters/" + $ResourceName } +function Get-RequiredResourceAccess{ + $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] + + $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess + $requiredAccess.ResourceAppId = $UsageServiceFirstPartyAppId + $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] + + Foreach ($permId in $PermissionIds) + { + $permAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess + $permAccess.Type = "Role" + $permAccess.Id = $permId + $requiredAccess.ResourceAccess.Add($permAccess) + } + + $requiredResourcesAccess.Add($requiredAccess) + return $requiredResourcesAccess +} + +# Called during repair registration. +function AddRequiredPermissionsIfNotPresent{ +param( + [string] $AppId + ) + + Write-Verbose "Adding the required permissions to AAD Application $AppId if not already added" + $usagesp = Get-AzureADServicePrincipal -Filter "AppId eq '$UsageServiceFirstPartyAppId'" + + $app = Get-AzureADApplication -Filter "AppId eq '$AppId'" + $shouldAddRequiredPerms = $false + + if($app.RequiredResourceAccess -eq $Null) + { + $shouldAddRequiredPerms = $true + } + else + { + $reqResourceAccess = $app.RequiredResourceAccess | Where-Object {$_.ResourceAppId -eq $UsageServiceFirstPartyAppId} + + if($reqResourceAccess -eq $Null) + { + $shouldAddRequiredPerms = $true + } + else + { + if ($reqResourceAccess.ResourceAccess -eq $Null) + { + $shouldAddRequiredPerms = $true + } + else + { + $spReqPermClusRW = $reqResourceAccess.ResourceAccess | Where-Object {$_.Id -eq $ClusterReadWritePermission} + $spReqPermClusR = $reqResourceAccess.ResourceAccess | Where-Object {$_.Id -eq $ClusterReadPermission} + $spReqPermClusNodeRW = $reqResourceAccess.ResourceAccess | Where-Object {$_.Id -eq $ClusterNodeReadWritePermission} + $spReqPermClusNodeR = $reqResourceAccess.ResourceAccess | Where-Object {$_.Id -eq $ClusterNodeReadPermission} + + # If App has these permissions, we have already added the required permissions earlier. Not need to add again. + if(($spReqPermClusRW -ne $Null) -and ($spReqPermClusR -ne $Null) -and ($spReqPermClusNodeRW -ne $Null) -and ($spReqPermClusNodeR -ne $Null)) + { + $shouldAddRequiredPerms = $false + } + else + { + $shouldAddRequiredPerms = $true + } + } + } + } + + # Add the required permissions + if($shouldAddRequiredPerms -eq $true) + { + $requiredResourcesAccess = Get-RequiredResourceAccess + Set-AzureADApplication -ObjectId $app.ObjectId -RequiredResourceAccess $requiredResourcesAccess | Out-Null + } +} + function Check-UsageAppRoles{ param( [string] $AppId @@ -292,41 +421,61 @@ param( Write-Verbose "Checking admin consent status for AAD Application $AppId" $usagesp = Get-AzureADServicePrincipal -Filter "AppId eq '$UsageServiceFirstPartyAppId'" - $usageWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterUsagePermission} - $metadataWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterMetadataPermission} + $appSP = Get-AzureADServicePrincipal -Filter "AppId eq '$AppId'" $assignedPerms = Get-AzureADServiceAppRoleAssignedTo -ObjectId $appSP.ObjectId - $usageWrite = $assignedPerms | where { ($_.Id -eq $usageWritePermission.Id) } - $metadataWrite = $assignedPerms | where { ($_.Id -eq $metadataWritePermission.Id) } - if($usageWrite -eq $Null -or $metadataWrite -eq $Null) + $clusterRead = $assignedPerms | where { ($_.Id -eq $ClusterReadPermission) } + $clusterReadWrite = $assignedPerms | where { ($_.Id -eq $ClusterReadWritePermission) } + $clusterNodeRead = $assignedPerms | where { ($_.Id -eq $ClusterNodeReadPermission) } + $clusterNodeReadWrite = $assignedPerms | where { ($_.Id -eq $ClusterNodeReadWritePermission) } + + if($clusterRead -eq $Null -or $clusterReadWrite -eq $Null -or $clusterNodeRead -eq $Null -or $clusterNodeReadWrite -eq $Null) { # Try Get-AzureADServiceAppRoleAssignment as well to get app role assignments. WAC token falls under this case. $assignedPerms = Get-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId } - if($usageWrite -eq $Null) + if($clusterRead -eq $Null) + { + $clusterRead = $assignedPerms | where { ($_.Id -eq $ClusterReadPermission) } + } + + if($clusterReadWrite -eq $Null) + { + $clusterReadWrite = $assignedPerms | where { ($_.Id -eq $ClusterReadWritePermission) } + } + + if($clusterNodeRead -eq $Null) { - $usageWrite = $assignedPerms | where { ($_.Id -eq $usageWritePermission.Id) } + $clusterNodeRead = $assignedPerms | where { ($_.Id -eq $ClusterNodeReadPermission) } } - if($metadataWrite -eq $Null) + if($clusterNodeReadWrite -eq $Null) { - $metadataWrite = $assignedPerms | where { ($_.Id -eq $metadataWritePermission.Id) } + $clusterNodeReadWrite = $assignedPerms | where { ($_.Id -eq $ClusterNodeReadWritePermission) } } - if($usageWrite -ne $Null -and $metadataWrite -ne $Null) # Check both Usage.Write and Metadata.Write are in consented state. + $assignedPermsList = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.DirectoryObject] + $assignedPermsList.Add($clusterRead) + $assignedPermsList.Add($clusterReadWrite) + $assignedPermsList.Add($clusterNodeRead) + $assignedPermsList.Add($clusterNodeReadWrite) + + Foreach ($perm in $assignedPermsList) { - if($usageWrite.DeletionTimestamp -eq $Null -or ($usageWrite.DeletionTimestamp -lt $usageWrite.CreationTimestamp)) + if($perm -eq $null) { - if($metadataWrite.DeletionTimestamp -eq $Null -or ($metadataWrite.DeletionTimestamp -lt $metadataWrite.CreationTimestamp)) - { - return $True - } + return $false + } + + if($perm.DeletionTimestamp -ne $Null -and ($perm.DeletionTimestamp -gt $perm.CreationTimestamp)) + { + return $false } } - return $false + return $true } function Create-Application{ @@ -364,26 +513,7 @@ param( } While ($Stoploop -eq $false) - $usageWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterUsagePermission} - $metadataWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterMetadataPermission} - - $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] - - $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess - $requiredAccess.ResourceAppId = $usagesp.AppId - $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] - - $usageWriteAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess - $usageWriteAccess.Type = "Role" - $usageWriteAccess.Id = $usageWritePermission.Id - $requiredAccess.ResourceAccess.Add($usageWriteAccess) - - $metadataWriteAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess - $metadataWriteAccess.Type = "Role" - $metadataWriteAccess.Id = $metadataWritePermission.Id - $requiredAccess.ResourceAccess.Add($metadataWriteAccess) - - $requiredResourcesAccess.Add($requiredAccess) + $requiredResourcesAccess = Get-RequiredResourceAccess # Create application $app = New-AzureADApplication -DisplayName $AppName -RequiredResourceAccess $requiredResourcesAccess @@ -401,14 +531,14 @@ param( Write-Verbose "Granting admin consent for AAD Application Id $AppId" $usagesp = Get-AzureADServicePrincipal -Filter "AppId eq '$UsageServiceFirstPartyAppId'" - $usageWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterUsagePermission} - $metadataWritePermission = $usagesp.AppRoles| Where-Object {$_.Value -contains $ClusterMetadataPermission} $appSP = Get-AzureADServicePrincipal -Filter "AppId eq '$AppId'" try { - New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $metadataWritePermission.Id - New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $usageWritePermission.Id + New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $ClusterReadPermission + New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $ClusterReadWritePermission + New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $ClusterNodeReadPermission + New-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -PrincipalId $appSP.ObjectId -ResourceId $usagesp.ObjectId -Id $ClusterNodeReadWritePermission } catch { @@ -421,6 +551,47 @@ param( return $True } +function Remove-OldScopes{ +param( + [string] $AppId + ) + + Write-Verbose "Removing old scopes on AAD Application with Id $AppId" + $appSP = Get-AzureADServicePrincipal -Filter "AppId eq '$AppId'" + + # Remove AzureStackHCI.Billing.Sync and AzureStackHCI.Census.Sync permissions if present as we dont need them + $assignedPerms = Get-AzureADServiceAppRoleAssignedTo -ObjectId $appSP.ObjectId + + $billingSync = $assignedPerms | where { ($_.Id -eq $BillingSyncPermission) } + $censusSync = $assignedPerms | where { ($_.Id -eq $CensusSyncPermission) } + + if($billingSync -eq $Null -or $censusSync -eq $Null) + { + # Try Get-AzureADServiceAppRoleAssignment as well to get app role assignments. WAC token falls under this case. + $assignedPerms = Get-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId + } + + if($billingSync -eq $Null) + { + $billingSync = $assignedPerms | where { ($_.Id -eq $BillingSyncPermission) } + } + + if($censusSync -eq $Null) + { + $censusSync = $assignedPerms | where { ($_.Id -eq $CensusSyncPermission) } + } + + if($billingSync -ne $Null) + { + Remove-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -AppRoleAssignmentId $billingSync.ObjectId | Out-Null + } + + if($censusSync -ne $Null) + { + Remove-AzureADServiceAppRoleAssignment -ObjectId $appSP.ObjectId -AppRoleAssignmentId $censusSync.ObjectId | Out-Null + } +} + function Azure-Login{ param( [string] $SubscriptionId, @@ -429,7 +600,8 @@ param( [string] $GraphAccessToken, [string] $AccountId, [string] $EnvironmentName, - [string] $ProgressActivityName + [string] $ProgressActivityName, + [bool] $UseDeviceAuthentication ) Write-Progress -activity $ProgressActivityName -status $InstallAzResourcesMessage -percentcomplete 10 @@ -474,7 +646,7 @@ param( if([string]::IsNullOrEmpty($TenantId)) { - if($IsIEPresent) + if(($UseDeviceAuthentication -eq $false) -and ($IsIEPresent)) { Connect-AzAccount -Environment $EnvironmentName -SubscriptionId $SubscriptionId -Scope Process | Out-Null } @@ -486,7 +658,7 @@ param( } else { - if($IsIEPresent) + if(($UseDeviceAuthentication -eq $false) -and ($IsIEPresent)) { Connect-AzAccount -Environment $EnvironmentName -TenantId $TenantId -SubscriptionId $SubscriptionId -Scope Process | Out-Null } @@ -547,7 +719,7 @@ param( $locations = $resources.Where{($_.ResourceTypes.ResourceTypeName -eq 'clusters' -and $_.RegistrationState -eq 'Registered')}.Locations $locations | foreach { - $regionName = Normalize-RegionName -Region $_ + $regionName = Normalize-RegionName -Region $_ if ($regionName -eq $Region) { # Supported region @@ -560,6 +732,190 @@ param( return $False } +function Check-ConnectionToCloudBillingService{ +param( + $ClusterNodes, + [System.Management.Automation.PSCredential] $Credential, + [string] $HealthEndpoint, + [System.Collections.ArrayList] $HealthEndPointCheckFailedNodes + ) + + Foreach ($clusNode in $ClusterNodes) + { + $nodeSession = $null + + try + { + if($Credential -eq $Null) + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name + } + else + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name -Credential $Credential + } + + # Check if node can reach cloud billing service + $healthResponse = Invoke-Command -Session $nodeSession -ScriptBlock { Invoke-WebRequest $Using:HealthEndpoint -UseBasicParsing} + + if(($healthResponse -eq $Null) -or ($healthResponse.StatusCode -ne [int][system.net.httpstatuscode]::ok)) + { + Write-Verbose ("StatusCode of invoking cloud billing service health endpoint on node " + $clusNode.Name + " : " + $healthResponse.StatusCode) + $HealthEndPointCheckFailedNodes.Add($clusNode.Name) | Out-Null + continue + } + } + catch + { + Write-Verbose ("Exception occured while testing health endpoint connectivity on Node: " + $clusNode.Name + " Exception: " + $_.Exception) + $HealthEndPointCheckFailedNodes.Add($clusNode.Name) | Out-Null + continue + } + } +} + +function Setup-Certificates{ +param( + $ClusterNodes, + [System.Management.Automation.PSCredential] $Credential, + [string] $ResourceName, + [string] $ObjectId, + [string] $CertificateThumbprint, + [string] $AppId, + [string] $TenantId, + [string] $CloudId, + [string] $ServiceEndpoint, + [string] $BillingServiceApiScope, + [string] $GraphServiceApiScope, + [string] $Authority, + [System.Collections.ArrayList] $NewCertificateFailedNodes, + [System.Collections.ArrayList] $SetCertificateFailedNodes, + [System.Collections.ArrayList] $OSNotLatestOnNodes, + [System.Collections.HashTable] $CertificatesToBeMaintained + ) + + $userProvidedCertAddedToAAD = $false + Foreach ($clusNode in $ClusterNodes) + { + $nodeSession = $null + + try + { + if($Credential -eq $Null) + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name + } + else + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name -Credential $Credential + } + } + catch + { + Write-Debug ("Exception occured in establishing new PSSession. ErrorMessage : " + $_.Exception.Message) + Write-Debug $_ + $NewCertificateFailedNodes.Add($clusNode.Name) | Out-Null + $SetCertificateFailedNodes.Add($clusNode.Name) | Out-Null + continue + } + + # Check if all nodes have required OS version + $nodeUBR = Invoke-Command -Session $nodeSession -ScriptBlock { (Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").UBR } + $nodeBuildNumber = Invoke-Command -Session $nodeSession -ScriptBlock { (Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuildNumber } + + if(($nodeBuildNumber -lt $GAOSBuildNumber) -or (($nodeBuildNumber -eq $GAOSBuildNumber) -and ($nodeUBR -lt $GAOSUBR))) + { + $OSNotLatestOnNodes.Add($clusNode.Name) | Out-Null + continue + } + + if([string]::IsNullOrEmpty($CertificateThumbprint)) + { + # User did not specify certificate, using self-signed certificate + try + { + Invoke-Command -Session $nodeSession -ScriptBlock $hciScript + $certBase64 = Invoke-Command -Session $nodeSession -ScriptBlock { New-AzureStackHCIRegistrationCertificate } + } + catch + { + Write-Debug ("Exception occured in New-AzureStackHCIRegistrationCertificate. ErrorMessage : " + $_.Exception.Message) + Write-Debug $_ + $NewCertificateFailedNodes.Add($clusNode.Name) | Out-Null + continue + } + } + else + { + # Get certificate from cert store. + $x509Cert = $Null; + try + { + $x509Cert = Invoke-Command -Session $nodeSession -ScriptBlock { Get-ChildItem Cert:\LocalMachine -Recurse | Where { $_.Thumbprint -eq $Using:CertificateThumbprint} | Select-Object -First 1} + } + catch{} + + # Certificate not found on node + if($x509Cert -eq $Null) + { + $CertificateNotFoundErrorMessage = $CertificateNotFoundOnNode -f $CertificateThumbprint,$clusNode.Name + return $CertificateNotFoundErrorMessage + } + + # Certificate should be valid for atleast 60 days from now + $60days = New-TimeSpan -Days 60 + $expectedValidTo = (Get-Date) + $60days + + if($x509Cert.NotAfter -lt $expectedValidTo) + { + $UserCertificateValidationErrorMessage = ($UserCertValidationErrorMessage -f $CertificateThumbprint, $x509Cert.NotAfter) + return $UserCertificateValidationErrorMessage + } + + $certBase64 = [System.Convert]::ToBase64String($x509Cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)) + } + + $Cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]([System.Convert]::FromBase64String($CertBase64)) + + # If user provided cert is not already added to AAD app or if we are using one certificate per node + if(($userProvidedCertAddedToAAD -eq $false) -or ([string]::IsNullOrEmpty($CertificateThumbprint))) + { + $AddAppCredentialMessageProgress = $AddAppCredentialMessage -f $ResourceName + Write-Progress -activity $RegisterProgressActivityName -status $AddAppCredentialMessageProgress -percentcomplete 80 + $now = [System.DateTime]::UtcNow + $appCredential = New-AzureADApplicationKeyCredential -ObjectId $ObjectId -Type AsymmetricX509Cert -Usage Verify -Value $CertBase64 -StartDate $now -EndDate $Cert.NotAfter + $CertificatesToBeMaintained.Add($appCredential.KeyId, $true) + $userProvidedCertAddedToAAD = $true + } + + # Set the certificate - Certificate will be set after testing the certificate by calling cloud service API + try + { + $SetCertParams = @{ + ServiceEndpoint = $ServiceEndpoint + BillingServiceApiScope = $BillingServiceApiScope + GraphServiceApiScope = $GraphServiceApiScope + AADAuthority = $Authority + AppId = $AppId + TenantId = $TenantId + CloudId = $CloudId + CertificateThumbprint = $CertificateThumbprint + } + + Invoke-Command -Session $nodeSession -ScriptBlock { Set-AzureStackHCIRegistrationCertificate @Using:SetCertParams } + } + catch + { + Write-Debug ("Exception occured in Set-AzureStackHCIRegistrationCertificate. ErrorMessage : " + $_.Exception.Message) + Write-Debug $_ + $SetCertificateFailedNodes.Add($clusNode.Name) | Out-Null + continue + } + } + + return $null +} + enum OperationStatus { Unused; @@ -569,10 +925,17 @@ enum OperationStatus Cancelled } +enum ConnectionTestResult +{ + Unused; + Succeeded; + Failed +} + <# .Description - Register-AzStackHCI creates a Microsoft.AzureStackHCI cloud resource representing the on-premise cluster and registers the on-premise cluster with Azure. - + Register-AzStackHCI creates a Microsoft.AzureStackHCI cloud resource representing the on-premises cluster and registers the on-premises cluster with Azure. + .PARAMETER SubscriptionId Specifies the Azure Subscription to create the resource. This is the only Mandatory parameter. @@ -580,7 +943,7 @@ enum OperationStatus Specifies the Region to create the resource. Default is EastUS. .PARAMETER ResourceName - Specifies the resource name of the resource created in Azure. If not specified, on-premise cluster name is used. + Specifies the resource name of the resource created in Azure. If not specified, on-premises cluster name is used. .PARAMETER TenantId Specifies the Azure TenantId. @@ -598,11 +961,20 @@ enum OperationStatus Specifies the ARM access token. Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. .PARAMETER EnvironmentName - Specifies the Azure Environment. Default is AzureCloud. Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE + Specifies the Azure Environment. Default is AzureCloud. Valid values are AzureCloud, AzurePPE .PARAMETER ComputerName Specifies the cluster name or one of the cluster node in on-premise cluster that is being registered to Azure. + .PARAMETER CertificateThumbprint + Specifies the thumbprint of the certificate available on all the nodes. User is responsible for managing the certificate. + + .PARAMETER RepairRegistration + Repair the current Azure Stack HCI registration with the cloud. This cmdlet deletes the local certificates on the clustered nodes and the remote certificates in the Azure AD application in the cloud and generates new replacement certificates for both. The resource group, resource name, and other registration choices are preserved. + + .PARAMETER UseDeviceAuthentication + Use device code authentication instead of an interactive browser prompt. + .PARAMETER Credential Specifies the credential for the ComputerName. Default is the current user executing the Cmdlet. @@ -615,7 +987,7 @@ enum OperationStatus .EXAMPLE Invoking on one of the cluster node. - C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" + C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" Result: Success ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/DemoHCICluster1-rg/providers/Microsoft.AzureStackHCI/clusters/DemoHCICluster1 PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/DemoHCICluster1-rg/providers/Microsoft.AzureStackHCI/clusters/DemoHCICluster1/overview @@ -631,7 +1003,7 @@ enum OperationStatus .EXAMPLE Invoking from WAC - C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -ArmAccessToken etyer..ere= -GraphAccessToken acyee..rerrer -AccountId user1@corp1.com -Region westus -ResourceName DemoHCICluster3 -ResourceGroupName DemoHCIRG + C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -ArmAccessToken etyer..ere= -GraphAccessToken acyee..rerrer -AccountId user1@corp1.com -Region westus -ResourceName DemoHCICluster3 -ResourceGroupName DemoHCIRG Result: PendingForAdminConsent ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/DemoHCIRG/providers/Microsoft.AzureStackHCI/clusters/DemoHCICluster3 PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/DemoHCIRG/providers/Microsoft.AzureStackHCI/clusters/DemoHCICluster3/overview @@ -677,6 +1049,15 @@ param( [Parameter(Mandatory = $false)] [string] $ComputerName, + [Parameter(Mandatory = $false)] + [string] $CertificateThumbprint, + + [Parameter(Mandatory = $false)] + [Switch]$RepairRegistration, + + [Parameter(Mandatory = $false)] + [Switch]$UseDeviceAuthentication, + [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] $Credential ) @@ -688,6 +1069,33 @@ param( $registrationOutput = New-Object -TypeName PSObject $operationStatus = [OperationStatus]::Unused + Install-PackageProvider NuGet -Force | Out-Null + $latestModule = Find-Module -Name Az.StackHCI -ErrorAction Ignore + $installedModule = Get-Module -Name Az.StackHCI | Sort-Object -Property Version -Descending | Select-Object -First 1 + + if($latestModule -eq $Null) + { + $CouldNotGetLatestModuleInformationWarningMsg = $CouldNotGetLatestModuleInformationWarning -f $installedModule.Version.Major + Write-Warning $CouldNotGetLatestModuleInformationWarningMsg + } + else + { + if($latestModule.Version.GetType() -eq [string]) + { + $latestModuleVersion = [System.Version]::Parse($latestModule.Version) + } + else + { + $latestModuleVersion = $latestModule.Version + } + + if(($latestModuleVersion.Major -eq $installedModule.Version.Major) -and ($latestModuleVersion -gt $installedModule.Version)) + { + $InstallLatestVersionWarningMsg = $InstallLatestVersionWarning -f $installedModule.Version, $latestModuleVersion + Write-Warning $InstallLatestVersionWarningMsg + } + } + if([string]::IsNullOrEmpty($ComputerName)) { $ComputerName = [Environment]::MachineName @@ -710,16 +1118,37 @@ param( { $clusterNodeSession = New-PSSession -ComputerName $ComputerName -Credential $Credential } - - Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATHCIMessage -percentcomplete 2; - Invoke-Command -Session $clusterNodeSession -ScriptBlock $hciScript - $RegContext = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-AzureStackHCI } } else { - Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATHCIMessage -percentcomplete 2; - Invoke-Command -ScriptBlock $hciScript - $RegContext = Get-AzureStackHCI + $clusterNodeSession = New-PSSession -ComputerName localhost + } + + Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATHCIMessage -percentcomplete 2; + Invoke-Command -Session $clusterNodeSession -ScriptBlock $hciScript + $RegContext = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-AzureStackHCI } + + if($RepairRegistration -eq $true) + { + if(-Not ([string]::IsNullOrEmpty($RegContext.AzureResourceUri))) + { + if([string]::IsNullOrEmpty($ResourceName)) + { + $ResourceName = $RegContext.AzureResourceUri.Split('/')[8] + } + + if([string]::IsNullOrEmpty($ResourceGroupName)) + { + $ResourceGroupName = $RegContext.AzureResourceUri.Split('/')[4] + } + } + else + { + Write-Error -Message $NoExistingRegistrationExistsErrorMessage + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return + } } Write-Progress -activity $RegisterProgressActivityName -status $ValidatingParametersFetchClusterName -percentcomplete 4 @@ -734,18 +1163,9 @@ param( } } - if($IsManagementNode) - { - Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATClusteringMessage -percentcomplete 8; - Invoke-Command -Session $clusterNodeSession -ScriptBlock $clusScript - $getCluster = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-Cluster } - } - else - { - Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATClusteringMessage -percentcomplete 8; - Invoke-Command -ScriptBlock $clusScript - $getCluster = Get-Cluster - } + Write-Progress -activity $RegisterProgressActivityName -status $InstallRSATClusteringMessage -percentcomplete 8; + Invoke-Command -Session $clusterNodeSession -ScriptBlock $clusScript + $getCluster = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-Cluster } if($getCluster -eq $Null) { @@ -766,27 +1186,40 @@ param( $ResourceGroupName = $ResourceName + "-rg" } - $TenantId = Azure-Login -SubscriptionId $SubscriptionId -TenantId $TenantId -ArmAccessToken $ArmAccessToken -GraphAccessToken $GraphAccessToken -AccountId $AccountId -EnvironmentName $EnvironmentName -ProgressActivityName $RegisterProgressActivityName + $TenantId = Azure-Login -SubscriptionId $SubscriptionId -TenantId $TenantId -ArmAccessToken $ArmAccessToken -GraphAccessToken $GraphAccessToken -AccountId $AccountId -EnvironmentName $EnvironmentName -ProgressActivityName $RegisterProgressActivityName -UseDeviceAuthentication $UseDeviceAuthentication Write-Verbose "Register-AzStackHCI triggered - Region: $Region ResourceName: $ResourceName ` - SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName AccountId: $AccountId EnvironmentName: $EnvironmentName" + SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName AccountId: $AccountId EnvironmentName: $EnvironmentName CertificateThumbprint: $CertificateThumbprint RepairRegistration: $RepairRegistration" $resourceId = Get-ResourceId -ResourceName $ResourceName -SubscriptionId $SubscriptionId -ResourceGroupName $ResourceGroupName Write-Verbose "ResourceId : $resourceId" $portalResourceUrl = Get-PortalHCIResourcePageUrl -TenantId $TenantId -EnvironmentName $EnvironmentName -SubscriptionId $SubscriptionId -ResourceGroupName $ResourceGroupName -ResourceName $ResourceName $resource = Get-AzResource -ResourceId $resourceId -ErrorAction Ignore - if($RegContext.RegistrationStatus -eq [RegistrationStatus]::Registered) + if(($RegContext.RegistrationStatus -eq [RegistrationStatus]::Registered) -and ($RepairRegistration -eq $false)) { - if(($RegContext.AzureResourceUri -eq $resourceId) -and ($resource -ne $Null)) # Already registered with same resource Id + if(($RegContext.AzureResourceUri -eq $resourceId)) { - $appId = $resource.Properties.aadClientId - $appPermissionsPageUrl = Get-PortalAppPermissionsPageUrl -AppId $appId -TenantId $TenantId -EnvironmentName $EnvironmentName - $operationStatus = [OperationStatus]::Success + if($resource -ne $Null) + { + # Already registered with same resource Id and resource exists + $appId = $resource.Properties.aadClientId + $appPermissionsPageUrl = Get-PortalAppPermissionsPageUrl -AppId $appId -TenantId $TenantId -EnvironmentName $EnvironmentName + $operationStatus = [OperationStatus]::Success + } + else + { + # Already registered with same resource Id and resource does not exists + $AlreadyRegisteredErrorMessage = $CloudResourceDoesNotExist -f $resourceId + Write-Error -Message $AlreadyRegisteredErrorMessage + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return + } } - else # Already registered with different resource Id or cloud resource does not exist. + else # Already registered with different resource Id { - $AlreadyRegisteredErrorMessage = If ($resource -eq $Null) { $CloudResourceDoesNotExist -f $resourceId } Else { $RegisteredWithDifferentResourceId -f $RegContext.AzureResourceUri } + $AlreadyRegisteredErrorMessage = $RegisteredWithDifferentResourceId -f $RegContext.AzureResourceUri Write-Error -Message $AlreadyRegisteredErrorMessage $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput @@ -801,18 +1234,36 @@ param( $resGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction Ignore - if($resGroup -eq $Null) + if($resource -ne $null) { if([string]::IsNullOrEmpty($Region)) { - $Region = "EastUS" + $Region = $resource.Location + } + elseif($Region -ne $resource.Location) + { + $ResourceExistsInDifferentRegionErrorMessage = $ResourceExistsInDifferentRegionError -f $resource.Location, $Region + Write-Error -Message $ResourceExistsInDifferentRegionErrorMessage + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return } } else { - if([string]::IsNullOrEmpty($Region)) + if($resGroup -eq $Null) { - $Region = $resGroup.Location + if([string]::IsNullOrEmpty($Region)) + { + $Region = "EastUS" + } + } + else + { + if([string]::IsNullOrEmpty($Region)) + { + $Region = $resGroup.Location + } } } @@ -836,7 +1287,14 @@ param( # Lookup cloud endpoint URL from region name - $ServiceEndpointAzureCloud = $ServiceEndpointsAzureCloud[$regionName] + if($regionName -eq $Region_EASTUSEUAP) + { + $ServiceEndpointAzureCloud = $ServiceEndpointsAzureCloud[$regionName] + } + else + { + $ServiceEndpointAzureCloud = $ServiceEndpointAzureCloudFrontDoor + } if($resource -eq $Null) { @@ -857,7 +1315,7 @@ param( { $CreatingResourceGroupMessageProgress = $CreatingResourceGroupMessage -f $ResourceGroupName Write-Progress -activity $RegisterProgressActivityName -status $CreatingResourceGroupMessageProgress -percentcomplete 55 - $resGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Region + $resGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Region -Tag @{$ResourceGroupCreatedByName = $ResourceGroupCreatedByValue} } @@ -887,8 +1345,10 @@ param( $appPermissionsPageUrl = Get-PortalAppPermissionsPageUrl -AppId $appId -TenantId $TenantId -EnvironmentName $EnvironmentName + # Existing AAD app might not have the newly added scopes, if so add them. + AddRequiredPermissionsIfNotPresent -AppId $appId $rolesPresent = Check-UsageAppRoles -AppId $appId - + $GrantingAdminConsentMessageProgress = $GrantingAdminConsentMessage -f $ResourceName Write-Progress -activity $RegisterProgressActivityName -status $GrantingAdminConsentMessageProgress -percentcomplete 65 @@ -922,33 +1382,55 @@ param( Write-Progress -activity $RegisterProgressActivityName -status $GettingCertificateMessage -percentcomplete 70 - if($IsManagementNode) + $clusterNodes = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-ClusterNode } + + $CertificatesToBeMaintained = @{} + $NewCertificateFailedNodes = [System.Collections.ArrayList]::new() + $SetCertificateFailedNodes = [System.Collections.ArrayList]::new() + $OSNotLatestOnNodes = [System.Collections.ArrayList]::new() + + $ServiceEndpoint = "" + $Authority = "" + $BillingServiceApiScope = "" + $GraphServiceApiScope = "" + + Get-EnvironmentEndpoints -EnvironmentName $EnvironmentName -ServiceEndpoint ([ref]$ServiceEndpoint) -Authority ([ref]$Authority) -BillingServiceApiScope ([ref]$BillingServiceApiScope) -GraphServiceApiScope ([ref]$GraphServiceApiScope) + + $setupCertsError = Setup-Certificates -ClusterNodes $clusterNodes -Credential $Credential -ResourceName $ResourceName -ObjectId $objectId -CertificateThumbprint $CertificateThumbprint -AppId $appId -TenantId $TenantId -CloudId $cloudId ` + -ServiceEndpoint $ServiceEndpoint -BillingServiceApiScope $BillingServiceApiScope -GraphServiceApiScope $GraphServiceApiScope -Authority $Authority -NewCertificateFailedNodes $NewCertificateFailedNodes ` + -SetCertificateFailedNodes $SetCertificateFailedNodes -OSNotLatestOnNodes $OSNotLatestOnNodes -CertificatesToBeMaintained $CertificatesToBeMaintained + + if($setupCertsError -ne $null) { - $certBase64 = Invoke-Command -Session $clusterNodeSession -ScriptBlock { New-AzureStackHCIRegistrationCertificate } + Write-Error -Message $setupCertsError + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return } - else + + if(($SetCertificateFailedNodes.Count -ge 1) -or ($NewCertificateFailedNodes.Count -ge 1)) { - $certBase64 = New-AzureStackHCIRegistrationCertificate + # Failed on atleast 1 node + $SettingCertificateFailedMessage = $SettingCertificateFailed -f ($NewCertificateFailedNodes -join ","),($SetCertificateFailedNodes -join ",") + Write-Error -Message $SettingCertificateFailedMessage + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return } - $Cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]([System.Convert]::FromBase64String($CertBase64)) - - $AddAppCredentialMessageProgress = $AddAppCredentialMessage -f $ResourceName - Write-Progress -activity $RegisterProgressActivityName -status $AddAppCredentialMessageProgress -percentcomplete 80 - $now = [System.DateTime]::UtcNow - $appCredential = New-AzureADApplicationKeyCredential -ObjectId $objectId -Type AsymmetricX509Cert -Usage Verify -Value $CertBase64 -StartDate $now -EndDate $Cert.NotAfter + if($OSNotLatestOnNodes.Count -ge 1) + { + $NotAllTheNodesInClusterAreGAError = $NotAllTheNodesInClusterAreGA -f ($OSNotLatestOnNodes -join ",") + Write-Error -Message $NotAllTheNodesInClusterAreGAError + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput + return + } Write-Progress -activity $RegisterProgressActivityName -status $RegisterAndSyncMetadataMessage -percentcomplete 90 # Register by calling on-prem usage service Cmdlet - $ServiceEndpoint = "" - $Authority = "" - $BillingServiceApiScope = "" - $GraphServiceApiScope = "" - - Get-EnvironmentEndpoints -EnvironmentName $EnvironmentName -ServiceEndpoint ([ref]$ServiceEndpoint) -Authority ([ref]$Authority) -BillingServiceApiScope ([ref]$BillingServiceApiScope) -GraphServiceApiScope ([ref]$GraphServiceApiScope) - $RegistrationParams = @{ ServiceEndpoint = $ServiceEndpoint BillingServiceApiScope = $BillingServiceApiScope @@ -958,23 +1440,37 @@ param( TenantId = $TenantId CloudId = $cloudId SubscriptionId = $SubscriptionId - Certificate = $certBase64 ObjectId = $objectId ResourceName = $ResourceName ProviderNamespace = "Microsoft.AzureStackHCI" ResourceArmId = $resourceId ServicePrincipalClientId = $spObjectId + CertificateThumbprint = $CertificateThumbprint } - if($IsManagementNode) - { - Invoke-Command -Session $clusterNodeSession -ScriptBlock { Set-AzureStackHCIRegistration @Using:RegistrationParams } - } - else + Invoke-Command -Session $clusterNodeSession -ScriptBlock { Set-AzureStackHCIRegistration @Using:RegistrationParams } + + # Delete all certificates except certificates which we created in this current registration flow. + if(($RepairRegistration -eq $true) -or (-Not ([string]::IsNullOrEmpty($CertificateThumbprint))) ) { - Set-AzureStackHCIRegistration @RegistrationParams + Foreach ($keyCred in (Get-AzureADApplicationKeyCredential -ObjectId $objectId)) + { + if($CertificatesToBeMaintained[$keyCred.KeyId] -eq $true) + { + Write-Verbose ($SkippingDeleteCertificateFromAADApp -f $keyCred.KeyId) + continue + } + else + { + Write-Verbose ($DeletingCertificateFromAADApp -f $keyCred.KeyId) + Remove-AzureADApplicationKeyCredential -ObjectId $objectId -KeyId $keyCred.KeyId + } + } } + # Delete old unused scopes if present + Remove-OldScopes -AppId $appId + $operationStatus = [OperationStatus]::Success } } @@ -982,16 +1478,27 @@ param( Write-Progress -activity $RegisterProgressActivityName -Completed $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value $operationStatus - $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResourceId -Value $resourceId $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyPortalResourceURL -Value $portalResourceUrl + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResourceId -Value $resourceId $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyPortalAADAppPermissionsURL -Value $appPermissionsPageUrl + if($operationStatus -eq [OperationStatus]::PendingForAdminConsent) + { + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyDetails -Value $AdminConsentWarningMsg + } + else + { + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyDetails -Value $RegistrationSuccessDetailsMessage + } + Write-Output $registrationOutput } catch { - Write-Debug ("Exception occured in Register-AzStackHCI. ErrorMessage : " + $_.Exception.Message) - Write-Debug $_ + Write-Error -Exception $_.Exception -Category OperationStopped + # Get script line number, offset and Command that resulted in exception. Write-Error with the exception above does not write this info. + $positionMessage = $_.InvocationInfo.PositionMessage + Write-Error ("Exception occured in Register-AzStackHCI : " + $positionMessage) -Category OperationStopped throw } finally @@ -1004,14 +1511,14 @@ param( <# .Description - Unregister-AzStackHCI deletes the Microsoft.AzureStackHCI cloud resource representing the on-premise cluster and unregisters the on-premise cluster with Azure. + Unregister-AzStackHCI deletes the Microsoft.AzureStackHCI cloud resource representing the on-premises cluster and unregisters the on-premises cluster with Azure. The registered information available on the cluster is used to unregister the cluster if no parameters are passed. .PARAMETER SubscriptionId Specifies the Azure Subscription to create the resource .PARAMETER ResourceName - Specifies the resource name of the resource created in Azure. If not specified, on-premise cluster name is used. + Specifies the resource name of the resource created in Azure. If not specified, on-premises cluster name is used. .PARAMETER TenantId Specifies the Azure TenantId. @@ -1029,11 +1536,14 @@ param( Specifies the ARM access token. Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. .PARAMETER EnvironmentName - Specifies the Azure Environment. Default is AzureCloud. Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE + Specifies the Azure Environment. Default is AzureCloud. Valid values are AzureCloud, AzurePPE .PARAMETER ComputerName Specifies one of the cluster node in on-premise cluster that is being registered to Azure. + .PARAMETER UseDeviceAuthentication + Use device code authentication instead of an interactive browser prompt. + .PARAMETER Credential Specifies the credential for the ComputerName. Default is the current user executing the Cmdlet. @@ -1091,6 +1601,9 @@ param( [Parameter(Mandatory = $false)] [string] $ComputerName, + [Parameter(Mandatory = $false)] + [Switch]$UseDeviceAuthentication, + [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] $Credential ) @@ -1168,7 +1681,7 @@ param( if ($PSCmdlet.ShouldProcess($resourceId)) { - $TenantId = Azure-Login -SubscriptionId $SubscriptionId -TenantId $TenantId -ArmAccessToken $ArmAccessToken -GraphAccessToken $GraphAccessToken -AccountId $AccountId -EnvironmentName $EnvironmentName -ProgressActivityName $UnregisterProgressActivityName + $TenantId = Azure-Login -SubscriptionId $SubscriptionId -TenantId $TenantId -ArmAccessToken $ArmAccessToken -GraphAccessToken $GraphAccessToken -AccountId $AccountId -EnvironmentName $EnvironmentName -ProgressActivityName $UnregisterProgressActivityName -UseDeviceAuthentication $UseDeviceAuthentication Write-Verbose "Unregister-AzStackHCI triggered - ResourceName: $ResourceName ` SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName ` @@ -1186,6 +1699,48 @@ param( { Remove-AzureStackHCIRegistration } + + if($IsManagementNode) + { + $clusterNodes = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-ClusterNode } + } + else + { + $clusterNodes = Get-ClusterNode + } + + Foreach ($clusNode in $clusterNodes) + { + $nodeSession = $null + + try + { + if($Credential -eq $Null) + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name + } + else + { + $nodeSession = New-PSSession -ComputerName $clusNode.Name -Credential $Credential + } + + if([Environment]::MachineName -eq $clusNode.Name) + { + Remove-AzureStackHCIRegistrationCertificate + } + else + { + Invoke-Command -Session $nodeSession -ScriptBlock { Remove-AzureStackHCIRegistrationCertificate } + } + } + catch + { + Write-Warning ($FailedToRemoveRegistrationCertWarning -f $clusNode.Name) + Write-Debug ("Exception occured in clearing certificate on {0}. ErrorMessage : {1}" -f ($clusNode.Name), ($_.Exception.Message)) + Write-Debug $_ + continue + } + } } $resource = Get-AzResource -ResourceId $resourceId -ErrorAction Ignore @@ -1208,6 +1763,28 @@ param( $remResource = Remove-AzResource -ResourceId $resourceId -Force } + $resGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction Ignore + if($resGroup -ne $Null) + { + $resGroupTags = $resGroup.Tags + + if($resGroupTags -ne $null) + { + $resGroupTagsCreatedBy = $resGroupTags[$ResourceGroupCreatedByName] + + # If resource is created by us during registration and if there are no resources in resource group, then delete it. + if($resGroupTagsCreatedBy -eq $ResourceGroupCreatedByValue) + { + $resourcesInRG = Get-AzResource -ResourceGroupName $ResourceGroupName + + if($resourcesInRG -eq $null) # Resource group is empty + { + Remove-AzResourceGroup -Name $ResourceGroupName -Force | Out-Null + } + } + } + } + $operationStatus = [OperationStatus]::Success } else @@ -1218,12 +1795,20 @@ param( Write-Progress -activity $UnregisterProgressActivityName -Completed $unregistrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value $operationStatus + + if ($operationStatus -eq [OperationStatus]::Success) + { + $unregistrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyDetails -Value $UnregistrationSuccessDetailsMessage + } + Write-Output $unregistrationOutput } catch { - Write-Debug ("Exception occured in Unregister-AzStackHCI. ErrorMessage : " + $_.Exception.Message) - Write-Debug $_ + Write-Error -Exception $_.Exception -Category OperationStopped + # Get script line number, offset and Command that resulted in exception. Write-Error with the exception above does not write this info. + $positionMessage = $_.InvocationInfo.PositionMessage + Write-Error ("Exception occured in Unregister-AzStackHCI : " + $positionMessage) -Category OperationStopped throw } finally @@ -1234,5 +1819,175 @@ param( } } +<# + .Description + Test-AzStackHCIConnection verifies connectivity from on-premises clustered nodes to the Azure services required by Azure Stack HCI. + + .PARAMETER EnvironmentName + Specifies the Azure Environment. Default is AzureCloud. Valid values are AzureCloud, AzurePPE + + .PARAMETER Region + Specifies the Region to connect to. Not used unless it is Canary region. + + .PARAMETER ComputerName + Specifies one of the cluster node in on-premise cluster that is being registered to Azure. + + .PARAMETER Credential + Specifies the credential for the ComputerName. Default is the current user executing the Cmdlet. + + .OUTPUTS + PSCustomObject. Returns following Properties in PSCustomObject + Test: Name of the test performed. + EndpointTested: Endpoint used in the test. + IsRequired: True or False + Result: Succeeded or Failed + FailedNodes: List of nodes on which the test failed. + + .EXAMPLE + Invoking on one of the cluster node. Success case. + C:\PS>Test-AzStackHCIConnection + Test: Connect to Azure Stack HCI Service + EndpointTested: https://azurestackhci-df.azurefd.net/health + IsRequired: True + Result: Succeeded + + .EXAMPLE + Invoking on one of the cluster node. Failed case. + C:\PS>Test-AzStackHCIConnection + Test: Connect to Azure Stack HCI Service + EndpointTested: https://azurestackhci-df.azurefd.net/health + IsRequired: True + Result: Failed + FailedNodes: Node1inClus2, Node2inClus3 +#> +function Test-AzStackHCIConnection{ +param( + [Parameter(Mandatory = $false)] + [string] $EnvironmentName = $AzureCloud, + + [Parameter(Mandatory = $false)] + [string] $Region, + + [Parameter(Mandatory = $false)] + [string] $ComputerName, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] $Credential + ) + + try + { + Setup-Logging -LogFilePrefix "TestAzStackHCIConnection" + + $testConnectionnOutput = New-Object -TypeName PSObject + $connectionTestResult = [ConnectionTestResult]::Unused + + if([string]::IsNullOrEmpty($ComputerName)) + { + $ComputerName = [Environment]::MachineName + $IsManagementNode = $False + } + else + { + $IsManagementNode = $True + } + + if($IsManagementNode) + { + if($Credential -eq $Null) + { + $clusterNodeSession = New-PSSession -ComputerName $ComputerName + } + else + { + $clusterNodeSession = New-PSSession -ComputerName $ComputerName -Credential $Credential + } + } + else + { + $clusterNodeSession = New-PSSession -ComputerName localhost + } + + if(-not([string]::IsNullOrEmpty($Region))) + { + $regionName = Normalize-RegionName -Region $Region + + if($regionName -eq $Region_EASTUSEUAP) + { + $ServiceEndpointAzureCloud = $ServiceEndpointsAzureCloud[$regionName] + } + else + { + $ServiceEndpointAzureCloud = $ServiceEndpointAzureCloudFrontDoor + } + } + + $clusScript = { + $clusterPowershell = Get-WindowsFeature -Name RSAT-Clustering-PowerShell; + if ( $clusterPowershell.Installed -eq $false) + { + Install-WindowsFeature RSAT-Clustering-PowerShell | Out-Null; + } + } + + Invoke-Command -Session $clusterNodeSession -ScriptBlock $clusScript + $getCluster = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-Cluster } + + if($getCluster -eq $Null) + { + $NoClusterErrorMessage = $NoClusterError -f $ComputerName + Write-Error -Message $NoClusterErrorMessage + return + } + else + { + $ServiceEndpoint = "" + $Authority = "" + $BillingServiceApiScope = "" + $GraphServiceApiScope = "" + + Get-EnvironmentEndpoints -EnvironmentName $EnvironmentName -ServiceEndpoint ([ref]$ServiceEndpoint) -Authority ([ref]$Authority) -BillingServiceApiScope ([ref]$BillingServiceApiScope) -GraphServiceApiScope ([ref]$GraphServiceApiScope) + $EndPointToInvoke = $ServiceEndpoint + $HealthEndpointPath + + $clusterNodes = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-ClusterNode } + $HealthEndPointCheckFailedNodes = [System.Collections.ArrayList]::new() + + $testConnectionnOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyTest -Value $ConnectionTestToAzureHCIServiceName + $testConnectionnOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyEndpointTested -Value $EndPointToInvoke + $testConnectionnOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyIsRequired -Value $True + + Check-ConnectionToCloudBillingService -ClusterNodes $clusterNodes -Credential $Credential -HealthEndpoint $EndPointToInvoke -HealthEndPointCheckFailedNodes $HealthEndPointCheckFailedNodes + + if($HealthEndPointCheckFailedNodes.Count -ge 1) + { + # Failed on atleast 1 node + $connectionTestResult = [ConnectionTestResult]::Failed + $testConnectionnOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyFailedNodes -Value $HealthEndPointCheckFailedNodes + } + else + { + $connectionTestResult = [ConnectionTestResult]::Succeeded + } + + $testConnectionnOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value $connectionTestResult + Write-Output $testConnectionnOutput + return + } + } + catch + { + Write-Error -Exception $_.Exception -Category OperationStopped + # Get script line number, offset and Command that resulted in exception. Write-Error with the exception above does not write this info. + $positionMessage = $_.InvocationInfo.PositionMessage + Write-Error ("Exception occured in Test-AzStackHCIConnection : " + $positionMessage) -Category OperationStopped + throw + } + finally + { + Stop-Transcript | out-null + } +} + Export-ModuleMember -Function Register-AzStackHCI Export-ModuleMember -Function Unregister-AzStackHCI +Export-ModuleMember -Function Test-AzStackHCIConnection diff --git a/src/StackHCI/ChangeLog.md b/src/StackHCI/ChangeLog.md index 271ad98bec59..3f1698bcfdbc 100644 --- a/src/StackHCI/ChangeLog.md +++ b/src/StackHCI/ChangeLog.md @@ -18,6 +18,13 @@ - Additional information about change #1 --> ## Upcoming Release +* Made changes to registration script to register the GA version of On-Premises Azure Stack HCI with Azure. + - Supports registering with user provided certificate thumbprint. + - Supports On-Premises Azure Stack HCI OS changes to use independent certificate on cluster nodes. + - Cleans up resource group during unregistration. + - Improves registration output and logging. +* [Breaking Change] Breaks the public preview registration of On-Premises Azure Stack HCI with Azure. + - To register public preview On-Premises Azure Stack HCi with Azure, use 0.3.1 version of Az.StackHCI. ## Version 0.3.1 * Fixed an issue that may block Stack HCI registration. diff --git a/src/StackHCI/help/Az.StackHCI.md b/src/StackHCI/help/Az.StackHCI.md index 5d773a79695c..329d1f910ad1 100644 --- a/src/StackHCI/help/Az.StackHCI.md +++ b/src/StackHCI/help/Az.StackHCI.md @@ -14,6 +14,10 @@ Microsoft Azure PowerShell: StackHCI cmdlets ### [Register-AzStackHCI](Register-AzStackHCI.md) Register-AzStackHCI creates a Microsoft.AzureStackHCI cloud resource representing the on-premise cluster and registers the on-premise cluster with Azure. +### [Test-AzStackHCIConnection](Test-AzStackHCIConnection.md) +Test-AzStackHCIConnection verifies connectivity from on-premises clustered nodes to the Azure services required by Azure Stack HCI. + ### [Unregister-AzStackHCI](Unregister-AzStackHCI.md) Unregister-AzStackHCI deletes the Microsoft.AzureStackHCI cloud resource representing the on-premise cluster and unregisters the on-premise cluster with Azure. +The registered information available on the cluster is used to unregister the cluster if no parameters are passed. diff --git a/src/StackHCI/help/Register-AzStackHCI.md b/src/StackHCI/help/Register-AzStackHCI.md index 305a8bb9e036..a96f4c51ede9 100644 --- a/src/StackHCI/help/Register-AzStackHCI.md +++ b/src/StackHCI/help/Register-AzStackHCI.md @@ -16,7 +16,8 @@ Register-AzStackHCI creates a Microsoft.AzureStackHCI cloud resource representin Register-AzStackHCI [-SubscriptionId] [[-Region] ] [[-ResourceName] ] [[-TenantId] ] [[-ResourceGroupName] ] [[-ArmAccessToken] ] [[-GraphAccessToken] ] [[-AccountId] ] [[-EnvironmentName] ] - [[-ComputerName] ] [[-Credential] ] [] + [[-ComputerName] ] [[-CertificateThumbprint] ] [-RepairRegistration] + [-UseDeviceAuthentication] [[-Credential] ] [] ``` ## DESCRIPTION @@ -70,97 +71,97 @@ PortalAADAppPermissionsURL: https://portal.azure.com/#blade/Microsoft_AAD_Regist ## PARAMETERS -### -SubscriptionId -Specifies the Azure Subscription to create the resource. -This is the only Mandatory parameter. +### -AccountId +Specifies the ARM access token. +Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: -Required: True -Position: 1 +Required: False +Position: 8 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -Region -Specifies the Region to create the resource. -Default is EastUS. +### -ArmAccessToken +Specifies the ARM access token. +Specifying this along with GraphAccessToken and AccountId will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 2 +Position: 6 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ResourceName -Specifies the resource name of the resource created in Azure. -If not specified, on-premise cluster name is used. +### -CertificateThumbprint +Specifies the thumbprint of the certificate available on all the nodes. User is responsible for managing the certificate. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 3 +Position: 11 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -TenantId -Specifies the Azure TenantId. +### -ComputerName +Specifies the cluster name or one of the cluster node in on-premise cluster that is being registered to Azure. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 4 +Position: 10 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ResourceGroupName -Specifies the Azure Resource Group name. -If not specified \-rg will be used as resource group name. +### -Credential +Specifies the credential for the ComputerName. +Default is the current user executing the Cmdlet. ```yaml -Type: String +Type: System.Management.Automation.PSCredential Parameter Sets: (All) Aliases: Required: False -Position: 5 +Position: 12 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ArmAccessToken -Specifies the ARM access token. -Specifying this along with GraphAccessToken and AccountId will avoid Azure interactive logon. +### -EnvironmentName +Specifies the Azure Environment. +Default is AzureCloud. +Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 6 -Default value: None +Position: 9 +Default value: $AzureCloud Accept pipeline input: False Accept wildcard characters: False ``` @@ -170,7 +171,7 @@ Specifies the Graph access token. Specifying this along with ArmAccessToken and AccountId will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: @@ -181,70 +182,115 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -AccountId -Specifies the ARM access token. -Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. +### -Region +Specifies the Region to create the resource. +Default is EastUS. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 8 +Position: 2 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -EnvironmentName -Specifies the Azure Environment. -Default is AzureCloud. -Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE +### -RepairRegistration +Repair the current Azure Stack HCI registration with the cloud. This cmdlet deletes the local certificates on the clustered nodes and the remote certificates in the Azure AD application in the cloud and generates new replacement certificates for both. The resource group, resource name, and other registration choices are preserved. ```yaml -Type: String +Type: System.Management.Automation.SwitchParameter Parameter Sets: (All) Aliases: Required: False -Position: 9 -Default value: $AzureCloud +Position: Named +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` -### -ComputerName -Specifies the cluster name or one of the cluster node in on-premise cluster that is being registered to Azure. +### -ResourceGroupName +Specifies the Azure Resource Group name. +If not specified \-rg will be used as resource group name. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 10 +Position: 5 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -Credential -Specifies the credential for the ComputerName. -Default is the current user executing the Cmdlet. +### -ResourceName +Specifies the resource name of the resource created in Azure. +If not specified, on-premise cluster name is used. ```yaml -Type: PSCredential +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 11 +Position: 3 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` +### -SubscriptionId +Specifies the Azure Subscription to create the resource. +This is the only Mandatory parameter. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TenantId +Specifies the Azure TenantId. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -UseDeviceAuthentication +Use device code authentication instead of an interactive browser prompt. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/src/StackHCI/help/Test-AzStackHCIConnection.md b/src/StackHCI/help/Test-AzStackHCIConnection.md new file mode 100644 index 000000000000..db9034616ba8 --- /dev/null +++ b/src/StackHCI/help/Test-AzStackHCIConnection.md @@ -0,0 +1,129 @@ +--- +external help file: Az.StackHCI-help.xml +Module Name: Az.StackHCI +online version: https://docs.microsoft.com/en-us/powershell/module/az.stackhci/test-azstackhciconnection +schema: 2.0.0 +--- + +# Test-AzStackHCIConnection + +## SYNOPSIS +Test-AzStackHCIConnection verifies connectivity from on-premises clustered nodes to the Azure services required by Azure Stack HCI. + +## SYNTAX + +``` +Test-AzStackHCIConnection [[-EnvironmentName] ] [[-Region] ] [[-ComputerName] ] + [[-Credential] ] [] +``` + +## DESCRIPTION +Test-AzStackHCIConnection verifies connectivity from on-premises clustered nodes to the Azure services required by Azure Stack HCI. + +## EXAMPLES + +### EXAMPLE 1 +``` +Invoking on one of the cluster node. Success case. +``` + +C:\PS\>Test-AzStackHCIConnection +Test: Connect to Azure Stack HCI Service +EndpointTested: https://azurestackhci-df.azurefd.net/health +IsRequired: True +Result: Succeeded + +### EXAMPLE 2 +``` +Invoking on one of the cluster node. Failed case. +``` + +C:\PS\>Test-AzStackHCIConnection +Test: Connect to Azure Stack HCI Service +EndpointTested: https://azurestackhci-df.azurefd.net/health +IsRequired: True +Result: Failed +FailedNodes: Node1inClus2, Node2inClus3 + +## PARAMETERS + +### -ComputerName +Specifies one of the cluster node in on-premise cluster that is being registered to Azure. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Credential +Specifies the credential for the ComputerName. +Default is the current user executing the Cmdlet. + +```yaml +Type: System.Management.Automation.PSCredential +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EnvironmentName +Specifies the Azure Environment. +Default is AzureCloud. +Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: $AzureCloud +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Region +Specifies the Region to connect to. +Not used unless it is Canary region. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### PSCustomObject. Returns following Properties in PSCustomObject +### Test: Name of the test performed. +### EndpointTested: Endpoint used in the test. +### IsRequired: True or False +### Result: Succeeded or Failed +### FailedNodes: List of nodes on which the test failed. +## NOTES + +## RELATED LINKS diff --git a/src/StackHCI/help/Unregister-AzStackHCI.md b/src/StackHCI/help/Unregister-AzStackHCI.md index 41bec0bc8344..c0506d514b4e 100644 --- a/src/StackHCI/help/Unregister-AzStackHCI.md +++ b/src/StackHCI/help/Unregister-AzStackHCI.md @@ -16,7 +16,7 @@ The registered information available on the cluster is used to unregister the cl ``` Unregister-AzStackHCI [[-SubscriptionId] ] [[-ResourceName] ] [[-TenantId] ] [[-ResourceGroupName] ] [[-ArmAccessToken] ] [[-GraphAccessToken] ] - [[-AccountId] ] [[-EnvironmentName] ] [[-ComputerName] ] + [[-AccountId] ] [[-EnvironmentName] ] [[-ComputerName] ] [-UseDeviceAuthentication] [[-Credential] ] [-WhatIf] [-Confirm] [] ``` @@ -60,80 +60,82 @@ Result: Success ## PARAMETERS -### -SubscriptionId -Specifies the Azure Subscription to create the resource +### -AccountId +Specifies the ARM access token. +Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 1 +Position: 7 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ResourceName -Specifies the resource name of the resource created in Azure. -If not specified, on-premise cluster name is used. +### -ArmAccessToken +Specifies the ARM access token. +Specifying this along with GraphAccessToken and AccountId will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 2 +Position: 5 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -TenantId -Specifies the Azure TenantId. +### -ComputerName +Specifies one of the cluster node in on-premise cluster that is being registered to Azure. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 3 +Position: 9 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ResourceGroupName -Specifies the Azure Resource Group name. -If not specified \-rg will be used as resource group name. +### -Credential +Specifies the credential for the ComputerName. +Default is the current user executing the Cmdlet. ```yaml -Type: String +Type: System.Management.Automation.PSCredential Parameter Sets: (All) Aliases: Required: False -Position: 4 +Position: 10 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ArmAccessToken -Specifies the ARM access token. -Specifying this along with GraphAccessToken and AccountId will avoid Azure interactive logon. +### -EnvironmentName +Specifies the Azure Environment. +Default is AzureCloud. +Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 5 -Default value: None +Position: 8 +Default value: $AzureCloud Accept pipeline input: False Accept wildcard characters: False ``` @@ -143,7 +145,7 @@ Specifies the Graph access token. Specifying this along with ArmAccessToken and AccountId will avoid Azure interactive logon. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: @@ -154,82 +156,79 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -AccountId -Specifies the ARM access token. -Specifying this along with ArmAccessToken and GraphAccessToken will avoid Azure interactive logon. +### -ResourceGroupName +Specifies the Azure Resource Group name. +If not specified \-rg will be used as resource group name. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 7 +Position: 4 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -EnvironmentName -Specifies the Azure Environment. -Default is AzureCloud. -Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment, AzureGermanCloud, AzurePPE +### -ResourceName +Specifies the resource name of the resource created in Azure. +If not specified, on-premise cluster name is used. ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 8 -Default value: $AzureCloud +Position: 2 +Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -ComputerName -Specifies one of the cluster node in on-premise cluster that is being registered to Azure. +### -SubscriptionId +Specifies the Azure Subscription to create the resource ```yaml -Type: String +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 9 +Position: 1 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -Credential -Specifies the credential for the ComputerName. -Default is the current user executing the Cmdlet. +### -TenantId +Specifies the Azure TenantId. ```yaml -Type: PSCredential +Type: System.String Parameter Sets: (All) Aliases: Required: False -Position: 10 +Position: 3 Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. +### -UseDeviceAuthentication +Use device code authentication instead of an interactive browser prompt. ```yaml -Type: SwitchParameter +Type: System.Management.Automation.SwitchParameter Parameter Sets: (All) -Aliases: wi +Aliases: Required: False Position: Named -Default value: None +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` @@ -238,7 +237,7 @@ Accept wildcard characters: False Prompts you for confirmation before running the cmdlet. ```yaml -Type: SwitchParameter +Type: System.Management.Automation.SwitchParameter Parameter Sets: (All) Aliases: cf @@ -249,6 +248,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).