diff --git a/src/StackHCI/Az.StackHCI.psd1 b/src/StackHCI/Az.StackHCI.psd1 index 9e32cc5a21d3..becff9ed1271 100644 --- a/src/StackHCI/Az.StackHCI.psd1 +++ b/src/StackHCI/Az.StackHCI.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft Corporation # -# Generated on: 12/28/2022 +# Generated on: 1/30/2023 # @{ @@ -120,7 +120,7 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '* Added support for arc extensions which depend on HCI cluster''s IMDS endpoints.' + # ReleaseNotes = '' # Prerelease string of this module # Prerelease = '' diff --git a/src/StackHCI/Az.StackHCI.psm1 b/src/StackHCI/Az.StackHCI.psm1 index 024b0701bbca..8351db4f372d 100644 --- a/src/StackHCI/Az.StackHCI.psm1 +++ b/src/StackHCI/Az.StackHCI.psm1 @@ -33,7 +33,7 @@ } if(-not $accountsModule) { - Write-Error "`nThis module requires $accountsName version 2.7.5 or greater. For installation instructions, please see: https://learn.microsoft.com/powershell/azure/install-az-ps" -ErrorAction Stop + Write-Error "`nThis module requires $accountsName version 2.7.5 or greater. For installation instructions, please see: https://docs.microsoft.com/powershell/azure/install-az-ps" -ErrorAction Stop } elseif (($accountsModule.Version -lt [System.Version]'2.7.5') -and (-not $localAccounts)) { Write-Error "`nThis module requires $accountsName version 2.7.5 or greater. An earlier version of Az.Accounts is imported in the current PowerShell session. If you are running test, please try to add the switch '-RegenerateSupportModule' when executing 'test-module.ps1'. Otherwise please open a new PowerShell session and import this module again.`nAdditionally, this error could indicate that multiple incompatible versions of Azure PowerShell modules are installed on your system. For troubleshooting information, please see: https://aka.ms/azps-version-error" -ErrorAction Stop } diff --git a/src/StackHCI/ChangeLog.md b/src/StackHCI/ChangeLog.md index 40cddc62887c..26edb99cee55 100644 --- a/src/StackHCI/ChangeLog.md +++ b/src/StackHCI/ChangeLog.md @@ -18,6 +18,13 @@ - Additional information about change #1 --> ## Upcoming Release +* Added Remote Support terms and conditions for HCI device types. +* Unified Resource Group support for both Azure Stack HCI and Arc for server resources. +* Enhanced error feedback and logging in the Register-AzStackHCI cmdlet. +* Bug fixes and improvements in Azure Arc for servers enablement in Register-AzStackHCI cmdlet. +* Improved parameter validations in the Register-AzStackHCI cmdlet. +* Enabled Managed System Identity (MSI) for Registration in Fairfax Cloud. +* Minor bug fixes and improvements. ## Version 1.4.1 * Added support for arc extensions which depend on HCI cluster's IMDS endpoints. diff --git a/src/StackHCI/StackHCI.sln b/src/StackHCI/StackHCI.sln index ab5632ffa0c7..9bd91b20f01b 100644 --- a/src/StackHCI/StackHCI.sln +++ b/src/StackHCI/StackHCI.sln @@ -3,17 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Accounts", "..\Accounts\Accounts\Accounts.csproj", "{FB791932-EDE1-415A-BA76-5EFA5AA8BC6C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Accounts", "..\Accounts\Accounts\Accounts.csproj", "{6F0C64B4-5CB8-4CA6-8575-98BF34AB759B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication", "..\Accounts\Authentication\Authentication.csproj", "{7F22B82C-32E6-4EFB-93B7-5261A8BF3DD6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyLoading", "..\Accounts\AssemblyLoading\AssemblyLoading.csproj", "{6815DB85-CC27-49EF-8109-78F7019733C9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication.ResourceManager", "..\Accounts\Authentication.ResourceManager\Authentication.ResourceManager.csproj", "{51252581-E083-4F85-9F6F-36967BFDC123}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication", "..\Accounts\Authentication\Authentication.csproj", "{3C1BA518-E0AC-47C1-AE7D-27B16F5C14EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationAssemblyLoadContext", "..\Accounts\AuthenticationAssemblyLoadContext\AuthenticationAssemblyLoadContext.csproj", "{4310CACF-4315-4D04-93C3-DD8BAAACB3B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication.ResourceManager", "..\Accounts\Authentication.ResourceManager\Authentication.ResourceManager.csproj", "{AEFE312E-4EC1-4A3E-B676-9B74E6FF8BBC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authenticators", "..\Accounts\Authenticators\Authenticators.csproj", "{3917145E-C778-4BD3-B787-CED2F7102B21}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationAssemblyLoadContext", "..\Accounts\AuthenticationAssemblyLoadContext\AuthenticationAssemblyLoadContext.csproj", "{725668DE-C9C2-4CBD-BA7C-D895D39CE442}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Az.StackHCI", "Az.StackHCI.csproj", "{7BF4B682-53F7-4BA5-A8AE-349D42D29531}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authenticators", "..\Accounts\Authenticators\Authenticators.csproj", "{6925CA77-4203-4DBF-BD49-1E443480BFC2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Az.StackHCI", "Az.StackHCI.csproj", "{1CD10118-7C26-4ED2-9858-8D0908EB86E1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,29 +26,33 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FB791932-EDE1-415A-BA76-5EFA5AA8BC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB791932-EDE1-415A-BA76-5EFA5AA8BC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB791932-EDE1-415A-BA76-5EFA5AA8BC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB791932-EDE1-415A-BA76-5EFA5AA8BC6C}.Release|Any CPU.Build.0 = Release|Any CPU - {7F22B82C-32E6-4EFB-93B7-5261A8BF3DD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F22B82C-32E6-4EFB-93B7-5261A8BF3DD6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F22B82C-32E6-4EFB-93B7-5261A8BF3DD6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F22B82C-32E6-4EFB-93B7-5261A8BF3DD6}.Release|Any CPU.Build.0 = Release|Any CPU - {51252581-E083-4F85-9F6F-36967BFDC123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51252581-E083-4F85-9F6F-36967BFDC123}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51252581-E083-4F85-9F6F-36967BFDC123}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51252581-E083-4F85-9F6F-36967BFDC123}.Release|Any CPU.Build.0 = Release|Any CPU - {4310CACF-4315-4D04-93C3-DD8BAAACB3B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4310CACF-4315-4D04-93C3-DD8BAAACB3B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4310CACF-4315-4D04-93C3-DD8BAAACB3B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4310CACF-4315-4D04-93C3-DD8BAAACB3B9}.Release|Any CPU.Build.0 = Release|Any CPU - {3917145E-C778-4BD3-B787-CED2F7102B21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3917145E-C778-4BD3-B787-CED2F7102B21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3917145E-C778-4BD3-B787-CED2F7102B21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3917145E-C778-4BD3-B787-CED2F7102B21}.Release|Any CPU.Build.0 = Release|Any CPU - {7BF4B682-53F7-4BA5-A8AE-349D42D29531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7BF4B682-53F7-4BA5-A8AE-349D42D29531}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7BF4B682-53F7-4BA5-A8AE-349D42D29531}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7BF4B682-53F7-4BA5-A8AE-349D42D29531}.Release|Any CPU.Build.0 = Release|Any CPU + {6F0C64B4-5CB8-4CA6-8575-98BF34AB759B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F0C64B4-5CB8-4CA6-8575-98BF34AB759B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F0C64B4-5CB8-4CA6-8575-98BF34AB759B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F0C64B4-5CB8-4CA6-8575-98BF34AB759B}.Release|Any CPU.Build.0 = Release|Any CPU + {6815DB85-CC27-49EF-8109-78F7019733C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6815DB85-CC27-49EF-8109-78F7019733C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6815DB85-CC27-49EF-8109-78F7019733C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6815DB85-CC27-49EF-8109-78F7019733C9}.Release|Any CPU.Build.0 = Release|Any CPU + {3C1BA518-E0AC-47C1-AE7D-27B16F5C14EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C1BA518-E0AC-47C1-AE7D-27B16F5C14EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C1BA518-E0AC-47C1-AE7D-27B16F5C14EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C1BA518-E0AC-47C1-AE7D-27B16F5C14EE}.Release|Any CPU.Build.0 = Release|Any CPU + {AEFE312E-4EC1-4A3E-B676-9B74E6FF8BBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEFE312E-4EC1-4A3E-B676-9B74E6FF8BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEFE312E-4EC1-4A3E-B676-9B74E6FF8BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEFE312E-4EC1-4A3E-B676-9B74E6FF8BBC}.Release|Any CPU.Build.0 = Release|Any CPU + {725668DE-C9C2-4CBD-BA7C-D895D39CE442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {725668DE-C9C2-4CBD-BA7C-D895D39CE442}.Debug|Any CPU.Build.0 = Debug|Any CPU + {725668DE-C9C2-4CBD-BA7C-D895D39CE442}.Release|Any CPU.ActiveCfg = Release|Any CPU + {725668DE-C9C2-4CBD-BA7C-D895D39CE442}.Release|Any CPU.Build.0 = Release|Any CPU + {6925CA77-4203-4DBF-BD49-1E443480BFC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6925CA77-4203-4DBF-BD49-1E443480BFC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6925CA77-4203-4DBF-BD49-1E443480BFC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6925CA77-4203-4DBF-BD49-1E443480BFC2}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD10118-7C26-4ED2-9858-8D0908EB86E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CD10118-7C26-4ED2-9858-8D0908EB86E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CD10118-7C26-4ED2-9858-8D0908EB86E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CD10118-7C26-4ED2-9858-8D0908EB86E1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/StackHCI/custom/README.md b/src/StackHCI/custom/README.md index e9c24f15b5a4..d32e6632589a 100644 --- a/src/StackHCI/custom/README.md +++ b/src/StackHCI/custom/README.md @@ -32,7 +32,7 @@ These provide functionality to our HTTP pipeline and other useful features. In s ### Attributes For processing the cmdlets, we've created some additional attributes: - `Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DescriptionAttribute` - - Used in C# cmdlets to provide a high-level description of the cmdlet. This is propagated to reference documentation via [help comments](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_comment_based_help) in the exported scripts. + - Used in C# cmdlets to provide a high-level description of the cmdlet. This is propagated to reference documentation via [help comments](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_comment_based_help) in the exported scripts. - `Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute` - Used in C# and script cmdlets to suppress creating an exported cmdlet at build-time. These cmdlets will *not be exposed* by `Az.StackHCI`. - `Microsoft.Azure.PowerShell.Cmdlets.StackHCI.InternalExportAttribute` diff --git a/src/StackHCI/custom/stackhci.ps1 b/src/StackHCI/custom/stackhci.ps1 index bef5c397f352..2a35d25a458a 100644 --- a/src/StackHCI/custom/stackhci.ps1 +++ b/src/StackHCI/custom/stackhci.ps1 @@ -24,6 +24,10 @@ $SettingCertificateFailed = "Failed to register. Couldn't generate self-signed c $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." +$HCIResourceGroupNameDifferentErrorMessage = "Cluster resource has already been created in resource group : {0}. Skip '-ResourceGroupName' or provide '-ResourceGroupName' as {0} in Register-AzStackHCI to perform registration." +$HCISubscriptionDifferentErrorMessage = "Cluster is already registered to subscription id : {0}. Skip '-SubscriptionId' or provide '-SubscriptionId' as {0} in Register-AzStackHCI to perform registration." +$HCIResourceNameDifferentErrorMessage = "Cluster is already registered with resource name : {0}. Skip '-ResourceName' or provide '-ResourceName' as {0} in Register-AzStackHCI to perform registration." +$ArcResourceGroupNameDifferentErrorMessage = "Azure Arc for servers resource has already been created in resource group : {0}. Skip -ArcServerResourceGroupName or provide -ArcServerResourceGroupName as {0} in Register-AzStackHCI with -RepairRegistration option to perform registration." $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." @@ -33,9 +37,8 @@ $ConnectingToCloudBillingServiceFailed = "Can't reach Azure from node(s) {0}. Ma $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." $ArcCmdletsNotAvailableError = "Azure Arc integration isn't available for the version of Azure Stack HCI installed on node(s) {0} yet. Check the documentation for details. You may need to install an update or join the Preview channel." $ArcRegistrationDisableInProgressError = "Unregister of Azure Arc integration is in progress. Try Unregister-AzStackHCI to finish unregistration and then try Register-AzStackHCI again." -$ArcIntegrationNotAvailableForCloudError = "Azure Arc integration is not available in {0}. Specify '-EnableAzureArcServer:`$false' in Register-AzStackHCI Cmdlet to register without Arc integration." +$ArcIntegrationNotAvailableForCloudError = "Azure Arc for Server integration is not supported in {0}. Specify '-EnableAzureArcServer:`$false' in Register-AzStackHCI Cmdlet to register without Arc integration." $ArcNeedsToBeEnabledError = "Azure Arc for servers integration can't be disabled. Skip the parameter '-EnableAzureArcServer' or Specify '-EnableAzureArcServer:`$true' in Register-AzStackHCI Cmdlet to register." -$ArcResourceGroupExists = "Arc resource group {0} already exists. Please delete the resource group for cluster registration." $ArcAADAppCreationMessage= "Creating AAD application for onboarding ARC" $FetchingRegistrationState = "Checking whether the cluster is already registered" $ValidatingParametersFetchClusterName = "Validating cmdlet parameters" @@ -60,17 +63,27 @@ $DeletingArcCloudResourceMessage = "Deleting Azure resource with ID {0} represen $DeletingExtensionMessage = "Deleting extension {0} on cluster {1}" $RegisterArcMessage = "Arc for servers registration triggered" $UnregisterArcMessage = "Arc for servers unregistration triggered" +$ArcMachineAlreadyExistsInResourceGroupError = "Arc machine(s) with names: {0} already exists in the Resource Group {1}. Use a different Resource group for registration or specify a different Arc for Servers Resource Group." +$SetAzureStackHCIRegistrationErrorMessage = "Exception occurred in Set-AzureStackHCIRegistration. ErrorMessage: {0}" +$ArcAlreadyRegisteredInDifferentResourceGroupError = "Arc servers are already registered in Resource Group: {0}. To change resource groups, please unregister and register again" +$ClusterCreationFailureMessage = "Failed to create cluster resource" $RegisterArcProgressActivityName = "Registering Azure Stack HCI with Azure Arc..." $UnregisterArcProgressActivityName = "Unregistering Azure Stack HCI with Azure Arc..." $RegisterArcRPMessage = "Registering Microsoft.HybridCompute and Microsoft.GuestConfiguration resource providers to subscription" $SetupArcMessage = "Initializing Azure Stack HCI integration with Azure Arc" $StartingArcAgentMessage = "Enabling Azure Arc integration on every clustered node" +$VerifyingArcMessage = "Verifying Azure Arc for Servers registration" $WaitingUnregisterMessage = "Disabling Azure Arc integration on every clustered node" $CleanArcMessage = "Cleaning up Azure Arc integration" +$ArcAlreadyEnabledInADifferentResourceError = "Below mentioned cluster node(s) are already Arc enabled with a different ARM Resource Id:`n{0}`nDisconnect Arc agent on these nodes and run Register-AzStackHCI again." + $ArcAgentRolesInsufficientPreviligeMessage = "Failed to assign required roles for Azure Arc integration. Your Azure AD account must be an Owner or User Access Administrator in the subscription to enable Azure Arc integration." $RegisterArcFailedWarningMessage = "Some clustered nodes couldn't be Arc-enabled right now. This can happen if some of the nodes are down. We'll automatically try again in an hour. In the meantime, you can use Get-AzureStackHCIArcIntegration to check status on each node." +$RegisterArcFailedExceptionMessage = "Failed to enable Arc on some clustered nodes." +$ArcSettingsPatchFailedWarningMessage = "Arc for Servers registration failed. Visit https://learn.microsoft.com/en-us/azure-stack/hci/deploy/troubleshoot-hci-registration#registration-completes-successfully-but-azure-arc-connection-in-portal-says-not-installed and follow the troubleshooting steps. If Azure-Arc registration continues failing for more than 12 hours, contact support." +$ArcSettingsPatchFailedLogMessage = "Arc for Servers registration failed. Unable to find the cluster nodes in Arc Settings resource." $UnregisterArcFailedError = "Couldn't disable Azure Arc integration on Node {0}. Try running Disable-AzureStackHCIArcIntegration Cmdlet on the node. If the node is in a state where Disable-AzureStackHCIArcIntegration Cmdlet could not be run, remove the node from the cluster and try Unregister-AzStackHCI Cmdlet again." $ArcExtensionCleanupFailedError = "Couldn't delete Arc extension {0} on cluster nodes. You can try the extension uninstallation steps listed at https://docs.microsoft.com/en-us/azure/azure-arc/servers/manage-agent for removing the extension and try Unregister-AzStackHCI again. If the node is in a state where extension uninstallation could not succeed, try Unregister-AzStackHCI with -Force switch." $ArcExtensionCleanupFailedWarning = "Couldn't delete Arc extension {0} on cluster nodes. Extension may continue to run even after unregistration." @@ -114,8 +127,10 @@ $SetupCloudManagementActivityName = "Cloud Management configuration..." $ConfiguringCloudManagementMessage = "Configuring Cloud Management agent." $ConfiguringCloudManagementClusterSvc = "Creating Cloud Management cluster resource." $StartingCloudManagementMessage = "Starting Cloud Management agent." +$RemoteSupportConsentText = "`r`n`r`nBy approving this request, the Microsoft support organization or the Azure engineering team supporting this feature ('Microsoft Support Engineer') will be given direct access to your device for troubleshooting purposes and/or resolving the technical issue described in the Microsoft support case. `r`n`r`nDuring a remote support session, a Microsoft Support Engineer may need to collect logs. By enabling remote support, you have agreed to a diagnostic logs collection by Microsoft Support Engineer to address a support case You also acknowledge and consent to the upload and retention of those logs in an Azure storage account managed and controlled by Microsoft. These logs may be accessed by Microsoft in the context of a support case and to improve the health of Azure Stack HCI. `r`n`r`nThe data will be used only to troubleshoot failures that are subject to a support ticket, and will not be used for marketing, advertising, or any other commercial purposes without your consent. The data may be retained for up to ninety (90) days and will be handled following our standard privacy practices (https://privacy.microsoft.com/en-US/). Any data previously collected with your consent will not be affected by the revocation of your permission." $MaskString = "XXXXXXX" +$AlreadyLoggedFlag = "Already Logged" #endregion #region Constants @@ -174,9 +189,10 @@ $AuthorityAzureGermanCloud = "https://login.microsoftonline.de" $BillingServiceApiScopeAzureGermanCloud = "https://azurestackhci-usage.azurewebsites.de/.default" $GraphServiceApiScopeAzureGermanCloud = "https://graph.cloudapi.de/.default" -$RPAPIVersion = "2022-10-01"; -$HCIArcAPIVersion = "2022-03-01" +$RPAPIVersion = "2022-12-01"; +$HCIArcAPIVersion = "2022-05-01" $HCIArcExtensionAPIVersion = "2021-09-01" +$HCApiVersion = "2022-03-10" $HCIArcInstanceName = "/arcSettings/default" $HCIArcExtensions = "/Extensions" @@ -205,14 +221,21 @@ $ArcProgressBarId = 2 $AzureConnectedMachineOnboardingRole = "Azure Connected Machine Onboarding" $AzureConnectedMachineResourceAdministratorRole = "Azure Connected Machine Resource Administrator" +$ArcOnboardingRole = "Azure Connected Machine Resource Manager" $ArcRegistrationTaskName = "ArcRegistrationTask" $LogFileDir = '\Tasks\ArcForServers' +$ArcMachineResourceType = "Microsoft.HybridCompute/machines" + $ClusterScheduledTaskWaitTimeMinutes = 15 $ClusterScheduledTaskSleepTimeSeconds = 3 $ClusterScheduledTaskRunningState = "Running" $ClusterScheduledTaskReadyState = "Ready" +$GetArcSettingsWaitTimeMinutes = 1 +$GetArcSettingsSleepTimeSeconds = 15 +$ArcSettingsVerificationLimit = 5 + $ArcSettingsDisableInProgressState = "DisableInProgress" # Cluster Agent Service Names @@ -280,66 +303,55 @@ Function Write-WarnLog{ Write-Log -Level "WARN" -Message $Message } +<# +Writes the Error output to registration log file and console +If Category is passed as 'OperationStopped', the Script will not write the error message again in the final catch block +#> Function Write-ErrorLog{ [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] [CmdletBinding()] param( - [Parameter(Mandatory=$False)] + [Parameter(Mandatory=$true)] [string] $Message, - [Parameter(Mandatory=$False)] + [Parameter(Mandatory=$false)] + [System.Management.Automation.ErrorRecord] + $Exception, + [Parameter(Mandatory=$false)] [string] - $Category, - [Parameter(Mandatory=$False)] - [Exception] - $Exception + $Category ) + $ErrorLogMessage = $PSBoundParameters["Message"] + $WriteErrorMessage = $PSBoundParameters["Message"] - if($PSBoundParameters["Exception"] -and $PSBoundParameters["Message"]) + if($PSBoundParameters["Exception"]) { - $ErrorLogMessageWithException = "{0} Exception: {1}" - Write-Log -Level "ERROR" -Message ($ErrorLogMessageWithException -f ($PSBoundParameters["Message"], $PSBoundParameters["Exception"])) - }elseif($PSBoundParameters["Message"]) - { - Write-Log -Level "ERROR" -Message $PSBoundParameters["Message"] - } elseif($PSBoundParameters["Exception"]) - { - Write-Log -Level "ERROR" -Message $PSBoundParameters["Exception"] + $exceptionFormatted = $Exception | Format-List * -Force | Out-String + $invocationInfoFormatted = $Exception.InvocationInfo | Format-List * -Force | Out-String + $innerExceptionFormatted = "" + $_.Exception.InnerException | ForEach-Object { + $innerExceptionFormatted = $innerExceptionFormatted + ($_ | Format-List * -Force | Out-String) + } | Out-Null + $ErrorLogMessage = $ErrorLogMessage + ("`n{0}`n{1}`n{2}" -f $exceptionFormatted, $invocationInfoFormatted, $innerExceptionFormatted) + $WriteErrorMessage = $WriteErrorMessage + "`n{0}" -f $exceptionFormatted } - Write-Error @PSBoundParameters -} - -# Writes an error without the stack trace -Function Write-ErrorMessage { - [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] - [CmdletBinding()] - param( - [Parameter(Mandatory=$False)] - [string] - $Message, - [Parameter(Mandatory=$False)] - [string] - $Category, - [Parameter(Mandatory=$False)] - [Exception] - $Exception - ) + # Writing error message in the log file + Write-Log -Level "ERROR" -Message $ErrorLogMessage - if($PSBoundParameters["Exception"] -and $PSBoundParameters["Message"]) - { - $ErrorLogMessageWithException = "{0} Exception: {1}" - Write-Log -Level "ERROR" -Message ($ErrorLogMessageWithException -f ($PSBoundParameters["Message"], $PSBoundParameters["Exception"])) - }elseif($PSBoundParameters["Message"]) + if($PSBoundParameters["Category"]) { - Write-Log -Level "ERROR" -Message $PSBoundParameters["Message"] - } elseif($PSBoundParameters["Exception"]) - { - Write-Log -Level "ERROR" -Message $PSBoundParameters["Exception"] + $WriteErrorMessage = $WriteErrorMessage + "`nCategoryInfo: {0}" -f $Category.ToString() } - Write-Error @PSBoundParameters -ErrorAction SilentlyContinue - $Host.UI.WriteErrorLine($Message) + # Writing error message on the console + $Host.UI.WriteErrorLine($WriteErrorMessage) + + # If Category is 'OperationStopped', add 'Already Logged' flag in the $Error variable to prevent logging exception again in the final catch block + if($PSBoundParameters["Category"] -eq "OperationStopped") + { + $Error.Add($AlreadyLoggedFlag) | Out-Null + } } Function Write-NodeEventLog{ @@ -429,6 +441,23 @@ Function Print-FunctionParameters{ return "Parameters for {0} are: {1}" -f $Message, ($body | Out-String ) } +$CheckNodeArcRegistrationStateScriptBlock = { + if(Test-Path -Path "C:\Program Files\AzureConnectedMachineAgent\azcmagent.exe") + { + $arcAgentStatus = Invoke-Expression -Command "& 'C:\Program Files\AzureConnectedMachineAgent\azcmagent.exe' show -j" + + # Parsing the status received from Arc agent + $arcAgentStatusParsed = $arcAgentStatus | ConvertFrom-Json + + # Throw an error if the node is Arc enabled to a different resource group or subscription id + if(($arcAgentStatusParsed.subscriptionId -ne $Using:SubscriptionId) -or ($arcAgentStatusParsed.resourceGroup -ne $Using:ArcResourceGroupName)) + { + $differentResourceExceptionMessage = "{0}: Subscription Id: {1}, Resource Group: {2}" -f $Using:clusterNode, $arcAgentStatusParsed.subscriptionId, $arcAgentStatusParsed.resourceGroup + throw $differentResourceExceptionMessage + } + } +} + $registerArcScript = { try { @@ -464,6 +493,8 @@ $registerArcScript = { Write-Information 'Triggering Arc For Servers registration cmdlet' $arcStatus = Get-AzureStackHCIArcIntegration + $EnableAzureStackHCIArcIntegrationRetrySleepTimeSeconds = 10 + if ($arcStatus.ClusterArcStatus -eq 'Enabled') { $nodeStatus = $arcStatus.NodesArcStatus @@ -474,7 +505,24 @@ $registerArcScript = { { Write-Information 'Registering Arc for servers.' Write-EventLog -LogName Application -Source 'HCI Registration' -EventId 9002 -EntryType 'Information' -Message 'Initiating Arc For Servers registration' - Enable-AzureStackHCIArcIntegration -AgentInstallerWebLink $AgentInstaller_WebLink -AgentInstallerName $AgentInstaller_Name -AgentInstallerLogFile $AgentInstaller_LogFile -AgentExecutablePath $AgentExecutable_Path + Enable-AzureStackHCIArcIntegration -AgentInstallerWebLink $AgentInstaller_WebLink -AgentInstallerName $AgentInstaller_Name -AgentInstallerLogFile $AgentInstaller_LogFile -AgentExecutablePath $AgentExecutable_Path *>&1 | Tee-Object -Variable enableArcIntegrationOutput + + $IsEnableArcIntegrationSuccessful = $true + $enableArcIntegrationOutput | foreach { + if($_.ToString().Contains('401 Unauthorized')) + { + $IsEnableArcIntegrationSuccessful = $false + } + } + + if(-Not $IsEnableArcIntegrationSuccessful) + { + Write-EventLog -LogName Application -Source 'HCI Registration' -EventId 9006 -EntryType 'Information' -Message 'Failed to enable Azure Arc integration. Trying it again.' + Write-Information 'Failed to enable Azure Arc integration. Trying it again.' + Start-Sleep -Seconds $EnableAzureStackHCIArcIntegrationRetrySleepTimeSeconds + Enable-AzureStackHCIArcIntegration -AgentInstallerWebLink $AgentInstaller_WebLink -AgentInstallerName $AgentInstaller_Name -AgentInstallerLogFile $AgentInstaller_LogFile -AgentExecutablePath $AgentExecutable_Path -ErrorAction Stop + } + Sync-AzureStackHCI Write-EventLog -LogName Application -Source 'HCI Registration' -EventId 9003 -EntryType 'Information' -Message 'Completed Arc For Servers registration' } @@ -500,14 +548,26 @@ $registerArcScript = { } catch { - Write-Error -Exception $_.Exception -Category OperationStopped # Get script line number, offset and Command that resulted in exception. Write-ErrorLog with the exception above does not write this info. $positionMessage = $_.InvocationInfo.PositionMessage - Write-EventLog -LogName Application -Source 'HCI Registration' -EventId 9116 -EntryType 'Warning' -Message 'Failed Arc For Servers registration: '+$positionMessage - Write-Error ('Exception occurred in RegisterArcScript : ' + $positionMessage) -Category OperationStopped + Write-EventLog -LogName Application -Source 'HCI Registration' -EventId 9116 -EntryType 'Warning' -Message ('Failed Arc For Servers registration: '+ $positionMessage) + Write-Error ('Exception occurred in RegisterArcScript : ' + $positionMessage) -Exception $_.Exception -Category OperationStopped } finally { + $customImdsScript = { try{ $customImdsRegKey = Get-Item 'HKLM:\Software\Microsoft\Windows Azure\CurrentVersion\IMDS' -ErrorAction Stop } catch{ $customImdsRegKey = New-Item 'HKLM:\Software\Microsoft\Windows Azure\CurrentVersion\IMDS' -Force -ErrorAction Stop } $customImdsRegKey | New-ItemProperty -Name 'CustomIMDSHostAddress' -Value 'http://127.0.0.1:42542' -Force -ErrorAction Stop | Out-Null } + try + { + Write-Verbose ('Configuring CustomIMDSHostAddress') + Invoke-Command -ScriptBlock $customImdsScript + } + catch + { + Write-Verbose ('Exception occurred while setting custom IMDS host. ErrorMessage : ' + $_.Exception.Message) + Write-Verbose ($_ | Out-String) + Write-Warning ('Could not configure CustomIMDSHostAddress for node.') + } + try{ Stop-Transcript } catch {} } } @@ -583,7 +643,7 @@ function Execute-Without-ProgressBar{ } catch { - Write-ErrorLog -Exception $_.Exception -Message "Exception occured while executing cmd: $ScriptBlock" -ErrorAction Continue + Write-ErrorLog -Exception $_ -Message "Exception occured while executing cmd: $ScriptBlock" -ErrorAction Continue throw } finally @@ -593,6 +653,36 @@ function Execute-Without-ProgressBar{ return $result } +<# +Executes the given script inside an Invoke-Command. +Useful for scripts where the error needs to be caught inside the Invoke-Command. +Note: The parameters variable used inside $ScriptBlock should be named $Params or the method won't work as expected +#> +function Run-InvokeCommand { + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param ( + [parameter(Mandatory=$true)] + [scriptblock] $ScriptBlock, + [parameter(Mandatory=$true)] + [System.Management.Automation.Runspaces.PSSession] $Session, + [parameter(Mandatory=$true)] + [System.Collections.HashTable] $Params + ) + Invoke-Command -Session $Session -ArgumentList $Params, $ScriptBlock -ScriptBlock { + param ($Params, $ScriptBlock) + try { + Invoke-Expression $ScriptBlock + } + catch { + if($null -ne $_.Exception.InnerException.Detail.Error) + { + throw $_.Exception.InnerException.Detail.Error + } + throw $_.Exception.Message + } + } +} + function Retry-Command { [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] param ( @@ -1066,8 +1156,7 @@ param( [string] $Region, [ref] $SupportedRegions ) - $resources = Retry-Command -ScriptBlock { Get-AzResourceProvider -ProviderNamespace Microsoft.AzureStackHCI } -RetryIfNullOutput $true - $locations = $resources.Where{($_.ResourceTypes.ResourceTypeName -eq 'clusters' -and $_.RegistrationState -eq 'Registered')}.Locations + $locations = Retry-Command -ScriptBlock { (Get-AzResourceProvider -ProviderNamespace Microsoft.AzureStackHCI).Where{($_.ResourceTypes.ResourceTypeName -eq 'clusters' -and $_.RegistrationState -eq 'Registered')}.Locations } -RetryIfNullOutput $true Write-VerboseLog ("RP supported regions : $locations") $locations | foreach { $regionName = Normalize-RegionName -Region $_ @@ -1114,7 +1203,7 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Message "Exception occured while registering $ProviderNamespace RP" -ErrorAction Continue + Write-ErrorLog -Exception $_ -Message "Exception occured while registering $ProviderNamespace RP" -ErrorAction Continue throw } } @@ -1331,7 +1420,7 @@ param( # Set the certificate - Certificate will be set after testing the certificate by calling cloud service API try { - $SetCertParams = @{ + $Params = @{ ServiceEndpoint = $ServiceEndpoint BillingServiceApiScope = $BillingServiceApiScope GraphServiceApiScope = $GraphServiceApiScope @@ -1342,13 +1431,17 @@ param( CertificateThumbprint = $CertificateThumbprint } - Invoke-Command -Session $nodeSession -ScriptBlock { Set-AzureStackHCIRegistrationCertificate @Using:SetCertParams } - Write-VerboseLog ("successfully updated authentication parameters on node: {0}" -f ($SetCertParams | Out-String)) + $SetAzureStackHCIRegistrationCertificateScript = { + Set-AzureStackHCIRegistrationCertificate -ErrorAction Stop @Params + } + + Run-InvokeCommand -ScriptBlock $SetAzureStackHCIRegistrationCertificateScript -Session $nodeSession -Params $Params + + Write-VerboseLog ("successfully updated authentication parameters on node: {0}" -f ($Params | Out-String)) } catch { Write-VerboseLog ("Exception occurred in Set-AzureStackHCIRegistrationCertificate. ErrorMessage : " + $_.Exception.Message) - Write-VerboseLog ($_) $SetCertificateFailedNodes.Add($clusNode.Name) | Out-Null continue } @@ -1371,7 +1464,7 @@ function Assign-ArcRoles { $arcSPNRbacRoles = Get-AzRoleAssignment -ObjectId $SpObjectId $foundConnectedMachineOnboardingRole=$false $foundMachineResourceAdminstratorRole=$false - $arcSPNRbacRoles | ForEach-Object { + $arcSPNRbacRoles | ForEach-Object { $roleName = $_.RoleDefinitionName if ($roleName -eq $AzureConnectedMachineOnboardingRole) { @@ -1393,21 +1486,21 @@ function Assign-ArcRoles { Write-VerboseLog ("successfully assigned RBAC Roles to ARC SP: {0}" -f $SpObjectId) $stopLoop = $true } - catch + catch { $positionMessage = $_.InvocationInfo.PositionMessage - if ($retryCount -ge $maxRetryCount) + if ($retryCount -ge $maxRetryCount) { # Timed out. Write-WarnLog ("Failed to assign roles to service principal with object Id $($SpObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $positionMessage) return $false } - if ($_.Exception.Message.Contains("Microsoft.Authorization/roleAssignments/write")) { + if ($_.Exception.Response.Content.Contains("Microsoft.Authorization/roleAssignments/write")) { Write-VerboseLog ("New-AzRoleAssignment Missing Permissions. IsWAC: $IsWAC") - if ($IsWAC -eq $false) + if ($IsWAC -eq $false) { # Insufficient privilige error. - Write-ErrorLog -Message $ArcAgentRolesInsufficientPreviligeMessage -ErrorAction Continue + Write-ErrorLog -Message $ArcAgentRolesInsufficientPreviligeMessage -Exception $_ -ErrorAction Continue } return $false } @@ -1420,6 +1513,146 @@ function Assign-ArcRoles { While (-Not $stopLoop) return $true } + +function Verify-ArcSettings{ + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [string] $ArcResourceId, + [System.Management.Automation.Runspaces.PSSession] $Session + ) + Write-VerboseLog "Verifying Arc settings resource" + $verifyArcSettingsOutput = [ErrorDetail]::ArcIntegrationFailedOnNodes + $clusterNodes = Invoke-Command -Session $Session -ScriptBlock { Get-ClusterNode } | ForEach-Object { ($_.Name) } + $limit = (Get-Date).AddMinutes($GetArcSettingsWaitTimeMinutes) + + while ((Get-Date) -lt $limit) { + if($verifyArcSettingsOutput -eq [ErrorDetail]::ArcIntegrationFailedOnNodes) + { + Write-VerboseLog "Waiting for $GetArcSettingsSleepTimeSeconds seconds" + Start-Sleep -Seconds $GetArcSettingsSleepTimeSeconds + } + else + { + Write-VerboseLog "Arc settings resource successfully verified" + break + } + $verifyArcSettingsOutput = [ErrorDetail]::Success + + $arcSettingsResponse = Get-AzResource -ResourceId $ArcResourceId -ApiVersion $HCIArcAPIVersion + + $arcSettingsNodesMap = @{} + $arcSettingsResponse.properties.perNodeDetails | ForEach-Object { $arcSettingsNodesMap.Add($_.Name, $_.State) } | Out-Null + + Foreach ($clusterNode in $clusterNodes) + { + if((($arcSettingsNodesMap.Contains($clusterNode) -eq $false) | Out-Null) -or ($arcSettingsNodesMap[$clusterNode] -ne "Connected")) + { + Write-VerboseLog "Cluster node : $clusterNode is not in connected state in ArcSettings." + $verifyArcSettingsOutput = [ErrorDetail]::ArcIntegrationFailedOnNodes + } + } + } + return $verifyArcSettingsOutput +} + +function Verify-ArcRegistration{ + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [string] $ArcResourceId, + [System.Management.Automation.Runspaces.PSSession] $Session, + [bool] $IsManagementNode, + [System.Management.Automation.PSCredential] $Credential, + [string] $ComputerName + ) + Write-VerboseLog "Verifying Arc for Servers registration" + # Get and verify Arc Settings resource + $arcRegistrationOutput = Verify-ArcSettings -Session $Session -ArcResourceId $ArcResourceId + + $arcSettingsVerificationCount = 0 + while($arcSettingsVerificationCount -lt $ArcSettingsVerificationLimit) + { + if($arcRegistrationOutput -eq [ErrorDetail]::ArcIntegrationFailedOnNodes) + { + Write-VerboseLog "Unable to find the cluster nodes in Arc Settings resource. Triggering Sync-AzureStackHCI again." + # Trigger Sync-AzureStackHCI to patch Arc settings + Invoke-Command -Session $Session -ScriptBlock { Sync-AzureStackHCI } + + # Get and verify Arc Settings resource + $arcRegistrationOutput = Verify-ArcSettings -Session $Session -ArcResourceId $ArcResourceId + } + else + { + break + } + $arcSettingsVerificationCount++ + } + + if($arcRegistrationOutput -eq [ErrorDetail]::ArcIntegrationFailedOnNodes) + { + Write-VerboseLog $ArcSettingsPatchFailedLogMessage + Write-NodeEventLog -Message $ArcSettingsPatchFailedLogMessage -EventID 9123 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName + Write-Warning $ArcSettingsPatchFailedWarningMessage + } + else + { + Write-VerboseLog "Successfully verified Arc for Servers registration. Arc for Servers registration succeeded." + } + + return $arcRegistrationOutput +} + +function Verify-NodesArcRegistrationState{ + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + $ClusterNodes, + [string] $SubscriptionId, + [string] $ArcResourceGroupName, + [System.Management.Automation.PSCredential] $Credential, + [string] $ClusterDNSSuffix + ) + $NodesAlreadyArcEnabledDifferentResource = [System.Collections.ArrayList]::new() + + foreach ($clusterNode in $clusterNodes) + { + # Create session into the cluster node + $clusterNodeWithDNSSuffix = "$clusterNode.$ClusterDNSSuffix" + if($Null -eq $Credential) + { + $nodeSession = New-PSSession -ComputerName $clusterNodeWithDNSSuffix + } + else + { + $nodeSession = New-PSSession -ComputerName $clusterNodeWithDNSSuffix -Credential $Credential + } + + try + { + Invoke-Command -Session $nodeSession -ScriptBlock $CheckNodeArcRegistrationStateScriptBlock -ErrorAction Stop + } + catch + { + if(($null -ne $_.Exception.Message) -and $_.Exception.Message.Contains($clusterNode) -and $_.Exception.Message.Contains("Subscription Id") -and $_.Exception.Message.Contains("Resource Group")) + { + $NodesAlreadyArcEnabledDifferentResource.Add($_.Exception.Message) | Out-Null + } + else + { + Write-WarnLog $_.Exception.Message + } + } + + # Cleanup node session + Remove-PSSession $nodeSession | Out-Null + } + + if($NodesAlreadyArcEnabledDifferentResource.Length -gt 0) + { + $NodesAlreadyArcEnabledDifferentResource = $NodesAlreadyArcEnabledDifferentResource -join "`n" + $ExceptionMessage = $ArcAlreadyEnabledInADifferentResourceError -f $NodesAlreadyArcEnabledDifferentResource + throw $ExceptionMessage + } +} + function Enable-ArcForServers{ [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] param( @@ -1465,22 +1698,19 @@ param( Invoke-Command -Session $Session -ScriptBlock { $nodeStatus = $(Get-AzureStackHCIArcIntegration).NodesArcStatus - if ($nodeStatus -ne $null -and $nodeStatus.Count -ge $clusterNodeNames.Count) + if (($null -ne $nodeStatus) -and ($nodeStatus.Count -ge $clusterNodeNames.Count)) { Foreach ($node in $nodeStatus.Keys) { if($nodeStatus[$node] -ne $using:enabledArcStatus) { - Write-Warning ( $using:RegisterArcFailedWarningMessage) - $retStatus = [ErrorDetail]::ArcIntegrationFailedOnNodes - break + throw $using:RegisterArcFailedExceptionMessage } } } else { - Write-Warning ($using:RegisterArcFailedWarningMessage) - $retStatus = [ErrorDetail]::ArcIntegrationFailedOnNodes + throw $using:RegisterArcFailedExceptionMessage } } } @@ -1489,7 +1719,6 @@ param( Write-WarnLog ($RegisterArcFailedWarningMessage) $retStatus = [ErrorDetail]::ArcIntegrationFailedOnNodes Write-VerboseLog ("Exception occurred in registering nodes to Arc For Servers. ErrorMessage : {0}" -f ($_.Exception.Message)) - Write-VerboseLog ($_) } # Cleanup sessions. @@ -1633,11 +1862,11 @@ param( } else { - if($ArcResource.Properties.arcApplicationObjectId -eq $Null) + if($Null -eq $ArcResource.Properties.arcApplicationObjectId) { Write-VerboseLog ("Initiating Arc AAD App creation by HCI RP") Write-Progress -Id $ArcProgressBarId -ParentId $MainProgressBarId -Activity $RegisterArcProgressActivityName -Status $ArcAADAppCreationMessage -PercentComplete 30 - $arcIdentity = Execute-Without-ProgressBar -ScriptBlock { Invoke-AzResourceAction -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -Action createArcIdentity -Force } + Execute-Without-ProgressBar -ScriptBlock { Invoke-AzResourceAction -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -Action createArcIdentity -Force } | Out-Null $ArcResource = Get-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -ErrorAction Ignore Write-VerboseLog ("Created Arc AAD App by HCI service") } @@ -1734,7 +1963,7 @@ param( catch { $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in registering cluster scheduled task. ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $positionMessage) -Category OperationStopped -ErrorAction Continue + Write-ErrorLog ("Exception occurred in registering cluster scheduled task") -Exception $_ -Category OperationStopped -ErrorAction Continue throw } finally @@ -1749,12 +1978,18 @@ param( Write-Progress -Id $ArcProgressBarId -ParentId $MainProgressBarId -Activity $RegisterArcProgressActivityName -Status $StartingArcAgentMessage -PercentComplete 50 $arcResult = Enable-ArcForServers -Session $session -Credential $Credential -ClusterDNSSuffix $ClusterDNSSuffix + if($arcResult -eq [ErrorDetail]::Success) + { + Write-Progress -Id $ArcProgressBarId -ParentId $MainProgressBarId -Activity $RegisterArcProgressActivityName -Status $VerifyingArcMessage -PercentComplete 80 + Write-VerboseLog ("Successfully registered cluster with Arc for Servers.") + + $arcResult = Verify-ArcRegistration -ArcResourceId $arcResourceId -Session $session -Credential $Credential -ComputerName $ComputerName -IsManagementNode $IsManagementNode + } + Write-Progress -Id $ArcProgressBarId -activity $RegisterArcProgressActivityName -Completed Remove-PSSession $session | Out-Null - Write-VerboseLog ("Successfully registered cluster with Arc for Servers.") - return $arcResult } @@ -1852,6 +2087,7 @@ param( { Write-VerboseLog ("querying installed extensions") $extensions = Get-AzResource -ResourceId $arcResourceExtensions -ApiVersion $HCIArcExtensionAPIVersion + $ArcResourceGroupName = $arcres.Properties.arcInstanceResourceGroup } $extensionsCleanupSucceeded = $true @@ -1887,7 +2123,7 @@ param( else { $ArcExtensionCleanupFailedErrorMsg = $ArcExtensionCleanupFailedError -f $extension.Name - Write-ErrorLog -Message $ArcExtensionCleanupFailedErrorMsg -ErrorAction Continue + Write-ErrorLog -Message $ArcExtensionCleanupFailedErrorMsg -Exception $_ -ErrorAction Continue } } } @@ -1902,7 +2138,7 @@ param( # Clean up clustered scheduled task, if it exists. try { - # Connect to cluster and use that session for registering clustered scheduled task + # Connect to cluster and use that session for Unregistering clustered scheduled task if($Credential -eq $Null) { $clusterNameSession = New-PSSession -ComputerName ($clusterDNSName + "." + $ClusterDNSSuffix) @@ -1923,7 +2159,7 @@ param( catch { $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in unregistering cluster scheduled task. ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $positionMessage) -Category OperationStopped -ErrorAction Continue + Write-ErrorLog ("Exception occurred in unregistering cluster scheduled task") -Exception $_ -Category OperationStopped -ErrorAction Continue throw } finally @@ -1954,7 +2190,7 @@ param( if($Null -ne $arcAADApplication) { # when registration happens via older version of the registration script and unregistration happens via newever version - # service will not be able to delete the app since it does not own it. + # service will not be able to delete the app since it does not own it. try { Write-VerboseLog ("Deleting ARC AAD application: $($arcStatus.ApplicationId)") @@ -1963,7 +2199,7 @@ param( catch { #consume exception, this is best effort. Log warning and continue if it fails. - $msg = "Deleting ARC AAD application Failed $($arcStatus.ApplicationId) . ErrorMessage : {0} .Please delete it manually." -f ($_.Exception.Message) + $msg = "Deleting ARC AAD application Failed $($arcStatus.ApplicationId) . ErrorMessage : {0}. Please delete it manually." -f ($_.Exception.Message) Write-NodeEventLog -Message $msg -EventID 9011 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName Write-WarnLog ($msg) } @@ -1983,6 +2219,14 @@ param( Write-VerboseLog ("Successfully unregistered cluster from Arc for Servers") } + + if (-not([string]::IsNullOrEmpty($ArcResourceGroupName))) + { + # Removing Arc onboarding permissions from HCI RP App on Arc resource group + Remove-ArcRoleAssignments -ResourceGroupName $ArcResourceGroupName -ResourceId $ResourceId | Out-Null + + Remove-ResourceGroup -ResourceGroupName $ArcResourceGroupName + } } Write-Progress -Id $ArcProgressBarId -ParentId $MainProgressBarId -activity $UnregisterArcProgressActivityName -Completed @@ -2013,7 +2257,6 @@ enum OperationStatus { Unused; Failed; Success; - PendingForAdminConsent; Cancelled; RegisterSucceededButArcFailed } @@ -2087,7 +2330,7 @@ enum ErrorDetail { Registrations through Windows Admin Center specifies this parameter to true. .PARAMETER ArcServerResourceGroupName - Specifies the Arc Resource Group name. If not specified, service will generate a unique Resource Group name + Specifies the Arc Resource Group name. If not specified, cluster resource group name will be used. .PARAMETER ArcSpnCredential Specifies the credentials to be used for onboarding ARC agent. If not specified, new set of credentials will be generated. @@ -2121,10 +2364,10 @@ enum ErrorDetail { .EXAMPLE Invoking with all the parameters - C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciClusterRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential + C:\PS>Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciRG -ArcServerResourceGroupName HciRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential Result: Success - ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 - PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview + ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 + PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview #> function Register-AzStackHCI{ [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] @@ -2258,20 +2501,45 @@ param( $ResourceName = $RegContext.AzureResourceUri.Split('/')[8] Write-VerboseLog ("resolved resource Name $ResourceName from registration context") } + elseif ($ResourceName -ne $RegContext.AzureResourceUri.Split('/')[8]) + { + Write-ErrorLog -Message ($HCIResourceNameDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[8]) -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message ($HCIResourceNameDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[8]) -EventID 9127 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } if([string]::IsNullOrEmpty($ResourceGroupName)) { $ResourceGroupName = $RegContext.AzureResourceUri.Split('/')[4] Write-VerboseLog ("resolved resource group name $ResourceGroupName from registration context") } + elseif ($ResourceGroupName -ne $RegContext.AzureResourceUri.Split('/')[4]) + { + Write-ErrorLog -Message ($HCIResourceGroupNameDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[4]) -Category OperationStopped -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message ($HCIResourceGroupNameDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[4]) -EventID 9125 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } + + if($SubscriptionId -ne $RegContext.AzureResourceUri.Split('/')[2]) + { + Write-ErrorLog -Message ($HCISubscriptionDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[2]) -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message ($HCISubscriptionDifferentErrorMessage -f $RegContext.AzureResourceUri.Split('/')[2]) -EventID 9128 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } } else { - Write-ErrorLog -Message $NoExistingRegistrationExistsErrorMessage -ErrorAction Continue + Write-ErrorLog -Message $NoExistingRegistrationExistsErrorMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List - Write-NodeEventLog -Message $NoExistingRegistrationExistsErrorMessage -EventID 9101 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + Write-NodeEventLog -Message $NoExistingRegistrationExistsErrorMessage -EventID 9101 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw } } @@ -2307,11 +2575,11 @@ param( if($getCluster -eq $Null) { $NoClusterErrorMessage = $NoClusterError -f $ComputerName - Write-ErrorLog -Message $NoClusterErrorMessage -ErrorAction Continue + Write-ErrorLog -Message $NoClusterErrorMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $NoClusterErrorMessage -EventID 9102 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } else { @@ -2325,34 +2593,43 @@ param( $ResourceGroupName = $ResourceName + "-rg" Write-VerboseLog ("using cluster Name as resourcegroup name: $ResourceGroupName") } + $registrationBeginMsg="Register-AzStackHCI triggered - Region: $Region ResourceName: $ResourceName ` SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName ` AccountId: $AccountId EnvironmentName: $EnvironmentName CertificateThumbprint: $CertificateThumbprint ` RepairRegistration: $RepairRegistration EnableAzureArcServer: $EnableAzureArcServer IsWAC: $IsWAC" - Write-VerboseLog ($registrationBeginMsg) + $registrationBeginMsgPIIScrubbed="Register-AzStackHCI triggered - Region: $Region ResourceName: $ResourceName ` - SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName ` - EnvironmentName: $EnvironmentName CertificateThumbprint: $CertificateThumbprint ` - RepairRegistration: $RepairRegistration EnableAzureArcServer: $EnableAzureArcServer IsWAC: $IsWAC" + SubscriptionId: $SubscriptionId Tenant: $TenantId ResourceGroupName: $ResourceGroupName ` + EnvironmentName: $EnvironmentName CertificateThumbprint: $CertificateThumbprint ` + RepairRegistration: $RepairRegistration EnableAzureArcServer: $EnableAzureArcServer IsWAC: $IsWAC" + + if ([string]::IsNullOrEmpty($ArcServerResourceGroupName)) + { + $registrationBeginMsg = $registrationBeginMsg + " ArcServerResourceGroupName: $ArcServerResourceGroupName" + $registrationBeginMsgPIIScrubbed = $registrationBeginMsgPIIScrubbed + " ArcServerResourceGroupName: $ArcServerResourceGroupName" + } + + Write-VerboseLog ($registrationBeginMsg) Write-NodeEventLog -Message $registrationBeginMsgPIIScrubbed -EventID 9001 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName if(($EnableAzureArcServer -eq $false) -and ($EnvironmentName -ne $AzureChinaCloud)) { - Write-ErrorMessage -Message $ArcNeedsToBeEnabledError + Write-ErrorLog -Category OperationStopped -Message $ArcNeedsToBeEnabledError $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $ArcNeedsToBeEnabledError -EventID 9122 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } if(($EnvironmentName -eq $AzureChinaCloud) -and ($EnableAzureArcServer -eq $true)) { $ArcNotAvailableMessage = $ArcIntegrationNotAvailableForCloudError -f $EnvironmentName - Write-ErrorLog -Message $ArcNotAvailableMessage -ErrorAction Continue + Write-ErrorLog -Message $ArcNotAvailableMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $ArcNotAvailableMessage -EventID 9103 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } if(-Not ([string]::IsNullOrEmpty($Region))) @@ -2371,6 +2648,39 @@ param( if($resource -ne $null) { Write-VerboseLog ("Cluster resource is already created") + + # Fetching Arc settings resource + $arcResourceId = $resourceId + $HCIArcInstanceName + $arcres = Get-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -ErrorAction Ignore + # Setting the value for ArcServerResourceGroupName + if ([string]::IsNullOrEmpty($ArcServerResourceGroupName)) + { + if($null -ne $arcres) + { + $ArcServerResourceGroupName = $arcres.Properties.arcInstanceResourceGroup + Write-VerboseLog ("Arc for servers resource already exist. Resolved Arc for servers Resource group name from Arc for servers resource: $ArcServerResourceGroupName") + } + # Use Cluster RG as Arc RG if Arc RG is empty and Arc settings is not set + else + { + $ArcServerResourceGroupName = $resourceGroupName + Write-VerboseLog ("Using Cluster Resource group as Arc for Servers Resource group name: $ArcServerResourceGroupName") + } + } + else + { + if (($null -ne $arcres) -and ($arcres.Properties.arcInstanceResourceGroup -ne $ArcServerResourceGroupName)) + { + $ArcAlreadyRegisteredInDifferentResourceGroupErrorMessage = $ArcAlreadyRegisteredInDifferentResourceGroupError -f $arcres.Properties.arcInstanceResourceGroup + Write-ErrorLog -Message $ArcAlreadyRegisteredInDifferentResourceGroupErrorMessage -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyDetails -Value $ArcAlreadyRegisteredInDifferentResourceGroupErrorMessage + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message $ArcAlreadyRegisteredInDifferentResourceGroupErrorMessage -EventID 9129 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } + } + $resourceLocation = Normalize-RegionName -Region $resource.Location Write-VerboseLog ("Location resolved from cloud resource: $resourceLocation") if([string]::IsNullOrEmpty($Region)) @@ -2380,11 +2690,11 @@ param( elseif($Region -ne $resourceLocation) { $ResourceExistsInDifferentRegionErrorMessage = $ResourceExistsInDifferentRegionError -f $resourceLocation, $Region - Write-ErrorLog -Message $ResourceExistsInDifferentRegionErrorMessage -ErrorAction Continue + Write-ErrorLog -Message $ResourceExistsInDifferentRegionErrorMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $ResourceExistsInDifferentRegionErrorMessage -EventID 9104 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } } else @@ -2425,19 +2735,90 @@ param( } } } - if(-not [string]::IsNullOrEmpty($ArcServerResourceGroupName)) + } + + #USE CLUSTER RG AS ARC RG + if ([string]::IsNullOrEmpty($ArcServerResourceGroupName)) + { + $ArcServerResourceGroupName = $resourceGroupName + Write-VerboseLog ("using cluster rg as arcserver resourcegroup name: $ResourceGroupName") + } + + $arcResGroup = Get-AzResourceGroup -Name $ArcServerResourceGroupName -ErrorAction Ignore + + if ($Null -ne $arcResGroup) + { + Write-VerboseLog("Checking if an arc machine with same name as current nodes already exists in the Arc For Server Resource Group") + forEach ($clusNode in $clusterNodes) { - $arcResGroup = Get-AzResourceGroup -Name $ArcServerResourceGroupName -ErrorAction Ignore - if($arcResGroup -ne $Null) + $machineResourceId = "/Subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ArcServerResourceGroupName + "/providers/Microsoft.HybridCompute/machines/" + $clusNode + $machineResourceIdWithAPI = "{0}?api-version={1}" -f $machineResourceId, $HCApiVersion + $arcMachineResource = Get-AzResource -ResourceId $machineResourceIdWithAPI -ErrorAction Ignore + $sameNodeNames = [System.Collections.ArrayList]::new() + + if ($Null -ne $arcMachineResource) { - $ArcResourceGroupExistsErrorMessage = $ArcResourceGroupExists -f $ArcServerResourceGroupName - Write-Error -Message $ArcResourceGroupExistsErrorMessage - $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed - Write-Output $registrationOutput | Format-List - Write-NodeEventLog -Message $ArcResourceGroupExistsErrorMessage -EventID 9105 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + $parentClusterResourceId = $arcMachineResource.properties.parentClusterResourceId + #If ParentClusterResourceId is empty, Arc enablement happened before Registration. + if ([string]::IsNullOrEmpty($parentClusterResourceId)) + { + Write-VerboseLog("ParentClusterResourceId for Node $clusNode is empty, continue.") + continue + } + #Case: ParentClusterResourceId is Set. + else + { + $errorMessage = "Cluster is {} and ParentClusterResourceId for $clusNode does not match the Cluster Resource." + # If Cluster is registered from before, then ParentClusterResourceId should match the clusterResourceId + if ($RegContext.RegistrationStatus -eq [RegistrationStatus]::Registered) + { + if ($parentClusterResourceId -eq $resourceId) + { + Write-VerboseLog ("Cluster is registered and ParentClusterResourceId for $clusNode matches the Cluster Resource, continue.") + continue + } + else + { + $errorMessageLog = $errorMessage -f "registered" + Write-ErrorLog($errorMessageLog) + $sameNodeNames.Add($clusNode) | Out-Null + } + } + #If Cluster is not yet registered, then ParentClusterResourceId should be Null + else + { + $errorMessageLog = $errorMessage -f "not registered" + Write-ErrorLog ($errorMessageLog) + $sameNodeNames.Add($clusNode) | Out-Null + } + } } } + + if ($sameNodeNames.Count -gt 0) + { + Write-VerboseLog("A node exists with the same name in the arc for servers resource group. Choose a different resource group for Arc for server resources.") + $sameNodeNamesAsList = $sameNodeNames -join "," + $ArcMachineAlreadyExistsInResourceGroupErrorMessage = $ArcMachineAlreadyExistsInResourceGroupError -f $sameNodeNamesAsList, $ArcServerResourceGroupName + Write-ErrorLog -Message $ArcMachineAlreadyExistsInResourceGroupErrorMessage -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyDetails -Value $ArcMachineAlreadyExistsInResourceGroupErrorMessage + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message $ArcMachineAlreadyExistsInResourceGroupErrorMessage -EventID 9105 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } + } + + try + { + Verify-NodesArcRegistrationState -ClusterNodes $clusterNodes -SubscriptionId $SubscriptionId -ArcResourceGroupName $ArcServerResourceGroupName -Credential $Credential -ClusterDNSSuffix $clusterDNSSuffix + } + catch + { + Write-ErrorLog $_.Exception.Message -Category OperationStopped + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + throw } # Normalize region name @@ -2462,22 +2843,22 @@ param( Write-VerboseLog ("Cluster is already registered but the cloud resource does not exist.") # Already registered with same resource Id and resource does not exists $AlreadyRegisteredErrorMessage = $CloudResourceDoesNotExist -f $resourceId - Write-ErrorLog -Message $AlreadyRegisteredErrorMessage -ErrorAction Continue + Write-ErrorLog -Message $AlreadyRegisteredErrorMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $AlreadyRegisteredErrorMessage -EventID 9106 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } } else # Already registered with different resource Id { Write-VerboseLog ("Cluster is already registered and cloud resource does not match.") $AlreadyRegisteredErrorMessage = $RegisteredWithDifferentResourceId -f $RegContext.AzureResourceUri - Write-ErrorLog -Message $AlreadyRegisteredErrorMessage -ErrorAction Continue + Write-ErrorLog -Message $AlreadyRegisteredErrorMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $AlreadyRegisteredErrorMessage -EventID 9107 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } } else @@ -2492,11 +2873,11 @@ param( if ($regionSupported -eq $False) { $RegionNotSupportedMessage = $RegionNotSupported -f $Region, $supportedRegions - Write-ErrorLog -Message $RegionNotSupportedMessage -ErrorAction Continue + Write-ErrorLog -Message $RegionNotSupportedMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $RegionNotSupportedMessage -EventID 9108 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } # Check if OS version is 22H2 or newer @@ -2509,44 +2890,47 @@ param( $isCloudManagementSupported = ([Int]::Parse($osVersionInfo.BuildNumber) -ge $22H2BuildNumber) -and ($cloudManagementCapable -eq $true) Write-VerboseLog ("Cloud Management supported: {0}" -f $isCloudManagementSupported) - + if($Null -eq $resource) { # Create new HCI resource by calling RP if($Null -eq $resGroup) { - $CreatingResourceGroupMessageProgress = $CreatingResourceGroupMessage -f $ResourceGroupName - Write-VerboseLog ("$CreatingResourceGroupMessageProgress") - Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $CreatingResourceGroupMessageProgress -percentcomplete 55 - $resGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Region -Tag @{$ResourceGroupCreatedByName = $ResourceGroupCreatedByValue} + $CreatingResourceGroupMessageProgress = $CreatingResourceGroupMessage -f $ResourceGroupName + Write-VerboseLog ("$CreatingResourceGroupMessageProgress") + Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $CreatingResourceGroupMessageProgress -percentcomplete 55 + $resGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Region -Tag @{$ResourceGroupCreatedByName = $ResourceGroupCreatedByValue } } $CreatingCloudResourceMessageProgress = $CreatingCloudResourceMessage -f $ResourceName Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $CreatingCloudResourceMessageProgress -percentcomplete 60 - if( ($EnvironmentName -eq $AzureCloud) -or ($EnvironmentName -eq $AzureCanary) -or ($EnvironmentName -eq $AzurePPE) ) - { + if( ($EnvironmentName -eq $AzureCloud) -or ($EnvironmentName -eq $AzureCanary) -or ($EnvironmentName -eq $AzurePPE) -or ($EnvironmentName -eq $AzureUSGovernment) ) + { $properties = [ResourceProperties]::new($Region, @{}, $Tag) - $payload = ConvertTo-Json -InputObject $properties - $resourceIdWithAPI = "{0}?api-version={1}" -f $resourceId, $RPAPIVersion - Write-VerboseLog ("$CreatingCloudResourceMessageProgress with properties : {0}" -f ($payload | Out-String)) - Write-VerboseLog ("ResorceIdWithApi: $resourceIdWithAPI") - $response = Invoke-AzRestMethod -Path $resourceIdWithAPI -Method PUT -Payload $payload - if(-not(($response.StatusCode -ge 200) -and ($response.StatusCode -lt 300))) - { - Write-ErrorLog -Message ("Failed to create ARM resource representing the cluster. Code: {0}, Details: {1}" -f $response.StatusCode, $response.Content) - throw - } } else { - $properties = @{} - Write-VerboseLog ("$CreatingCloudResourceMessageProgress with properties : {0}" -f ($properties | Out-String)) - $resource = New-AzResource -ResourceId $resourceId -Location $Region -ApiVersion $RPAPIVersion -PropertyObject $properties -Tag $Tag -Force + $properties = @{"tags" = $Tag; "location" = $Region} + } + $payload = ConvertTo-Json -InputObject $properties + $resourceIdWithAPI = "{0}?api-version={1}" -f $resourceId, $RPAPIVersion + Write-VerboseLog ("$CreatingCloudResourceMessageProgress with properties : {0}" -f ($payload | Out-String)) + Write-VerboseLog ("ResorceIdWithApi: $resourceIdWithAPI") + $clusterResult = New-ClusterWithRetries -ResourceIdWithAPI $resourceIdWithAPI -Payload $payload + if($clusterResult -eq $false) + { + Write-ErrorLog -Message $ClusterCreationFailureMessage -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message $ClusterCreationFailureMessage -EventID 9130 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw } + $resource = Get-AzResource -ResourceId $resourceId -ApiVersion $RPAPIVersion -ErrorAction Ignore } - if((($Null -eq $resource.Identity) -or ($resource.Identity.Type -ne "SystemAssigned")) -and ( ($EnvironmentName -eq $AzureCloud) -or ($EnvironmentName -eq $AzureCanary) -or ($EnvironmentName -eq $AzurePPE))) + + if((($Null -eq $resource.Identity) -or ($resource.Identity.Type -ne "SystemAssigned")) -and ( ($EnvironmentName -eq $AzureCloud) -or ($EnvironmentName -eq $AzureCanary) -or ($EnvironmentName -eq $AzurePPE) -or ($EnvironmentName -eq $AzureUSGovernment))) { #we are here, if we are in repairregistration flow and resource might have been already created, we will check if MSI is not enabled, if it is not enabled, we will patch the resource again. $RepairingCloudResourceMessageProgress = $RepairingCloudResourceMessage -f $ResourceName @@ -2565,7 +2949,7 @@ param( $response = Invoke-AzRestMethod -Path $resourceIdWithAPI -Method PATCH -Payload $payload if(-not(($response.StatusCode -ge 200) -and ($response.StatusCode -lt 300))) { - Write-ErrorLog -Message ("Failed to repair ARM resource representing the cluster. Code: {0}, Details: {1}" -f $response.StatusCode, $response.Content) + Write-ErrorLog -Message ("Failed to repair ARM resource representing the cluster. Code: {0}, Details: {1}" -f $response.StatusCode, $response.Content) -Category OperationStopped throw } $resource = Get-AzResource -ResourceId $resourceId -ApiVersion $RPAPIVersion -ErrorAction Ignore @@ -2609,100 +2993,83 @@ param( if($null -ne $setupCertsError) { Write-VerboseLog ("Setup-Certificates has failed") - Write-ErrorLog -Message $setupCertsError + Write-ErrorLog -Message $setupCertsError -Category OperationStopped $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $setupCertsError -EventID 9109 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } if(($SetCertificateFailedNodes.Count -ge 1) -or ($NewCertificateFailedNodes.Count -ge 1)) { Write-VerboseLog ("Setup-Certificates failed on atleast one node") $SettingCertificateFailedMessage = $SettingCertificateFailed -f ($NewCertificateFailedNodes -join ","),($SetCertificateFailedNodes -join ",") - Write-ErrorLog -Message $SettingCertificateFailedMessage -ErrorAction Continue + Write-ErrorLog -Message $SettingCertificateFailedMessage -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $SettingCertificateFailedMessage -EventID 9110 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } if($OSNotLatestOnNodes.Count -ge 1) { $NotAllTheNodesInClusterAreGAError = $NotAllTheNodesInClusterAreGA -f ($OSNotLatestOnNodes -join ",") - Write-ErrorLog -Message $NotAllTheNodesInClusterAreGAError -ErrorAction Continue + Write-ErrorLog -Message $NotAllTheNodesInClusterAreGAError -Category OperationStopped -ErrorAction Continue $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $registrationOutput | Format-List Write-NodeEventLog -Message $NotAllTheNodesInClusterAreGAError -EventID 9111 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $RegisterAndSyncMetadataMessage -percentcomplete 90 # Register by calling on-prem usage service Cmdlet - - $RegistrationParams = @{ - ServiceEndpoint = $ServiceEndpoint - BillingServiceApiScope = $BillingServiceApiScope - GraphServiceApiScope = $GraphServiceApiScope - AADAuthority = $Authority - AppId = $appId - TenantId = $TenantId - CloudId = $cloudId - SubscriptionId = $SubscriptionId - ObjectId = $objectId - ResourceName = $ResourceName - ProviderNamespace = "Microsoft.AzureStackHCI" - ResourceArmId = $resourceId - ServicePrincipalClientId = $spObjectId - CertificateThumbprint = $CertificateThumbprint - } - - Invoke-Command -Session $clusterNodeSession -ScriptBlock { Set-AzureStackHCIRegistration @Using:RegistrationParams } - - # Add custom IMDS registry entry - Write-VerboseLog ("Configuring CustomIMDSHostAddress on each node.") - foreach ($clusNode in $clusterNodes) - { - $nodeSession = $null - try - { - if($null -eq $Credential) - { - $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) - } - else - { - $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) -Credential $Credential - } - } - catch - { - Write-VerboseLog ("Exception occurred in establishing new PSSession to $($clusNode.Name). ErrorMessage : " + $_.Exception.Message) - Write-VerboseLog ($_) - Write-WarnLog ("Could not configure CustomIMDSHostAddress for node: $($clusNode.Name)") - continue - } - Write-VerboseLog ("Configuring CustomIMDSHostAddress on $($clusNode.Name)") - $customImdsScript = { try{ $customImdsRegKey = Get-Item 'HKLM:\Software\Microsoft\Windows Azure\CurrentVersion\IMDS' -ErrorAction Stop } catch{ $customImdsRegKey = New-Item 'HKLM:\Software\Microsoft\Windows Azure\CurrentVersion\IMDS' -Force -ErrorAction Stop } $customImdsRegKey | New-ItemProperty -Name "CustomIMDSHostAddress" -Value "http://127.0.0.1:42542" -Force -ErrorAction Stop | Out-Null } - try - { - Invoke-Command -Session $nodeSession -ScriptBlock $customImdsScript - } - catch - { - Write-VerboseLog ("Exception occurred while setting custom IMDS host on $($clusNode.Name). ErrorMessage : " + $_.Exception.Message) - Write-VerboseLog ($_) - Write-WarnLog ("Could not configure CustomIMDSHostAddress for node: $($clusNode.Name)") + try + { + $Params = @{ + ServiceEndpoint = $ServiceEndpoint + BillingServiceApiScope = $BillingServiceApiScope + GraphServiceApiScope = $GraphServiceApiScope + AADAuthority = $Authority + AppId = $appId + TenantId = $TenantId + CloudId = $cloudId + SubscriptionId = $SubscriptionId + ObjectId = $objectId + ResourceName = $ResourceName + ProviderNamespace = "Microsoft.AzureStackHCI" + ResourceArmId = $resourceId + ServicePrincipalClientId = $spObjectId + CertificateThumbprint = $CertificateThumbprint } - - if($null -ne $nodeSession) - { - Remove-PSSession $nodeSession -ErrorAction Ignore | Out-Null + + $SetAzureStackHCIRegistrationScript = { + Set-AzureStackHCIRegistration -ErrorAction Stop @Params } + + Run-InvokeCommand -ScriptBlock $SetAzureStackHCIRegistrationScript -Session $clusterNodeSession -Params $Params + Write-VerboseLog ("Successfully performed: {0}" -f ($Params | Out-String)) + } + catch + { + $errorMessage = $_.Exception.Message.ToString() + Write-VerboseLog ($SetAzureStackHCIRegistrationErrorMessage -f $errorMessage) + Write-ErrorLog ($SetAzureStackHCIRegistrationErrorMessage -f $errorMessage) -Category OperationStopped -Exception $_ -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + throw } + $isCloudManagementFeatureEnabled = $false if ($isCloudManagementSupported -eq $true) + { + $cloudManagementFeatureDetectoid = { $null -ne (Get-AzureStackHCI).NextSync } + $isCloudManagementFeatureEnabled = Invoke-Command -Session $clusterNodeSession -ScriptBlock $cloudManagementFeatureDetectoid -ErrorAction Ignore + Write-VerboseLog ("Cloud Management supported: {0}" -f $isCloudManagementSupported) + Write-VerboseLog ("Cloud Management enabled: {0}" -f $isCloudManagementFeatureEnabled) + } + + if ($isCloudManagementSupported -eq $true -AND $isCloudManagementFeatureEnabled -eq $true) { Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $ConfiguringCloudManagementMessage -percentcomplete 91 Write-Progress -Id $SecondaryProgressBarId -activity $SetupCloudManagementActivityName -status $ConfiguringCloudManagementMessage -percentcomplete 10 @@ -2768,7 +3135,7 @@ param( { Write-VerboseLog ("Cloud Management cluster resource: $($svcResource | Format-List |Out-String)") Write-VerboseLog ("Starting Cluster Group $ClusterAgentGroupName") - $group = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Start-ClusterGroup -Name $using:ClusterAgentGroupName -ErrorAction Ignore } + $group = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Start-ClusterGroup -Name $using:ClusterAgentGroupName -Wait 120 -ErrorAction Ignore } if ($group.State -ne "Online") { $serviceError = "Failed to start {0} clustered role." -f $ClusterAgentGroupName @@ -2801,105 +3168,124 @@ param( if ( $EnableAzureArcServer -eq $true ) { - Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $RegisterArcMessage -percentcomplete 93 - Write-VerboseLog ("$RegisterArcMessage") - $ArcCmdletsAbsentOnNodes = [System.Collections.ArrayList]::new() - - Foreach ($clusNode in $clusterNodes) + $arcResGroup = Get-AzResourceGroup -Name $ArcServerResourceGroupName -ErrorAction Ignore + if ($arcResGroup -eq $Null) { - $nodeSession = $null - - try - { - if($Credential -eq $Null) - { - $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) - } - else - { - $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) -Credential $Credential - } - } - catch - { - Write-VerboseLog ("Exception occurred in establishing new PSSession to $($clusNode.Name). ErrorMessage : " + $_.Exception.Message) - Write-VerboseLog ($_) - $ArcCmdletsAbsentOnNodes.Add($clusNode.Name) | Out-Null - continue - } - - # Check if node has Arc registration Cmdlets - $cmdlet = Invoke-Command -Session $nodeSession -ScriptBlock { Get-Command Get-AzureStackHCIArcIntegration -Type Cmdlet -ErrorAction Ignore } - - if($cmdlet -eq $null) - { - Write-VerboseLog ("Arc cmdlet not present on node : {0}" -f $clusNode.Name) - $ArcCmdletsAbsentOnNodes.Add($clusNode.Name) | Out-Null - } - - if($nodeSession -ne $null) - { - Remove-PSSession $nodeSession -ErrorAction Ignore | Out-Null - } + $CreatingResourceGroupMessageProgress = $CreatingResourceGroupMessage -f $ArcServerResourceGroupName + Write-VerboseLog ("$CreatingResourceGroupMessageProgress") + Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $CreatingResourceGroupMessageProgress -percentcomplete 90 + $arcResGroup = New-AzResourceGroup -Name $ArcServerResourceGroupName -Location $Region -Tag @{$ResourceGroupCreatedByName = $ResourceGroupCreatedByValue } } - if($ArcCmdletsAbsentOnNodes.Count -ge 1) + if ($Null -eq $resource.Properties.ResourceProviderObjectId) { - # Show Arc error on 20h2 only if -EnableAzureArcServer:$true is explicity passed by user - if($PSBoundParameters.ContainsKey('EnableAzureArcServer')) - { - $ArcCmdletsNotAvailableErrorMsg = $ArcCmdletsNotAvailableError -f ($ArcCmdletsAbsentOnNodes -join ",") - Write-ErrorLog -Message $ArcCmdletsNotAvailableErrorMsg -ErrorAction Continue - $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed - Write-Output $registrationOutput | Format-List - Write-NodeEventLog -Message $ArcCmdletsNotAvailableErrorMsg -EventID 9112 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return - } + Write-VerboseLog("Resource Provider Object Id is Null. Can't assign roles to HCI RP for ARC Onboarding") + $operationStatus = [OperationStatus]::RegisterSucceededButArcFailed + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value [ErrorDetail]::ArcPermissionsMissing } - else + + else { - $arcResourceId = $resourceId + $HCIArcInstanceName - $arcResourceGroupName = $ResourceGroupName + $RPObjectId = $resource.Properties.ResourceProviderObjectId + $setRolesResult = Set-ArcRoleforRPSpn -RPObjectId $RPObjectId -ArcServerResourceGroupName $ArcServerResourceGroupName - Write-VerboseLog ("checking if Arc resource $arcResourceId already exists") - $arcres = Get-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -ErrorAction Ignore + if($setRolesResult -ne [ErrorDetail]::Success) + { + $operationStatus = [OperationStatus]::RegisterSucceededButArcFailed + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value $setRolesResult + } - if($arcres -eq $null) + else { - Write-VerboseLog ("Arc Resource does not exist, create new resource") - if(-not [string]::IsNullOrEmpty($ArcServerResourceGroupName)) + Write-Progress -Id $MainProgressBarId -activity $RegisterProgressActivityName -status $RegisterArcMessage -percentcomplete 93 + Write-VerboseLog ("$RegisterArcMessage") + $ArcCmdletsAbsentOnNodes = [System.Collections.ArrayList]::new() + + Foreach ($clusNode in $clusterNodes) { + $nodeSession = $null + + try + { + if($Credential -eq $Null) + { + $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) + } + else + { + $nodeSession = New-PSSession -ComputerName ($clusNode.Name + "." + $clusterDNSSuffix) -Credential $Credential + } + } + catch + { + Write-VerboseLog ("Exception occurred in establishing new PSSession to $($clusNode.Name). ErrorMessage : " + $_.Exception.Message) + Write-VerboseLog ($_) + $ArcCmdletsAbsentOnNodes.Add($clusNode.Name) | Out-Null + continue + } - Write-VerboseLog ("Specifying Arc ResourceGroup $ArcServerResourceGroupName during Arc resource creation") - $arcInstanceResourceGroup = @{"arcInstanceResourceGroup" = $ArcServerResourceGroupName} - $arcres = New-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -PropertyObject $arcInstanceResourceGroup -Force + # Check if node has Arc registration Cmdlets + $cmdlet = Invoke-Command -Session $nodeSession -ScriptBlock { Get-Command Get-AzureStackHCIArcIntegration -Type Cmdlet -ErrorAction Ignore } + + if($cmdlet -eq $null) + { + Write-VerboseLog ("Arc cmdlet not present on node : {0}" -f $clusNode.Name) + $ArcCmdletsAbsentOnNodes.Add($clusNode.Name) | Out-Null + } + + if($nodeSession -ne $null) + { + Remove-PSSession $nodeSession -ErrorAction Ignore | Out-Null + } } - else + + if($ArcCmdletsAbsentOnNodes.Count -ge 1) { - $arcres = New-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -Force + # Show Arc error on 20h2 only if -EnableAzureArcServer:$true is explicity passed by user + if($PSBoundParameters.ContainsKey('EnableAzureArcServer')) + { + $ArcCmdletsNotAvailableErrorMsg = $ArcCmdletsNotAvailableError -f ($ArcCmdletsAbsentOnNodes -join ",") + Write-ErrorLog -Message $ArcCmdletsNotAvailableErrorMsg -Category OperationStopped -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message $ArcCmdletsNotAvailableErrorMsg -EventID 9112 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } } - } - else - { - Write-VerboseLog ("Arc Resource already exists") - if ($arcres.Properties.aggregateState -eq $ArcSettingsDisableInProgressState) + else { - Write-ErrorLog -Message $ArcRegistrationDisableInProgressError -ErrorAction Continue - $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed - Write-Output $registrationOutput | Format-List - Write-NodeEventLog -Message $ArcRegistrationDisableInProgressError -EventID 9113 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return - } - } + $arcResourceId = $resourceId + $HCIArcInstanceName - $arcResourceGroupName = $arcres.Properties.arcInstanceResourceGroup - Write-VerboseLog ("Register-AzStackHCI: Arc registration triggered. ArcResourceGroupName: $arcResourceGroupName") - $arcResult = Register-ArcForServers -IsManagementNode $IsManagementNode -ComputerName $ComputerName -Credential $Credential -TenantId $TenantId -SubscriptionId $SubscriptionId -ResourceGroup $arcResourceGroupName -Region $Region -ArcSpnCredential $ArcSpnCredential -ClusterDNSSuffix $clusterDNSSuffix -IsWAC:$IsWAC -Environment:$EnvironmentName -ArcResource $arcres + Write-VerboseLog ("checking if Arc resource $arcResourceId already exists") + + if($null -eq $arcres) + { + Write-VerboseLog ("Arc Resource does not exist, create new resource") + $arcInstanceResourceGroup = @{"arcInstanceResourceGroup" = $ArcServerResourceGroupName} + $arcres = New-AzResource -ResourceId $arcResourceId -ApiVersion $HCIArcAPIVersion -PropertyObject $arcInstanceResourceGroup -Force + } + else + { + Write-VerboseLog ("Arc Resource already exists") + if ($arcres.Properties.aggregateState -eq $ArcSettingsDisableInProgressState) + { + Write-ErrorLog -Message $ArcRegistrationDisableInProgressError -Category OperationStopped -ErrorAction Continue + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed + Write-Output $registrationOutput | Format-List + Write-NodeEventLog -Message $ArcRegistrationDisableInProgressError -EventID 9113 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + throw + } + } - if($arcResult -ne [ErrorDetail]::Success) - { - $operationStatus = [OperationStatus]::RegisterSucceededButArcFailed - $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value $arcResult + Write-VerboseLog ("Register-AzStackHCI: Arc registration triggered. ArcResourceGroupName: $ArcServerResourceGroupName") + $arcResult = Register-ArcForServers -IsManagementNode $IsManagementNode -ComputerName $ComputerName -Credential $Credential -TenantId $TenantId -SubscriptionId $SubscriptionId -ResourceGroup $ArcServerResourceGroupName -Region $Region -ArcSpnCredential $ArcSpnCredential -ClusterDNSSuffix $clusterDNSSuffix -IsWAC:$IsWAC -Environment:$EnvironmentName -ArcResource $arcres + + if($arcResult -ne [ErrorDetail]::Success) + { + $operationStatus = [OperationStatus]::RegisterSucceededButArcFailed + $registrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value $arcResult + } + } } } } @@ -2919,13 +3305,14 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - # 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-NodeEventLog -Message ("Exception occurred in Register-AzStackHCI : " + $positionMessage) -EventID 9114 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - Write-ErrorLog ("Exception occurred in Register-AzStackHCI : " + $positionMessage) -Category OperationStopped - throw + + if(-Not $Error.Contains($AlreadyLoggedFlag)) + { + Write-ErrorLog ("Exception occurred in Register-AzStackHCI") -Exception $_ -Category OperationStopped + } } finally { @@ -2937,6 +3324,87 @@ param( } } +function Set-ArcRoleforRPSpn { + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [String] $RPObjectId, + [String] $ArcServerResourceGroupName + ) + $stopLoop = $false + [int]$retryCount = "0" + [int]$maxRetryCount = "5" + Write-VerboseLog ("Assigning HCI RP App Arc Onboarding permissions") + do + { + try + { + New-AzRoleAssignment -ObjectId $RPObjectId -ResourceGroupName $ArcServerResourceGroupName -RoleDefinitionName $ArcOnboardingRole | Out-Null + Write-VerboseLog("Sucessfully assigned ARC Roles to HCI RP service principal with Object Id $($RPObjectId)") + $stopLoop = $true + } + catch + { + # 'Conflict' can happen when either the RoleAssignment already exists or the limit for number of role assignments has been reached. + if ($_.Exception.Response.StatusCode -eq 'Conflict') + { + $roleAssignment = Get-AzRoleAssignment -ObjectId $RPObjectId -ResourceGroupName $ArcServerResourceGroupName -RoleDefinitionName $ArcOnboardingRole + if ($null -ne $roleAssignment) + { + Write-VerboseLog("Sucessfully assigned ARC Roles to HCI RP service principal with Object Id $($RPObjectId)") + return [ErrorDetail]::Success + } + Write-ErrorLog ("Failed to assign roles to service principal with object Id $($RPObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage) + return [ErrorDetail]::ArcPermissionsMissing + } + if ($retryCount -ge $maxRetryCount) + { + # Timed out. + Write-ErrorLog ("Failed to assign roles to service principal with object Id $($RPObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage) + return [ErrorDetail]::ArcPermissionsMissing + } + Write-VerboseLog ("Could not assign roles to service principal with Object Id $($RPObjectId). Retrying in 10 seconds...") + Start-Sleep -Seconds 10 + $retryCount = $retryCount + 1 + } + } + While(-Not $stopLoop) + return [ErrorDetail]::Success +} + +function New-ClusterWithRetries { + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [String] $ResourceIdWithAPI, + [String] $Payload + ) + $stopLoop = $false + [int]$retryCount = "0" + [int]$maxRetryCount = "10" + do + { + + $response = Invoke-AzRestMethod -Path $ResourceIdWithAPI -Method PUT -Payload $Payload + + if (($response.StatusCode -ge 200) -and ($response.StatusCode -lt 300)) + { + $stopLoop = $true + return $true + } + if ($retryCount -ge $maxRetryCount) + { + # Timed out. + Write-WarnLog ("Failed to create ARM resource representing the cluster. StatusCode: {0}, ErrorCode: {}, Details: {1}" -f $response.StatusCode, $response.ErrorCode, $response.Content) + return $false + } + Write-VerboseLog ("Failed to create ARM resource representing the cluster. Retrying in 10 seconds...") + Start-Sleep -Seconds 10 + $retryCount = $retryCount + 1 + + } + While (-Not $stopLoop) + return $true +} + <# .Description Unregister-AzStackHCI deletes the Microsoft.AzureStackHCI cloud resource representing the on-premises cluster and unregisters the on-premises cluster with Azure. @@ -3124,11 +3592,11 @@ param( { if($RegContext.RegistrationStatus -ne [RegistrationStatus]::Registered) { - Write-ErrorLog -Message $RegistrationInfoNotFound -ErrorAction Continue + Write-ErrorLog -Message $RegistrationInfoNotFound -Category OperationStopped -ErrorAction Continue $unregistrationOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value [OperationStatus]::Failed Write-Output $unregistrationOutput | Format-List Write-NodeEventLog -Message $RegistrationInfoNotFound -EventID 9115 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - return + throw } } @@ -3194,7 +3662,6 @@ param( if($RegContext.RegistrationStatus -eq [RegistrationStatus]::Registered) { - Invoke-Command -Session $clusterNodeSession -ScriptBlock { Remove-AzureStackHCIRegistration } Write-VerboseLog ("Successfully completed Remove-AzureStackHCIRegistration on cluster") $clusterNodes = Invoke-Command -Session $clusterNodeSession -ScriptBlock { Get-ClusterNode } @@ -3261,43 +3728,8 @@ param( } } - $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 - { - Write-VerboseLog ("Resource group is empty and created by Az.StackHCI. Deleting it") - try - { - Remove-AzResourceGroup -Name $ResourceGroupName -Force | Out-Null - } - catch - { - Write-VerboseLog ("Deleting Resource Group $ResourceGroupName failed. ErrorMessage : {0}", ($_.Exception.Message)) - } - } - else - { - Write-VerboseLog ("Resource group is not empty, not deleting ") - } - } - else - { - Write-VerboseLog ("Resource group not created by Az.StackHCI. Not deleting") - } - } - } + Write-VerboseLog("Unregister ResourceGroupName $ResourceGroupName") + Remove-ResourceGroup -ResourceGroupName $ResourceGroupName $operationStatus = [OperationStatus]::Success } @@ -3320,12 +3752,15 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - # Get script line number, offset and Command that resulted in exception. Write-ErrorLog with the exception above does not write this info. - $positionMessage = $_.InvocationInfo.PositionMessage - Write-NodeEventLog -Message ("Exception occurred in Unregister-AzStackHCI : " + $positionMessage) -EventID 9118 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning - Write-ErrorLog ("Exception occurred in Unregister-AzStackHCI : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw + # Get script line number, offset and Command that resulted in exception. Write-ErrorLog with the exception above does not write this info. + $positionMessage = $_.InvocationInfo.PositionMessage + Write-NodeEventLog -Message ("Exception occurred in Unregister-AzStackHCI : " + $positionMessage) -EventID 9118 -IsManagementNode $IsManagementNode -credentials $Credential -ComputerName $ComputerName -Level Warning + + if(-Not $Error.Contains($AlreadyLoggedFlag)) + { + Write-ErrorLog ("Exception occurred in Unregister-AzStackHCI") -Exception $_ -Category OperationStopped -ErrorAction Continue + } + } finally { @@ -3337,6 +3772,101 @@ param( } } +function Remove-ArcRoleAssignments { + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [String] $ResourceGroupName, + [String] $ResourceId + ) + try + { + Write-VerboseLog ("Removing Arc onboarding role from HCI RP App on resource group: $ResourceGroupName.") + $arcResourcesInArcRG = Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceType $ArcMachineResourceType + $clusterCloudResource = Get-AzResource -ResourceId $ResourceId -ApiVersion $RPAPIVersion -ErrorAction Ignore + + foreach ($arcResource in $arcResourcesInArcRG) + { + $arcResourceDetails = Get-AzResource -ResourceId $arcResource.ResourceId -ApiVersion $HCApiVersion + if(-Not [string]::IsNullOrEmpty($arcResourceDetails.Properties.parentClusterResourceId)) + { + Write-VerboseLog ("Arc for server resource with parentClusterResourceId set exists in the resource group: $ResourceGroupName. Won't remove Arc onboarding role from HCI RP App.") + return + } + } + + if(($null -ne $clusterCloudResource) -and ($null -ne $clusterCloudResource.Properties.resourceProviderObjectId)) + { + Remove-AzRoleAssignment -ObjectId $clusterCloudResource.Properties.resourceProviderObjectId -ResourceGroupName $ResourceGroupName -RoleDefinitionName $ArcOnboardingRole -ErrorAction Stop + Write-VerboseLog ("Successfully removed role: {0} from HCI RP App on resource group: {1}" -f $ArcOnboardingRole, $ResourceGroupName) + } + else + { + if($null -eq $clusterCloudResource) + { + Write-VerboseLog ("Unable to remove Arc onboarding role from HCI RP App as HCI cluster cloud resource doesn't exist.") + } + else + { + Write-VerboseLog ("Unable to remove Arc onboarding role from HCI RP App as HCI cluster cloud resource doesn't contain resourceProviderObjectId.") + } + } + } + catch + { + Write-VerboseLog ("Exception occurred while removing Arc onboarding role from HCI RP App on Arc resource group: {0}" -f $_.Exception.Message) + } +} + +function Remove-ResourceGroup { + [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.DoNotExportAttribute()] + param( + [String] $ResourceGroupName + ) + Write-VerboseLog ("Trying to delete resource group: $ResourceGroupName") + $resGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction Ignore + if ($Null -eq $resGroup) + { + Write-VerboseLog ("Resource Group Not Found") + return + } + + $resGroupTags = $resGroup.Tags + if ($Null -eq $resGroupTags) + { + Write-VerboseLog ("No tags found on the Resource Group, Not Deleting.") + return + } + $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 ($null -eq $resourcesInRG) + { + # Resource group is empty + Write-VerboseLog ("Resource group $ResourceGroupName is empty and created by Az.StackHCI. Deleting it") + try + { + Remove-AzResourceGroup -Name $ResourceGroupName -Force | Out-Null + } + catch + { + Write-VerboseLog ("Deleting Resource Group $ResourceGroupName failed. ErrorMessage : {0}", ($_.Exception.Message)) + } + } + else + { + Write-VerboseLog ("Resource group is not empty, not deleting ") + } + } + else + { + Write-VerboseLog ("Resource group not created by Az.StackHCI. Not deleting") + } +} + <# .Description Test-AzStackHCIConnection verifies connectivity from on-premises clustered nodes to the Azure services required by Azure Stack HCI. @@ -3380,7 +3910,7 @@ param( FailedNodes: Node1inClus2, Node2inClus3 #> function Test-AzStackHCIConnection{ - [Obsolete("Test-AzStackhHCIConnection is deprecated. Please use 'Invoke-AzStackHciConnectivityValidation' from 'AzStackHCI.EnvironmentChecker' module for enhanced connectivity verification tests. For more information, see https://learn.microsoft.com/en-us/azure-stack/hci/whats-new#new-azure-stack-hci-environment-checker-tool.")] + [Obsolete("Test-AzStackhHCIConnection is deprecated. Please use 'Invoke-AzStackHciConnectivityValidation' from 'AzStackHCI.EnvironmentChecker' module for enhanced connectivity verification tests. For more information, see https://learn.microsoft.com/en-us/azure-stack/hci/manage/use-environment-checker.")] param( [Parameter(Mandatory = $false)] [string] $EnvironmentName = $AzureCloud, @@ -3458,8 +3988,8 @@ param( if($getCluster -eq $Null) { $NoClusterErrorMessage = $NoClusterError -f $ComputerName - Write-ErrorLog -Message $NoClusterErrorMessage -ErrorAction Continue - return + Write-ErrorLog -Message $NoClusterErrorMessage -Category OperationStopped -ErrorAction Continue + throw } else { @@ -3500,11 +4030,10 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - # Get script line number, offset and Command that resulted in exception. Write-ErrorLog with the exception above does not write this info. - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Test-AzStackHCIConnection : " + $positionMessage) -Category OperationStopped - throw + if(-Not $Error.Contains($AlreadyLoggedFlag)) + { + Write-ErrorLog "Exception occurred in Test-AzStackHCIConnection" -Exception $_ -Category OperationStopped + } } finally { @@ -3670,14 +4199,11 @@ param( if ($regContext.RegistrationStatus -ne [RegistrationStatus]::Registered) { - Write-ErrorLog -Category InvalidOperation -Message $SetAzResourceClusterNotRegistered -ErrorAction Continue - + Write-ErrorLog -Category InvalidOperation -Message $SetAzResourceClusterNotRegistered -Category OperationStopped -ErrorAction Continue $setOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value ([OperationStatus]::Failed) $setOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value $SetAzResourceClusterNotRegistered - Write-Output $setOutput | Format-List - - return + throw } $clusScript = { @@ -3697,14 +4223,11 @@ param( if ($nodeDown -eq $true) { - Write-ErrorLog -Category ConnectionError -Message $SetAzResourceClusterNodesDown -ErrorAction Continue - + Write-ErrorLog -Category ConnectionError -Message $SetAzResourceClusterNodesDown -Category OperationStopped -ErrorAction Continue $setOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyResult -Value ([OperationStatus]::Failed) $setOutput | Add-Member -MemberType NoteProperty -Name $OutputPropertyErrorDetail -Value $SetAzResourceClusterNodesDown - Write-Output $setOutput | Format-List - - return + throw } $subscriptionId = $regContext.AzureResourceUri.Split('/')[2] @@ -3877,13 +4400,10 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - - # Get script line number, offset and Command that resulted in exception. Write-ErrorLog with the exception above does not write this info. - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in {0} : {1}" -f $PSCmdlet.MyInvocation.InvocationName, $positionMessage) -Category OperationStopped - - throw + if(-Not $Error.Contains($AlreadyLoggedFlag)) + { + Write-ErrorLog ("Exception occurred in {0}" -f $PSCmdlet.MyInvocation.InvocationName) -Exception $_ -Category OperationStopped + } } finally { @@ -3970,7 +4490,7 @@ param( if ($ret.Exception) { - Write-ErrorLog "Unable to configure IMDS Service on VM. $($ret.Exception)" -ErrorAction Continue + Write-ErrorLog "Unable to configure IMDS Service on VM. $($ret.Exception)" -Exception $ret.Exception -ErrorAction Continue throw } @@ -4111,7 +4631,7 @@ param( if ($ret.Exception) { - Write-ErrorLog "Unable to configure IMDS Service on host. $($ret.Exception)" + Write-ErrorLog "Unable to configure IMDS Service on host. $($ret.Exception)" -Exception $ret.Exception throw } @@ -4357,10 +4877,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog -Message ("Exception occurred in Enable-AzueStackHCIImdsAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog -Message "Exception occurred in Enable-AzueStackHCIImdsAttestation" -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } @@ -4463,10 +4981,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Enable-AzStackHCIAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Enable-AzStackHCIAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } @@ -4491,7 +5007,7 @@ param( } catch { - Write-ErrorLog -Category OperationStopped $ErrorAddingAllVMs + Write-ErrorLog $ErrorAddingAllVMs -Category OperationStopped } } } @@ -4633,10 +5149,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Enable-AzueStackHCIImdsAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Enable-AzueStackHCIImdsAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } @@ -4687,14 +5201,11 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Enable-AzueStackHCIImdsAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ - } + Write-ErrorLog ("Exception occurred in Enable-AzueStackHCIImdsAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } - + } Invoke-Command @SessionParams -ScriptBlock { Sync-AzureStackHCI } Write-Progress -Id $MainProgressBarId -activity $DisableAzsHciImdsActivity -status "Complete" -percentcomplete 100 @@ -4817,10 +5328,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Add-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Add-AzStackHCIVMAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } @@ -4876,10 +5385,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Add-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Add-AzStackHCIVMAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } End @@ -4968,10 +5475,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Remove-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Remove-AzStackHCIVMAttestation") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } @@ -5006,10 +5511,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped -ErrorAction Continue - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Remove-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped -ErrorAction Continue - throw $_ + Write-ErrorLog ("Exception occurred in Remove-AzStackHCIVMAttestation. Check logs for details.") -Exception $_ -Category OperationStopped -ErrorAction Continue + throw } } End @@ -5060,10 +5563,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Get-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped - throw $_ + Write-ErrorLog ("Exception occurred in Get-AzStackHCIVMAttestation") -Exception $_ -Category OperationStopped + throw } } @@ -5094,7 +5595,7 @@ param( } catch { - Write-ErrorLog ("Exception occurred when querying cluster node $NodeName") -Category OperationStopped + Write-ErrorLog ("Exception occurred when querying cluster node $NodeName") -Exception $_ -Category OperationStopped } foreach ($adapter in $VirtualMachinesAdapters) @@ -5109,10 +5610,8 @@ param( } catch { - Write-ErrorLog -Exception $_.Exception -Category OperationStopped - $positionMessage = $_.InvocationInfo.PositionMessage - Write-ErrorLog ("Exception occurred in Get-AzStackHCIVMAttestation : " + $positionMessage) -Category OperationStopped - throw $_ + Write-ErrorLog ("Exception occurred in Get-AzStackHCIVMAttestation. Check logs for details.") -Exception $_ -Category OperationStopped + throw } } End @@ -5327,6 +5826,18 @@ function Enable-AzStackHCIRemoteSupport{ $AgreeToRemoteSupportConsent ) + if ($AgreeToRemoteSupportConsent -ne $true) + { + if($PSCmdlet.ShouldContinue("`r`nProceed with enabling remote support?", $RemoteSupportConsentText)) + { + $AgreeToRemoteSupportConsent = $true + } + else + { + return + } + } + if(Assert-IsObservabilityStackPresent){ Import-Module DiagnosticsInitializer -Verbose -Force Enable-RemoteSupport -AccessLevel $AccessLevel -ExpireInMinutes $ExpireInMinutes -SasCredential $SasCredential -AgreeToRemoteSupportConsent:$AgreeToRemoteSupportConsent @@ -5440,7 +5951,7 @@ function Assert-IsObservabilityStackPresent{ } } catch{ - Write-Error "Failed while getting Observability Remote Support service." + Write-ErrorLog "Failed while getting Observability Remote Support service." -Exception $_ return $false } } diff --git a/src/StackHCI/examples/Register-AzStackHCI.md b/src/StackHCI/examples/Register-AzStackHCI.md index bac7ab16a2b2..147a8c55e09a 100644 --- a/src/StackHCI/examples/Register-AzStackHCI.md +++ b/src/StackHCI/examples/Register-AzStackHCI.md @@ -42,13 +42,13 @@ Invoking from WAC. ### Example 4: ```powershell -Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciClusterRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential +Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciRG -ArcServerResourceGroupName HciRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential ``` ```output Result: Success -ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 -PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview +ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 +PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview PortalAADAppPermissionsURL: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/990be58d-578c-4cff-b4cd-43e9c3a77866/isMSAApp/ ``` diff --git a/src/StackHCI/exports/ProxyCmdletDefinitions.ps1 b/src/StackHCI/exports/ProxyCmdletDefinitions.ps1 index dfac8de96ead..d8c2c10d9b7b 100644 --- a/src/StackHCI/exports/ProxyCmdletDefinitions.ps1 +++ b/src/StackHCI/exports/ProxyCmdletDefinitions.ps1 @@ -3110,7 +3110,7 @@ Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Comp .Example Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -ArmAccessToken etyer..ere= -AccountId user1@corp1.com -Region westus -ResourceName DemoHCICluster3 -ResourceGroupName DemoHCIRG .Example -Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciClusterRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential +Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciRG -ArcServerResourceGroupName HciRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential .Outputs PSCustomObject. Returns following Properties in PSCustomObject @@ -3243,7 +3243,7 @@ param( [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.Category('Body')] [System.String] # Specifies the Arc Resource Group name. - # If not specified, service will generate a unique Resource Group name + # If not specified, cluster resource group name will be used. ${ArcServerResourceGroupName}, [Parameter(Position=14)] diff --git a/src/StackHCI/exports/README.md b/src/StackHCI/exports/README.md index cfea9ca49b83..6759a212e2ce 100644 --- a/src/StackHCI/exports/README.md +++ b/src/StackHCI/exports/README.md @@ -1,5 +1,5 @@ # Exports -This directory contains the cmdlets *exported by* `Az.StackHCI`. No other cmdlets in this repository are directly exported. What that means is the `Az.StackHCI` module will run [Export-ModuleMember](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/export-modulemember) on the cmldets in this directory. The cmdlets in this directory are generated at **build-time**. Do not put any custom code, files, cmdlets, etc. into this directory. Please use `..\custom` for all custom implementation. +This directory contains the cmdlets *exported by* `Az.StackHCI`. No other cmdlets in this repository are directly exported. What that means is the `Az.StackHCI` module will run [Export-ModuleMember](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/export-modulemember) on the cmldets in this directory. The cmdlets in this directory are generated at **build-time**. Do not put any custom code, files, cmdlets, etc. into this directory. Please use `..\custom` for all custom implementation. ## Info - Modifiable: no diff --git a/src/StackHCI/exports/Register-AzStackHCI.ps1 b/src/StackHCI/exports/Register-AzStackHCI.ps1 index dbbe320d29a4..13b0d7298290 100644 --- a/src/StackHCI/exports/Register-AzStackHCI.ps1 +++ b/src/StackHCI/exports/Register-AzStackHCI.ps1 @@ -26,7 +26,7 @@ Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Comp .Example Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -ArmAccessToken etyer..ere= -AccountId user1@corp1.com -Region westus -ResourceName DemoHCICluster3 -ResourceGroupName DemoHCIRG .Example -Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciClusterRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential +Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciRG -ArcServerResourceGroupName HciRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential .Outputs PSCustomObject. Returns following Properties in PSCustomObject @@ -159,7 +159,7 @@ param( [Microsoft.Azure.PowerShell.Cmdlets.StackHCI.Category('Body')] [System.String] # Specifies the Arc Resource Group name. - # If not specified, service will generate a unique Resource Group name + # If not specified, cluster resource group name will be used. ${ArcServerResourceGroupName}, [Parameter(Position=14)] diff --git a/src/StackHCI/generate-info.json b/src/StackHCI/generate-info.json index d76d0019733c..def3ba129de8 100644 --- a/src/StackHCI/generate-info.json +++ b/src/StackHCI/generate-info.json @@ -1,8 +1,8 @@ { - "swagger_commit": "89a9bf17524904e7670f0fd2d62ac882ca00d85c", + "swagger_commit": "18e83e24c36eed85a240103b476de0220ace7f84", "node": "v14.15.5", "autorest": "`-- (empty)", - "autorest_core": "3.9.3", + "autorest_core": "3.9.4", "autorest_modelerfour": "4.15.414", "autorest_powershell": "3.0.498" } diff --git a/src/StackHCI/help/Register-AzStackHCI.md b/src/StackHCI/help/Register-AzStackHCI.md index 4e404a5bfc83..ad2030cea627 100644 --- a/src/StackHCI/help/Register-AzStackHCI.md +++ b/src/StackHCI/help/Register-AzStackHCI.md @@ -70,13 +70,13 @@ Invoking from WAC. ### Example 4: ```powershell -Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciClusterRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential +Register-AzStackHCI -SubscriptionId "12a0f531-56cb-4340-9501-257726d741fd" -Region westus -ResourceName HciCluster1 -TenantId "c31c0dbb-ce27-4c78-ad26-a5f717c14557" -ResourceGroupName HciRG -ArcServerResourceGroupName HciRG -ArmAccessToken eerrer..ere= -AccountId user1@corp1.com -EnvironmentName AzureCloud -ComputerName node1hci -Credential Get-Credential ``` ```output Result: Success -ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 -PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciClusterRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview +ResourceId: /subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1 +PortalResourceURL: https://portal.azure.com/#@c31c0dbb-ce27-4c78-ad26-a5f717c14557/resource/subscriptions/12a0f531-56cb-4340-9501-257726d741fd/resourceGroups/HciRG/providers/Microsoft.AzureStackHCI/clusters/HciCluster1/overview PortalAADAppPermissionsURL: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/990be58d-578c-4cff-b4cd-43e9c3a77866/isMSAApp/ ``` @@ -102,7 +102,7 @@ Accept wildcard characters: False ### -ArcServerResourceGroupName Specifies the Arc Resource Group name. -If not specified, service will generate a unique Resource Group name +If not specified, cluster resource group name will be used. ```yaml Type: System.String diff --git a/src/StackHCI/how-to.md b/src/StackHCI/how-to.md index 43c60d72a424..27cdfa3c726c 100644 --- a/src/StackHCI/how-to.md +++ b/src/StackHCI/how-to.md @@ -14,7 +14,7 @@ To generate documentation, the process is now integrated into the `build-module. To test the cmdlets, we use [Pester](https://github.com/pester/Pester). Tests scripts (`.ps1`) should be added to the `test` folder. To execute the Pester tests, run the `test-module.ps1` script. This will run all tests in `playback` mode within the `test` folder. To read more about testing cmdlets, look at the [README.md](examples/README.md) in the `examples` folder. ## Packing `Az.StackHCI` -To pack `Az.StackHCI` for distribution, run the `pack-module.ps1` script. This will take the contents of multiple directories and certain root-folder files to create a `.nupkg`. The structure of the `.nupkg` is created so it can be loaded part of a [PSRepository](https://learn.microsoft.com/powershell/module/powershellget/register-psrepository). Additionally, this package is in a format for distribution to the [PSGallery](https://www.powershellgallery.com/). For signing an Azure module, please contact the [Azure PowerShell](https://github.com/Azure/azure-powershell) team. +To pack `Az.StackHCI` for distribution, run the `pack-module.ps1` script. This will take the contents of multiple directories and certain root-folder files to create a `.nupkg`. The structure of the `.nupkg` is created so it can be loaded part of a [PSRepository](https://docs.microsoft.com/powershell/module/powershellget/register-psrepository). Additionally, this package is in a format for distribution to the [PSGallery](https://www.powershellgallery.com/). For signing an Azure module, please contact the [Azure PowerShell](https://github.com/Azure/azure-powershell) team. ## Module Script Details There are multiple scripts created for performing different actions for developing `Az.StackHCI`. diff --git a/src/StackHCI/internal/README.md b/src/StackHCI/internal/README.md index 6ced9f965b49..02c4ea3fb268 100644 --- a/src/StackHCI/internal/README.md +++ b/src/StackHCI/internal/README.md @@ -8,7 +8,7 @@ This directory contains a module to handle *internal only* cmdlets. Cmdlets that - Packaged: yes ## Details -The `Az.StackHCI.internal.psm1` file is generated to this folder. This module file handles the hidden cmdlets. These cmdlets will not be exported by `Az.StackHCI`. Instead, this sub-module is imported by the `..\custom\Az.StackHCI.custom.psm1` module, allowing you to use hidden cmdlets in your custom, exposed cmdlets. To call these cmdlets in your custom scripts, simply use [module-qualified calls](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_command_precedence?view=powershell-6#qualified-names). For example, `Az.StackHCI.internal\Get-Example` would call an internal cmdlet named `Get-Example`. +The `Az.StackHCI.internal.psm1` file is generated to this folder. This module file handles the hidden cmdlets. These cmdlets will not be exported by `Az.StackHCI`. Instead, this sub-module is imported by the `..\custom\Az.StackHCI.custom.psm1` module, allowing you to use hidden cmdlets in your custom, exposed cmdlets. To call these cmdlets in your custom scripts, simply use [module-qualified calls](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_command_precedence?view=powershell-6#qualified-names). For example, `Az.StackHCI.internal\Get-Example` would call an internal cmdlet named `Get-Example`. ## Purpose This allows you to include REST specifications for services that you *do not wish to expose from your module*, but simply want to call within custom cmdlets. For example, if you want to make a custom cmdlet that uses `Storage` services, you could include a simplified `Storage` REST specification that has only the operations you need. When you run the generator and build this module, note the generated `Storage` cmdlets. Then, in your readme configuration, use [cmdlet hiding](https://github.com/Azure/autorest/blob/master/docs/powershell/options.md#cmdlet-hiding-exportation-suppression) on the `Storage` cmdlets and they will *only be exposed to the custom cmdlets* you want to write, and not be exported as part of `Az.StackHCI`. diff --git a/src/StackHCI/test/Run-InvokeCommand.Tests.ps1 b/src/StackHCI/test/Run-InvokeCommand.Tests.ps1 new file mode 100644 index 000000000000..2fe5f059b123 --- /dev/null +++ b/src/StackHCI/test/Run-InvokeCommand.Tests.ps1 @@ -0,0 +1,21 @@ +if(($null -eq $TestName) -or ($TestName -contains 'Run-InvokeCommand')) +{ + $loadEnvPath = Join-Path $PSScriptRoot 'loadEnv.ps1' + if (-Not (Test-Path -Path $loadEnvPath)) { + $loadEnvPath = Join-Path $PSScriptRoot '..\loadEnv.ps1' + } + . ($loadEnvPath) + $TestRecordingFile = Join-Path $PSScriptRoot 'Run-InvokeCommand.Recording.json' + $currentPath = $PSScriptRoot + while(-not $mockingPath) { + $mockingPath = Get-ChildItem -Path $currentPath -Recurse -Include 'HttpPipelineMocking.ps1' -File + $currentPath = Split-Path -Path $currentPath -Parent + } + . ($mockingPath | Select-Object -First 1).FullName +} + +Describe 'Run-InvokeCommand' { + It '__AllParameterSets' -skip { + { throw [System.NotImplementedException] } | Should -Not -Throw + } +}