Moc.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # Moc Module # ######################################################################################### #requires -runasadministrator using module .\Common.psm1 #region Module Constants $moduleName = "Moc" $moduleVersion = "1.0.57" #endregion #region Download catalog constants # Default until MOC has it's own catalog $catalogName = "aks-hci-stable-catalogs-ext" $ringName = "stable" $productName = "mocstack" #endregion #region Global Config if (!$global:config) { $global:config = @{} } #region Script Constants $mocBinaries = @( "wssdagent.exe" "wssdcloudagent.exe" "mocctl.exe" "nodectl.exe" ) $requiredServerFeatures = @( "Hyper-V", "Hyper-V-PowerShell", "RSAT-Clustering-PowerShell" ) $svcFailureRestartMs = 60000 $svcFailureResetSecond = 86400 $svcNodeAgentDependency = "winmgmt" $script:defaultTokenExpiryDays = 90 $script:defaultTokenExpirySeconds = $script:defaultTokenExpiryDays * 60 * 60 * 24 $script:mocVersionFeb = "1.0.8.10223" $script:mocVersionMar = "1.0.9.10413" $script:mocVersionMay = "1.0.10.10513" $script:mocVersionJune = "1.0.11.10707" $script:mocVersionAug = "1.0.13.10907" $script:mocVersionJan2023 = "1.0.16.10113" $script:mocVersionFeb2023 = "1.0.17.10230" $global:cloudAgentRegistryPath = "HKLM:\SOFTWARE\Microsoft\wssdcloudagent" $global:nodeAgentRegistryPath = "HKLM:\SOFTWARE\Microsoft\wssdagent" $global:cloudAgentCertName = "cloudagent.pem" $global:nodeAgentCertName = "nodeagent.pem" $global:cloudloginYAMLName = "cloudlogin.yaml" $global:nodeloginYAMLName = "nodelogin.yaml" $global:nodeToCloudloginYAMLName = "nodeToCloudlogin.yaml" $global:VMMSSpec = "vmms" $global:failoverClusterSpec = "fc" $global:cloudAgentCACertName = "CloudAgent" $global:cloudAgentServerCertName = "Server" $global:smallBinConcurrentDownloads = 1 $global:FailoverResourceName = "MOC Cloud Agent Service" $global:RegistryStore = "registry" $global:ClusterRegistryStore = "Cluster-Registry" $mocBinariesMap = @{ $global:nodeCtlBinary = $global:nodeCtlFullPath; $global:cloudCtlBinary = $global:cloudCtlFullPath; $global:cloudAgentBinary = $global:cloudAgentFullPath; $global:nodeAgentBinary = $global:nodeAgentFullPath; } #endregion #region # Install Event Log New-ModuleEventLog -moduleName $moduleName #endregion Import-LocalizedData -BindingVariable "GenericLocMessage" -FileName commonLocalizationMessages Import-LocalizedData -BindingVariable "MocLocMessage" -FileName MocLocalizationMessages #region Private Function function Initialize-MocConfiguration { <# .DESCRIPTION Initialize Moc Configuration Wipes off any existing cached configuration #> if ($global:config.ContainsKey($moduleName)) { $global:config.Remove($moduleName) } $global:config += @{ $moduleName = @{ "cloudAgentAuthorizerPort" = 0 "cloudAgentPort" = 0 "cloudConfigLocation" = $global:defaultCloudConfigLocation "cloudFqdn" = "localhost" "cloudLocation" = "" "cloudServiceCidr" = "" "clusterRoleName" = "" "deploymentType" = [DeploymentType]::None "dnsservers" = "" "forceDnsReplication" = $false "gateway" = "" "imageDir" = "" "installationPackageDir" = "" "installState" = [InstallState]::NotInstalled "ipaddressprefix" = "" "k8snodeippoolstart" = "" "k8snodeippoolend" = "" "macPoolEnd" = "" "macpoolname" = "" "macPoolStart" = "" "manifestCache" = "" "mocCertLocation" = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:cloudAgentCertName) "mocLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:cloudloginYAMLName) "moduleVersion" = $moduleVersion "nodeAgentAuthorizerPort" = 0 "nodeAgentPort" = 0 "nodeCertLocation" = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:nodeAgentCertName) "nodeConfigLocation" = $defaultNodeConfigLocation "nodeLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeloginYAMLName) "nodeToCloudLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeToCloudloginYAMLName) "nodeToCloudLoginYAMLDir" = "" "skipHostLimitChecks" = $false "skipUpdates" = $false "sshPrivateKey" = "" "sshPublicKey" = "" "sshCIDR" = "" "sshIPAddresses" = "" "sshRestrictCommands" = $false "stagingShare" = "" "tokenExpiryDays" = 0 "useStagingShare" = $false "version" = "" "vlanid" = 0 "vnetName" = "" "vswitchName" = "" "vnetvippoolend" = "" "vnetvippoolstart" = "" "workingDir" = "" "catalog" = "" "ring" = "" "proxyServerCertFile" = "" "proxyServerHTTP" = "" "proxyServerHTTPS" = "" "proxyServerNoProxy" = "" "proxyServerPassword" = "" "proxyServerUsername" = "" "certificateValidityFactor" = $global:certificateValidityFactor "caCertificateValidityFactor" = $global:caCertificateValidityFactor "nodeCertificateValidityFactor" = $global:nodeCertificateValidityFactor "cloudAgentTimeout" = $global:cloudAgentTimeout "deploymentId" = "" "nodeCount" = 1 "useNetworkController" = $false "networkControllerFqdnOrIpAddress" = "" "networkControllerClientCertificateName" = "" "networkControllerLbSubnetRef" = "" "networkControllerLnetRef" = "" "defaultVipPoolName" = "" "vipPoolStart" = "" "vipPoolEnd" = "" "offlineDownload" = $false "offsiteTransferCompleted" = $false "accessFileDirPath" = "" "accessFilePath" = "" "useUpdatedFailoverClusterCreationLogic" = $false "useDefaultClusterRoleName" = $false }; } } #endregion # Initialize Initialize-MocConfiguration #endregion #region invoke helpers function Invoke-MocShowCommand { <# .DESCRIPTION Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic service (multi-node/cluster deployments). .PARAMETER arguments Arguments to pass to cloud ctl. .PARAMETER ignoreError Optionally, ignore errors from the command (don't throw). #> param ( [String]$arguments, [Switch]$ignoreError, [ValidateSet("tsv", "csv", "yaml", "json")] [string]$output = "json" ) $arguments += " --output $output" $out = Invoke-MocCommand -arguments $arguments -ignoreError:$ignoreError.IsPresent if ([string]::IsNullOrWhiteSpace($out)) { return } return $out | ConvertFrom-Json } function Invoke-MocListCommand { <# .DESCRIPTION Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic service (multi-node/cluster deployments). .PARAMETER arguments Arguments to pass to cloud ctl. .PARAMETER ignoreError Optionally, ignore errors from the command (don't throw). #> param ( [String]$arguments, [Switch]$ignoreError, [ValidateSet("tsv", "csv", "yaml", "json")] [string]$output = "json", [string]$filter ) $arguments += " --output $output" if ($filter) { $arguments += " --query ""$filter""" } $out = Invoke-MocCommand -arguments $arguments -ignoreError:$ignoreError.IsPresent if ([string]::IsNullOrWhiteSpace($out) -or $out -like "No *") { return } Write-Verbose "$out" return $out | ConvertFrom-Json } function Invoke-MocCommand { <# .DESCRIPTION Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic service (multi-node/cluster deployments). .PARAMETER arguments Arguments to pass to cloud ctl. .PARAMETER ignoreError Optionally, ignore errors from the command (don't throw). .PARAMETER argDictionary Dictionary of arguments (e.g. obtained from $PSBoundParameters). .PARAMETER boolFlags List of boolean flags to be passed .PARAMETER activity Activity name to use when updating progress #> param ( [String]$arguments, [Switch]$ignoreError, [System.Collections.IDictionary] $argDictionary, [string[]] $boolFlags, [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity if (-not (Get-Command $global:cloudCtlFullPath -ErrorAction SilentlyContinue)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_missing_cmd , $global:cloudCtlFullPath)), ([ErrorTypes]::IsErrorFlag)) } $cloudFqdn = Get-CloudFqdn $cmdArgs = "--cloudFqdn $cloudFqdn $arguments" $argString = ConvertTo-ArgString -argDictionary $argDictionary -boolFlags $boolFlags if ($argString) { $cmdArgs += " " + $argString } try { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.invoke_moc_command, $cmdArgs)) $response = Invoke-CommandLine -Command $global:cloudCtlFullPath -Arguments $cmdArgs -ignoreError:$ignoreError -moduleName $global:MocModule } catch { $isCertificateExpired = $_.Exception -like "*Error: Certificate has expired: Expired*" if($isCertificateExpired) { Write-Host "Warning: The Certificate was expired. Renewing now." Repair-MocLogin $response = Invoke-CommandLine -Command $global:cloudCtlFullPath -Arguments $cmdArgs -ignoreError:$ignoreError -moduleName $global:MocModule } else { if ($_.Exception.Message) { Write-StatusWithProgress -activity $activity -moduleName $moduleName ` -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.invoke_moc_command_failed, $cmdArgs, $_.Exception.Message)) } throw $_ } } Write-Status $response -moduleName $global:MocModule Uninitialize-MocEnvironment -activity $activity return $response } function Invoke-NodeCommand { <# .DESCRIPTION Executes a nodeagent command. .PARAMETER arguments Arguments to pass to node ctl. #> param ( [String]$arguments ) if (-not (Get-Command $global:nodeCtlFullPath -ErrorAction SilentlyContinue)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_missing_cmd , $global:nodeCtlFullPath)), ([ErrorTypes]::IsErrorFlag)) } $cmdArgs = "$arguments" Invoke-CommandLine -Command $global:nodeCtlFullPath -Arguments $cmdArgs -moduleName $global:MocModule } function Invoke-MocLogin { <# .DESCRIPTION Provisions the Script to have access to node ctl .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$loginYaml ) Invoke-MocCommand $(" security login --loginpath ""$loginYaml"" --identity") } #endregion #region Exported Functions function Install-Moc { <# .DESCRIPTION The main deployment method for MOC. This function is responsible for provisioning files, deploying the agents. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -createConfigIfNotPresent -activity $activity $curState = Get-InstallState -module $moduleName switch ($curState) { ([InstallState]::Installed) { Write-Status -moduleName $moduleName $($MocLocMessage.moc_already_installed) Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_reinstall_uninstall) Uninitialize-MocEnvironment -activity $activity return } ([InstallState]::Installing) { Write-Status -moduleName $moduleName $($MocLocMessage.moc_installing) Uninitialize-MocEnvironment -activity $activity return } ([InstallState]::NotInstalled) { # Fresh install break } Default { # Cleanup partial installs from previous attempts Uninstall-Moc -activity $activity } } try { Install-MocInternal -activity $activity } catch [Exception] { $errorMessage = Write-ModuleEventException -message $MocLocMessage.moc_install_failed -exception $_ -moduleName $modulename Uninstall-Moc -SkipConfigCleanup:$True -activity $activity throw [CustomException]::new([System.Exception]::new($errorMessage, $_.Exception), ([ErrorTypes]::IsErrorFlag)) } Uninitialize-MocEnvironment -activity $activity Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Reset-Moc { <# .DESCRIPTION Cleans up an existing MOC deployment and reinstalls everything. This isn't equivalent to executing 'Uninstall-Moc' followed by 'Install-Moc' as Reset-Moc will preserve existing configuration settings and any downloaded images. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity Uninstall-Moc -SkipConfigCleanup:$True -activity $activity Install-MocInternal -activity $activity Uninitialize-MocEnvironment -activity $activity Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Uninstall-Moc { <# .DESCRIPTION Removes a MOC deployment. .PARAMETER SkipConfigCleanup Skip removal of the configurations after uninstall. After Uninstall, you have to Set-MocConfig to install again. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [Switch]$SkipConfigCleanup, [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } try { Initialize-MocEnvironment -activity $activity } catch [Exception] { Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } Set-MocConfigValue -name "installState" -value ([InstallState]::Uninstalling) try { # Confirm-Remoting - We assume that the remoting check was already done and nothing has changed Reset-Host -removeAll -skipConfigDeletion:$SkipConfigCleanup.IsPresent } catch [Exception] { Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled) Uninitialize-MocEnvironment -activity $activity if (!$SkipConfigCleanup.IsPresent) { Reset-Configuration -moduleName $moduleName } Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Initialize-MocNode { <# .SYNOPSIS Run checks on every physical node to see if all requirements are satisfied to install. .DESCRIPTION Run checks on every physical node to see if all requirements are satisfied to install. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_initializing_node) #Initialize-MocEnvironment Enable-Remoting Confirm-Remoting -localhostOnly Test-ForWindowsFeatures -features $script:requiredServerFeatures -nodeName $env:computername Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Update-Moc { <# .DESCRIPTION Update MOC to specified version. If not use the latest version .PARAMETER version The Optional version to update to. If none specified, it would be updated to latest .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$version, [String]$deploymentId, [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity $curState = Get-InstallState -module $moduleName if ($curState -eq [InstallState]::Updating) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_update_in_progress, $moduleName)), ([ErrorTypes]::IsUserErrorFlag)) } $currentVersion = Get-MocVersion Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_current_version, $currentVersion)) # This code is needed as a temporary solution till May2022 release is obselete. # the deployment ID was not part of MocConfig till June2022 release. During upgrade of AksHci from any previous # versions the deployment ID is passed from the upgrade-akshci. # # The only catch we have is if the update-Moc is called directly then we would not have the deployment ID # we would need to generate a new deployment ID if the set-mocconfig did not set the deployment ID if (-not [string]::IsNullOrWhiteSpace($deploymentId)) { Set-MocConfigValue -name "deploymentId" -value $deploymentId } else { # If the deploymentID passed is empty, We check the global moc config to see if the value already exists. # If it does not we genreate a new ID $deploymentId = Get-MocConfigValue -name "deploymentId" if ([string]::IsNullOrWhiteSpace($deploymentId)) { $deploymentId = [Guid]::NewGuid().ToString() Set-MocConfigValue -name "deploymentId" -value $deploymentId } } # If no version is specified, try to move to the latest if (!$version) { # If no version is specified, use the latest $release = Get-LatestRelease -moduleName $moduleName $version = $release.Version } if ($version -eq $currentVersion) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_already_in_expected_version, $version)) Uninitialize-MocEnvironment -activity $activity return } Set-MocConfigValue -name "version" -value $version Get-ProductRelease -Version $version -module $moduleName | Out-Null Set-MocConfigValue -name "installState" -value ([InstallState]::Updating) $workingDir = $global:config[$moduleName]["workingDir"] try { $installationPackageDir = ([io.Path]::Combine($workingDir, $version)) Set-MocConfigValue -name "installationPackageDir" -value $installationPackageDir New-Item -ItemType Directory -Force -Path $installationPackageDir | Out-Null Update-MocInternal -activity $activity -fromVersion $currentVersion -toVersion $version } catch { $errorMessage = Write-ModuleEventException -message "Update-Moc failed." -exception $_ -moduleName $modulename # Collect logs try { $tpath = Get-MocLogs $errorMessage += "`r`n Logs are available at $tpath" } catch { Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } Set-MocConfigValue -name "version" -value $currentVersion Set-MocConfigValue -name "installationPackageDir" -value $([io.Path]::Combine($workingDir, $currentVersion)) Set-MocConfigValue -name "installState" -value ([InstallState]::UpdateFailed) # Revert Update-MocInternal -activity $activity -fromVersion $version -toVersion $currentVersion throw [System.Exception]::new($errorMessage, $_.Exception) } Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) Uninitialize-MocEnvironment -activity $activity } function Update-MocInternal { <# .DESCRIPTION Upgrade MOC to selected version. .PARAMETER activity Activity name to use when updating progress .PARAMETER fromVersion Updating from which version .PARAMETER toVersion Updating to which version #> param ( [String]$activity = $MyInvocation.MyCommand.Name, [String]$fromVersion, [String]$toVersion ) trap { Set-MocConfigValue -name "installState" -value ([InstallState]::UpdateFailed) throw $_ } Set-MocConfigValue -name "installState" -value ([InstallState]::Updating) if(([version]$toVersion -ge [version]$script:mocVersionAug) -and ([version]$fromVersion -lt [version]$script:mocVersionAug)) { Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir)) Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName)) } Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_updating_to_version , $toVersion)) New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null Get-MocRelease -version $toVersion -activity $activity # 1. Stop the agents # 2. Upgrade Agents $isMultiNodeDeployment = Test-MultiNodeDeployment if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Ignore | ForEach-Object { Stop-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Ignore } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { Stop-Service wssdagent -Force } Update-MocNode -nodeName $_.Name -activity $activity # remove this function call when Sept release is not supported Update-CloudAgent -cloudAgentName $global:cloudAgentAppName -fromVersion $fromVersion -toVersion $toVersion Update-NodeAgent -nodeName $_.Name -activity $activity -fromVersion $fromVersion -toVersion $toVersion } } else { Stop-Service wssdcloudagent -Force Stop-Service wssdagent -Force Update-CloudAgent -cloudAgentName $global:cloudAgentAppName -fromVersion $fromVersion -toVersion $toVersion Update-NodeAgent -nodeName ($env:computername) -activity $activity -fromVersion $fromVersion -toVersion $toVersion Update-MocNode -nodeName ($env:computername) -activity $activity } # 3. Start Cloud agent if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | ForEach-Object { Start-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Stop } } else { Start-Service wssdcloudagent -ErrorAction Ignore -WarningAction:SilentlyContinue } # We must wait for the generic service VIP to become usable Wait-ForCloudAgentEndpoint -activity $activity # 4. Start Node agents if ($isMultiNodeDeployment) { # if the new nodeToCloudLoginYAML directory does not exist, cx is updating from an older version to a newer version # we need to create the directory and add acl to it. If the directory already exist, cx is updating from a newer verison and acl already exists # we need to set-content for nodeIdentity in both case Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeCloudLoginFile = Get-NodeToCloudLoginYamlFilePath -nodeName $_.Name if (([version]$toVersion) -ge ([version]$script:mocVersionMar)) { # set tokenExpiryDays to 90 only if it is not set or < 90. # We are enforcing a 90day min expiry days in the code to avoid upgrade issues $tokenExpiryDays = Get-MocConfigValue -name "tokenExpiryDays" $tokenExpirySeconds = Get-MocConfigValue -name "tokenExpirySeconds" if (!$tokenExpirySeconds) { Set-MocConfigValue -name "tokenExpirySeconds" -value $script:defaultTokenExpirySeconds } if (!$tokenExpiryDays -or ($tokenExpiryDays -lt $script:defaultTokenExpiryDays) ) { Set-MocConfigValue -name "tokenExpiryDays" -value $script:defaultTokenExpiryDays if ([version]$fromVersion -lt [version]$script:mocVersionFeb2023) { Update-MocIdentity -name $_.Name -validityDays $global:config[$moduleName]["tokenExpiryDays"] -location $global:config[$modulename]["cloudLocation"] | Out-Null } else { Update-MocIdentity -name $_.Name -validitySeconds $global:config[$moduleName]["tokenExpirySeconds"] ` -location $global:config[$modulename]["cloudLocation"] -loginFilePath $nodeCloudLoginFile -enableTokenAutoRotate | Out-Null } } } $nodeIdentity = Invoke-MocIdentityRotate -name $_.Name Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err Invoke-Command -ComputerName $_ -ScriptBlock { Start-Service wssdagent -WarningAction:SilentlyContinue } } } else { $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"] if (([version]$toVersion) -ge ([version]$script:mocVersionMar)) { # set tokenExpiryDays to 90 only if it is not set or < 90. # We are enforcing a 90day min expiry days in the code to avoid upgrade issues $tokenExpiryDays = Get-MocConfigValue -name "tokenExpiryDays" $tokenExpirySeconds = Get-MocConfigValue -name "tokenExpirySeconds" if (!$tokenExpirySeconds) { Set-MocConfigValue -name "tokenExpirySeconds" -value $script:defaultTokenExpirySeconds } if (!$tokenExpiryDays -or ($tokenExpiryDays -lt $script:defaultTokenExpiryDays)) { Set-MocConfigValue -name "tokenExpiryDays" -value $script:defaultTokenExpiryDays if ([version]$fromVersion -lt [version]$script:mocVersionFeb2023) { Update-MocIdentity -name ($env:computername) -validityDays $global:config[$moduleName]["tokenExpiryDays"] -location $global:config[$modulename]["cloudLocation"] | Out-Null } else { Update-MocIdentity -name ($env:computername) -validitySeconds $global:config[$moduleName]["tokenExpirySeconds"] ` -location $global:config[$modulename]["cloudLocation"] -loginFilePath $nodeCloudLoginFile -enableTokenAutoRotate | Out-Null } } } $nodeIdentity = Invoke-MocIdentityRotate -name ($env:computername) Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err Start-Service wssdagent -ErrorAction Ignore -WarningAction:SilentlyContinue } $mocLocation = $global:config[$modulename]["cloudLocation"] Wait-ForActiveNodes -location $mocLocation -activity $activity Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) } function Repair-MocLogin { <# .DESCRIPTION Relogin to Admin using updated cloudlogin. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_repairing_admin_login) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity $loginpath = $global:config[$modulename]["mocLoginYAML"] Invoke-MocCommand " security login --loginpath ""$loginpath"" --identity" Uninitialize-MocEnvironment -activity $activity } function Repair-Moc { <# .DESCRIPTION Repair cloudagent cert & nodeagent loginconfig. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_repairing_nodeagent) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_stopping_nodeagent) ## Set cloudAgentTimeout value if not set $cloudAgentTimeout = $global:config[$modulename]["cloudAgentTimeout"] if ((-not $cloudAgentTimeout) -or ($cloudAgentTimeout -eq 0)) { Set-ConfigurationValue -name "cloudAgentTimeout" -value $global:cloudAgentTimeout -module $moduleName } # 1. Stop the agents $isMultiNodeDeployment = Test-MultiNodeDeployment $version = Get-MocVersion if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Ignore | ForEach-Object { Stop-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Ignore } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { Stop-Service wssdagent -Force } } } else { Stop-Service wssdcloudagent -Force Stop-Service wssdagent -Force } # 2. Start the cloud agent if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | ForEach-Object { Start-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Stop } } else { Start-Service wssdcloudagent -ErrorAction Ignore -WarningAction:SilentlyContinue } $cloudAgentTimeout = $global:config[$modulename]["cloudAgentTimeout"] Wait-ForCloudAgentEndpoint -timeout $cloudAgentTimeout -activity $activity Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_rotating_login_token) # 3. Rotate Token # 4. Restart agents if ($isMultiNodeDeployment) { $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"] # if the nodeToCloudLogin yaml does not exist in the new directory (i.e workingdir/wssdagent), then cx is reparing from an older verion # and the node agent service parameter's nodeToCloudLoginYAML variable contains the old location. To ensure compability, we will: # 1) create and set acl for the new nodeToCloudLoginYAML directory and write the nodeIdentity there. 2) Write the new node identity to # the existing location as well. The paramete will get updated when customer run update-nodeAgent the next time # If the nodeToCloudLogin yaml exist in the new directory, then cx is reparing from the newer version and the node agent cloud service # parameter contains the new location. we will only overwrite the node identity to the new version. Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeIdentity = Invoke-MocIdentityRotate -name $_.Name $updatedNodeCloudLoginFile = Get-NodeToCloudLoginYamlFilePath -nodeName $_.Name if (!(Test-Path -path $updatedNodeCloudLoginFile -PathType leaf)) { New-Item -ItemType File -Force -Path $updatedNodeCloudLoginFile | Out-Null } Set-Content -Path $updatedNodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err Invoke-Command -ComputerName $_ -ScriptBlock { $nodeToCloudLoginFile = $args[0] $cloudagentlogin = $args[1] if (Test-Path -Path $nodeToCloudLoginFile -PathType leaf) { Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err } Start-Service wssdagent -WarningAction:SilentlyContinue } -ArgumentList $nodeCloudLoginFile, $nodeIdentity } } else { $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"] $nodeIdentity = Invoke-MocIdentityRotate -name ($env:computername) Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err Start-Service wssdagent -ErrorAction Ignore -WarningAction:SilentlyContinue } $mocLocation = $global:config[$modulename]["cloudLocation"] Wait-ForActiveNodes -location $mocLocation -activity $activity Uninitialize-MocEnvironment -activity $activity } function Get-MocVersion { <# .DESCRIPTION Get the current MOC version .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Initialize-MocEnvironment -activity $activity Uninitialize-MocEnvironment -activity $activity return $global:config[$modulename]["version"] } function Update-MocNode { <# .DESCRIPTION Upgrade Moc binaries to latest on a node .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [Parameter(Mandatory=$true)] [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_upgrading_node, $nodeName)) Install-MocBinaries -nodeName $nodeName # Cleanup nodeagent console logs Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeLog = $args[0] Get-ChildItem -Recurse -Path $nodeLog -Filter wssd*.log -ErrorAction Ignore | ForEach-Object { Remove-Item -Path $_ -ErrorAction Ignore -Force } } -ArgumentList $global:config[$modulename]["nodeConfigLocation"] # Cleanup cloudagent console logs $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"] Get-ChildItem -Recurse -Path $cloudConfigPath -Filter wssd*.log -ErrorAction Ignore | ForEach-Object { Remove-Item -Path $_ -ErrorAction Ignore -Force } } function Install-MocBinaries { <# .DESCRIPTION Copies Moc binaries to a node .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName ) Install-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName } function Uninstall-MocBinaries { <# .DESCRIPTION Copies Moc binaries to a node .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName ) Uninstall-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName } function Get-MocConfig { <# .DESCRIPTION Loads and returns the current MOC configuration. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Import-MocConfig -activity $activity Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_get_configuration) # Fixup the type for readability reasons # we need mocConfig variable, without that $global:config[$moduleName]["installState"] mutation is not happeing $mocConfig = $global:config[$moduleName] $mocConfig["installState"] = Get-InstallState -module $moduleName $mocConfig["deploymentType"] = Get-ConfigurationValue -module $moduleName -type ([Type][DeploymentType]) -name "deploymentType" $global:config[$moduleName] = $mocConfig return $mocConfig } function Import-MocConfig { <# .DESCRIPTION Loads a configuration from persisted storage. If no configuration is present then a default configuration can be optionally generated and persisted. ' .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [switch]$createIfNotPresent, [string]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_importing_configuration) if (Test-Configuration -moduleName $moduleName) { Import-Configuration -moduleName $moduleName } else { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_cannot_deploy, $moduleName)), ([ErrorTypes]::IsUserErrorFlag)) } if ($global:config[$moduleName]["cloudConfigLocation"] -ine $defaultCloudConfigLocation) { $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName)) $currentValue = Get-MocConfigValue -name "mocCertLocation" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "mocCertLocation" -value $tmp } $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName)) $currentValue = Get-MocConfigValue -name "mocLoginYAML" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "mocLoginYAML" -value $tmp } } if ($global:config[$moduleName]["nodeConfigLocation"] -ine $defaultNodeConfigLocation) { $tmp = ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName)) $currentValue = Get-MocConfigValue -name "nodeCertLocation" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "nodeCertLocation" -value $tmp } } Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_importing_configuration_completed) Write-Status -moduleName $moduleName $($GenericLocMessage.comm_validating_configuration) if (-not (Test-LocalFilePath -path $global:config[$moduleName]["nodeConfigLocation"])) { throw $($MocLocMessage.moc_nodeConfigLocation_rerun) } } function Set-MocOffsiteConfig { <# .DESCRIPTION Configures MOC when using offline download in offsite scenario. Any parameter which is not explictly provided by the user will be defaulted. #> param ( [parameter()] [String] $workingDir = $global:defaultWorkingDir, [parameter()] [String] $catalog = $script:catalogName, [parameter()] [String] $ring = $script:ringName, [Parameter(Mandatory=$true)] [String] $stagingShare = $global:defaultStagingShare, [parameter()] [String] $version ) try { Import-MocConfig -activity $activity } catch {} $currentState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState" if ($currentState -ne $null) { switch ($currentState) { ([InstallState]::NotInstalled) { # Fresh install break } Default { Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_current_state, $currentState)) throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_new_config_in_current_state, $moduleName, $currentState)), $true) } } } Set-MocConfigValue -name "catalog" -value $catalog Set-MocConfigValue -name "ring" -value $ring Set-MocConfigValue -name "workingDir" -value $workingDir Set-MocConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, $("$catalog.json"))) Set-MocConfigValue -name "stagingShare" -value $stagingShare Set-MocConfigValue -name "offlineDownload" -value $true if (-not $version) { $version = Get-ConfigurationValue -Name "version" -module $moduleName if (-not $version) { # If no version is specified, use the latest $version = Get-MocLatestVersion Set-MocConfigValue -name "version" -value $version } } else { Get-MocLatestVersion | out-null # This clears the cache Get-ProductRelease -Version $version -module $moduleName | Out-Null Set-MocConfigValue -name "version" -value $version } Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_new_configuration_for_module_saved, $moduleName)) } function Set-MocOfflineOffsiteTransferCompleted { <# .DESCRIPTION Set offsiteTransferCompleted flag which is used for offline download offsite scenario. #> param ( [Parameter(Mandatory=$true)] [bool] $offsiteTransferCompleted ) Set-MocConfigValue -name "offsiteTransferCompleted" -value $offsiteTransferCompleted } function Set-MocConfig { <# .DESCRIPTION Configures MOC by persisting the specified parameters to the registry. Any parameter which is not explictly provided by the user will be defaulted. #> [CmdletBinding()] param ( [string] $activity = $MyInvocation.MyCommand.Name, [String] $workingDir = $global:defaultWorkingDir, # Optional. This can be configured or derived from $workingDir [String] $imageDir, [String] $version, [String] $stagingShare = $global:defaultStagingShare, # Optional. This can be configured or derived from $workingDir [String] $cloudConfigLocation, [String] $nodeConfigLocation = $global:defaultNodeConfigLocation, [String] $cloudLocation = $global:defaultCloudLocation, [VirtualNetwork] $vnet, [SSHConfiguration] $ssh, [int] $nodeAgentPort = $global:defaultNodeAgentPort, [int] $nodeAgentAuthorizerPort = $global:defaultNodeAuthorizerPort, [int] $cloudAgentPort = $global:defaultCloudAgentPort, [int] $cloudAgentAuthorizerPort = $global:defaultCloudAuthorizerPort, [int64] $tokenExpiryDays = $script:defaultTokenExpiryDays, [int64] $tokenExpirySeconds = $script:defaultTokenExpirySeconds, [String] $clusterRoleName, [Alias("cloudServiceCidr")] [String] $cloudServiceIP = "", [String] $sshPublicKey, [Switch] $skipUpdates, [Switch] $skipHostLimitChecks, [Switch] $skipRemotingChecks, [Switch] $forceDnsReplication, [String] $macPoolStart, [String] $macPoolEnd, [switch] $useStagingShare, [String] $catalog = $script:catalogName, [String] $ring = $script:ringName, [bool] $createAutoConfigContainers = $global:defaultCreateAutoConfigContainers, [ProxySettings] $proxySettings = $null, [String] $deploymentId = [Guid]::NewGuid().ToString(), [parameter(DontShow)] [float] $certificateValidityFactor = $global:certificateValidityFactor, [parameter(DontShow)] [float] $caCertificateValidityFactor = $global:caCertificateValidityFactor, [parameter(DontShow)] [Switch] $enablePreview, [parameter(DontShow)] [float] $nodeCertificateValidityFactor = $global:nodeCertificateValidityFactor, [int] $cloudAgentTimeout = $global:cloudAgentTimeout, [Parameter(ParameterSetName = 'NetworkController')] [Switch] $useNetWorkController, [Parameter(ParameterSetName = 'NetworkController')] [string] $networkControllerFqdnOrIpAddress, [Parameter(ParameterSetName = 'NetworkController')] [string] $networkControllerClientCertificateName, [Parameter(ParameterSetName = 'NetworkController')] [string] $networkControllerLbSubnetRef, [Parameter(ParameterSetName = 'NetworkController')] [string] $networkControllerLnetRef, [VipPoolSettings] $vipPool, [Switch] $skipValidationCheck, [bool] $offlineDownload = $false, [bool] $offsiteTransferCompleted = $false ) # First get the existing config and find out if we are in the middle of something. try { Import-MocConfig -activity $activity } catch {} $currentState = Get-InstallState -module $moduleName switch ($currentState) { ([InstallState]::NotInstalled) { # Fresh install break } Default { Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_current_state, $currentState)) throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_new_config_in_current_state, $moduleName, $currentState)), ([ErrorTypes]::IsUserErrorFlag)) } } # Normalizing directory path $workingDir = Update-DirectoryPath -directoryPath $workingDir $imageDir = Update-DirectoryPath -directoryPath $imageDir $stagingShare = Update-DirectoryPath -directoryPath $stagingShare $cloudConfigLocation = Update-DirectoryPath -directoryPath $cloudConfigLocation $nodeConfigLocation = Update-DirectoryPath -directoryPath $nodeConfigLocation # testClusterRoleName will be used to test failover cluster networking. We are not using the same name for test as it can cause race condition. # We have seen issue where AD entry was not cleaned up properly on time even though failover cluster group was removed successfully. # We are not setting the testClusterRoleName if clusterRoleName is passed by user. This is because in this case DNS/AD entries will be # added manually prior to deploying MOC. We might not have permission to create using a different role name. if ([string]::IsNullOrWhiteSpace($clusterRoleName)) { $clusterRoleName = $($global:cloudAgentAppName + "-" + [guid]::NewGuid()) $testClusterRoleName = $("test-" + [guid]::NewGuid()) Set-MocConfigValue -name "testClusterRoleName" -value $testClusterRoleName Set-MocConfigValue -name "useDefaultClusterRoleName" -value $true } # Test whether to use V1 or V2 failover cluster creation logic if (Test-UseUpdatedFailoverClusterCreationFlow) { Set-MocConfigValue -name "useUpdatedFailoverClusterCreationLogic" -value $true } if ([string]::IsNullOrWhiteSpace($workingDir)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir")) } if ([string]::IsNullOrWhiteSpace($imageDir)) { $imageDir = [io.Path]::Combine($workingDir, $global:imageDirectoryName) } if ([string]::IsNullOrWhiteSpace($cloudConfigLocation)) { $cloudConfigLocation = [io.Path]::Combine($workingDir, $global:cloudConfigDirectoryName) } if ($skipValidationCheck.IsPresent) { # if we are good, Validate the input configuration Confirm-Configuration -workingDir $workingDir -skipHostLimitChecks:$skipHostLimitChecks.IsPresent ` -imageDir $imageDir -cloudConfigLocation $cloudConfigLocation -skipRemotingChecks:$skipRemotingChecks.IsPresent ` -useStagingShare:$useStagingShare.IsPresent -stagingShare $stagingShare ` -vnet $vnet -cloudServiceCidr $cloudServiceIP ` -useNetWorkController:$useNetWorkController.IsPresent ` -networkControllerClientCertificateName $networkControllerClientCertificateName ` -networkControllerLbSubnetRef $networkControllerLbSubnetRef ` -networkControllerLnetRef $networkControllerLnetRef ` -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress ` -clusterRoleName $clusterRoleName } else { if ($skipHostLimitChecks.IsPresent) { Set-MocConfigValue -name "skipHostLimitChecks" -value $true } if ($skipRemotingChecks.IsPresent) { Set-MocConfigValue -name "skipRemotingChecks" -value $true } } Set-ProxyConfiguration -proxySettings $proxySettings -moduleName $moduleName # if we are good to proceed, create the requested configuration Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_creating_configuration, $moduleName)) Set-MocConfigValue -name "workingDir" -value $workingDir Set-MocConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, $("$catalog.json"))) Set-MocConfigValue -name "imageDir" -value $imageDir Set-MocConfigValue -name "cloudConfigLocation" -value $cloudConfigLocation Set-MocConfigValue -name "moduleVersion" -value $moduleVersion Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled) Set-MocConfigValue -name "stagingShare" -value $stagingShare Set-MocConfigValue -name "nodeConfigLocation" -value $nodeConfigLocation Set-MocConfigValue -name "cloudLocation" -value $cloudLocation Set-MocConfigValue -name "catalog" -value $catalog Set-MocConfigValue -name "ring" -value $ring Set-MocConfigValue -name "createAutoConfigContainers" -value $createAutoConfigContainers Set-MocConfigValue -name "deploymentId" -value $deploymentId Set-MocConfigValue -name "certificateValidityFactor" -value $certificateValidityFactor Set-MocConfigValue -name "caCertificateValidityFactor" -value $caCertificateValidityFactor Set-MocConfigValue -name "nodeCertificateValidityFactor" -value $nodeCertificateValidityFactor Set-MocConfigValue -name "cloudAgentTimeout" -value $cloudAgentTimeout Set-MocConfigValue -name "offlineDownload" -value $offlineDownload Set-MocConfigValue -name "offsiteTransferCompleted" -value $offsiteTransferCompleted if ($useNetworkController.IsPresent) { Set-MocConfigValue -name "useNetworkController" -value $true Set-MocConfigValue -name "networkControllerFqdnOrIpAddress" -value $networkControllerFqdnOrIpAddress Set-MocConfigValue -name "networkControllerClientCertificateName" -value $networkControllerClientCertificateName Set-MocConfigValue -name "networkControllerLbSubnetRef" -value $networkControllerLbSubnetRef Set-MocConfigValue -name "networkControllerLnetRef" -value $networkControllerLnetRef } else { Set-MocConfigValue -name "useNetworkController" -value $false } if ($vnet) { Set-VNetConfiguration -module $moduleName -vnet $vnet } if ($sshPublicKey) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_depricated_parameter_please_use, "sshPublicKey", "ssh")) } if ($ssh) { Set-SSHConfiguration -module $moduleName -sshConfig $ssh } $sshPublicKey = Get-MocConfigValue -name "sshPublicKey" if([string]::IsNullOrWhiteSpace($sshPublicKey)) { # There seems to be an issue with ssh_keygen. It doesnt create public key when using share. if ($workingDir.StartsWith("\\")) { Set-MocConfigValue -name "sshPublicKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa.pub") Set-MocConfigValue -name "sshPrivateKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa") } else { Set-MocConfigValue -name "sshPublicKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa.pub")) Set-MocConfigValue -name "sshPrivateKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa")) } } Set-MocConfigValue -name "nodeAgentPort" -value $nodeAgentPort Set-MocConfigValue -name "nodeAgentAuthorizerPort" -value $nodeAgentAuthorizerPort Set-MocConfigValue -name "cloudAgentPort" -value $cloudAgentPort Set-MocConfigValue -name "cloudAgentAuthorizerPort" -value $cloudAgentAuthorizerPort Set-MocConfigValue -name "clusterRoleName" -value $clusterRoleName Set-MocConfigValue -name "skipUpdates" -value $skipUpdates.IsPresent Set-MocConfigValue -name "skipHostLimitChecks" -value $skipHostLimitChecks.IsPresent Set-MocConfigValue -name "forceDnsReplication" -value $forceDnsReplication.IsPresent Set-MocConfigValue -name "useStagingShare" -value $useStagingShare.IsPresent Set-MocConfigValue -name "macPoolStart" -value $macPoolStart Set-MocConfigValue -name "macPoolEnd" -value $macPoolEnd Set-MocConfigValue -name "cloudServiceCidr" -value $cloudServiceIP if ($vipPool -ne $null) { Set-MocConfigValue -name "defaultVipPoolName" -value $vipPool.Name Set-MocConfigValue -name "vipPoolStart" -value $vipPool.VipPoolStart Set-MocConfigValue -name "vipPoolEnd" -value $vipPool.VipPoolEnd } if (-not $version) { # If no version is specified, use the latest from the product catalog $release = Get-LatestRelease -moduleName $moduleName $version = $release.Version Set-MocConfigValue -name "version" -value $version } else { Get-LatestCatalog -moduleName $moduleName | Out-Null # This clears the cache Get-ProductRelease -Version $version -module $moduleName | Out-Null Set-MocConfigValue -name "version" -value $version } $commands = Get-ConfigurationValue -Name "commands" -module $moduleName if (-not $commands) { # If no commands are found, initialize with current command $currentCommand = [Command]::new($activity, [System.Diagnostics.Process]::GetCurrentProcess().Id, $(hostname), $(get-date)) $commands = @($currentCommand) Set-MocConfigValue -name "commands" -value $commands } Set-MocConfigValue -name "installationPackageDir" -value ([io.Path]::Combine($workingDir, $version)) Set-MocConfigValue -name "mocCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName)) Set-MocConfigValue -name "nodeCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName)) Set-MocConfigValue -name "mocLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName)) Set-MocConfigValue -name "nodeLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeloginYAMLName)) Set-MocConfigValue -name "nodeToCloudLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeToCloudloginYAMLName)) Set-MocConfigValue -name "tokenExpiryDays" -value $tokenExpiryDays Set-MocConfigValue -name "tokenExpirySeconds" -value $tokenExpirySeconds # In the new approach, the nodeToCloudYAMLLogin file will be stored in the working directory so it's shared with all nodes in a multi-node # deployment scenario. For example, will be stored in C:\AksHci\wssdagent Set-MocConfigValue -name "nodeToCloudLoginYAMLDir" -value ([io.Path]::Combine($workingDir, $global:nodeConfigDirectoryName)) Initialize-Directories #Multi-Admin Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir)) Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName)) [System.Environment]::SetEnvironmentVariable('ACCESSFILE_DIR_PATH', $global:config[$moduleName]["accessFileDirPath"]) Set-DeploymentType # There is one use case, where configuration is set when it is lost and restored. # Setting the install state appropriately if ((Test-Path $global:cloudCtlFullPath)) { try { Wait-ForCloudAgentEndpoint -timeout 1 Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_existing_configuration_loaded) } catch { # Ingoring this explicitly as no existing deploying is found } } else { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_new_configuration_saved) } Save-ConfigurationDirectory -moduleName $moduleName -WorkingDir $workingDir Save-Configuration -moduleName $moduleName if (-not $skipValidationCheck.IsPresent) { Test-DownloadSdkConfiguration -proxySettings $proxySettings $testResults = Test-MocConfiguration -skip $overallResult = $true $resultDetails = "" $failedTest = "" foreach($result in $testResults) { if ($result.TestResult -eq "Failed") { $overallResult = $false if ($failedTest -ne "") { $failedTest = $failedTest + ", " + $result.TestName $resultDetails = $resultDetails + "`n" + $result.Details } else { $failedTest = $result.TestName $resultDetails = $result.Details } } } if (-not $overallResult) { $testResults | ConvertTo-Html -Title $mocLocMessage.moc_validation_report_title | Out-File -FilePath moc_validation_report.html $reportFileName = "moc_validation_report.html" Reset-Configuration -moduleName $moduleName $errorMsg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_set_configuration_failure, $reportFileName)) ` + "`n" + $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_failed_tests, $failedTest)) ` + "`n" + $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_failed_tests_details, $resultDetails)) Write-Host '' Write-Host '' Write-Host $errorMsg -ForegroundColor Red Write-Host '' Write-Host '' throw [CustomException]::new($errorMsg, ([ErrorTypes]::IsUserErrorFlag)) } else { Write-Host '' Write-Host '======================================================' Write-Host $MocLocMessage.moc_validation_set_configuration_success -ForegroundColor Green Write-Host '======================================================' Write-Host '' } } else { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_new_configuration_for_module_saved, $moduleName)) } Uninitialize-MocEnvironment -activity $activity } function Set-MocConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) Set-ConfigurationValue -name $name -value $value -module $moduleName } function Get-MocConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) return Get-ConfigurationValue -name $name -module $moduleName } function Get-MocHyperThreadingEnabled { <# .DESCRIPTION Check if HyperThreading is enabled on the current node comparing number of actual cores and the logical processors #> $hyperThreadingEnabled = $true $moclocation = $global:config[$modulename]["cloudLocation"] $coresInfo = Invoke-MocCommand " cloud node list -o json --query ""[?properties.statuses.Info]"" --location $moclocation" if ($null -ne $coresInfo) { $cores = [UInt32]::Parse([regex]::Match($coresInfo, "cores:(\d+)").captures.groups[1].value) $logicalProcessors = [UInt32]::Parse([regex]::Match($coresInfo, "logicalprocessors:(\d+)").captures.groups[1].value) $hyperthreadingEnabled = ($cores -ne $logicalProcessors) } return $hyperthreadingEnabled } function Get-CloudAgentServiceParameters { <# .DESCRIPTION Returns the service parameters for cloud agent service #> if (Test-MultiNodeDeployment) { $dataStore = $global:ClusterRegistryStore $multiNodeParams = "--clusterresourcename ""$global:FailoverResourceName""" } else { $dataStore = $global:RegistryStore } $cloudFqdn = Get-CloudFqdn $certificateValidityFactor = $global:config[$modulename]["certificateValidityFactor"] $caCertificateValidityFactor = $global:config[$modulename]["caCertificateValidityFactor"] $cloudConfigLocation = $global:config[$modulename]["cloudConfigLocation"] $useNetworkController = $global:config[$modulename]["useNetworkController"] $networkControllerFqdnOrIpAddress = $global:config[$modulename]["networkControllerFqdnOrIpAddress"] $networkControllerLbSubnetRef = $global:config[$modulename]["networkControllerLbSubnetRef"] $networkControllerLnetRef = $global:config[$modulename]["networkControllerLnetRef"] $networkControllerClientCertificateName = $global:config[$modulename]["networkControllerClientCertificateName"] $ncParameters = " --usenetworkcontroller --networkcontrollerfqdnoripaddress ""$networkControllerFqdnOrIpAddress"" --networkcontrollerlbsubnetref ""$networkControllerLbSubnetRef"" --networkcontrollerlnetref ""$networkControllerLnetRef"" --networkcontrollerclientcertificatename ""$networkControllerClientCertificateName""" if ($global:config[$modulename]["deploymentId"]) { $deploymentId = "--deploymentid " + $global:config[$modulename]["deploymentId"] } $cloudServiceParameters = "--service --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""$dataStore"" $multiNodeParams --certificatevalidityfactor $certificateValidityFactor --cacertificatevalidityfactor $caCertificateValidityFactor $deploymentId" if ($useNetworkController) { $cloudServiceParameters = $cloudServiceParameters + $ncParameters } return $cloudServiceParameters } function Install-CloudAgentOnNode { <# .DESCRIPTION Provision a Cloud Agent for multinode setup 1. Download Cloud Agent Binary 2. Configure Service This function will not start the cloud agent service. .PARAMETER nodeName The node to execute on. .PARAMETER startupType Startup type to create cloud agent service. #> param ( [String]$nodeName, [String]$startupType ) $cloudServiceParameters = Get-CloudAgentServiceParameters Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installing_cloudagent_service_on, $nodeName)) Invoke-Command -ComputerName $nodeName -ScriptBlock { $cloudAgentFullPath = $args[0] $cloudServiceParameters = $args[1] $startupType = $args[2] $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType $startupType -DisplayName "WSSD Cloud Agent Service" | Out-Null } -ArgumentList $global:cloudAgentFullPath, $cloudServiceParameters, $startupType } function Uninstall-CloudAgentService { <# .DESCRIPTION Removes Cloud Agent Service .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Invoke-Command -ComputerName $nodeName -ScriptBlock { $cloudConfigLocation = $args[0] $registryLocation = $args[1] $GenericLocMessage = $args[2] try { Get-Service wssdcloudagent -ErrorAction Ignore | ForEach-Object { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { Stop-Service wssdcloudagent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } } # Flush dns on all nodes to avoid issues with stale entries when re-installing cloudagent. # The server flushes dns during installation, so not needed for single node. Clear-DnsClientCache } catch [Exception] { # Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_directory, $(hostname))) if ($cloudConfigLocation) { Remove-Item -Path $cloudConfigLocation -Force -Recurse -ErrorAction Ignore } write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_registry, $(hostname))) Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction Ignore } -ArgumentList @($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentRegistryPath, $GenericLocMessage) } function Test-MocSdnEnabled { <# .DESCRIPTION Check if SDN integration is enabled #> $sdnConfig = $global:config[$modulename]["useNetworkController"] return ($sdnConfig -eq $true) } function Invoke-MocRotateCACertificate { <# .DESCRIPTION The main deployment method for MOC. This function is responsible for provisioning files, deploying the agents. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [int]$sleepDuration=20, [int]$timeout=3600, #seconds in a hour [String]$activity = $MyInvocation.MyCommand.Name ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -createConfigIfNotPresent -activity $activity Write-Status -moduleName $moduleName $($GenericLocMessage.moc_rotate_ca_certificate) $currentDate = (Get-Date).ToUniversalTime() try { Update-MocCertificate -name $global:cloudAgentCACertName # Sleeping for the server certificate to be generated sleep 5 Repair-MocLogin Wait-ForCACertificateUpdate -sleepDuration $sleepDuration -timeout $timeout -currentDate $currentDate Wait-ForServerCertificateUpdate -sleepDuration $sleepDuration -timeout $timeout -currentDate $currentDate } catch { throw $_ } Uninitialize-MocEnvironment -activity $activity Write-Status -moduleName $moduleName $($GenericLocMessage.moc_rotate_ca_certificate_complete) } #region Installation and Provisioning functions function Install-MocInternal { <# .DESCRIPTION The main deployment method for MOC. This function is responsible for provisioning files, deploying the agents. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) trap { Set-MocConfigValue -name "installState" -value ([InstallState]::InstallFailed) throw $_ } Set-MocConfigValue -name "installState" -value ([InstallState]::Installing) Reset-Host -removeAll -skipImageDeletion -skipConfigDeletion -activity $activity # Calling Initialize again, since Reset-Host cleans up everything Initialize-Directories Get-MocRelease -version $(Get-MocVersion) -activity $activity Initialize-Cloud -activity $activity Test-CloudConfiguration Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) Write-Status -moduleName $moduleName $($MocLocMessage.moc_installation_complete) } function Initialize-MocEnvironment { <# .DESCRIPTION Executes steps to prepare the environment for day 0 operations. .PARAMETER createConfigIfNotPresent Whether the call should create a new deployment configuration if one is not already present. .PARAMETER activity Activity name to use when updating progress #> param ( [Switch]$createConfigIfNotPresent, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_discovering_configuration) Import-MocConfig -createIfNotPresent:($createConfigIfNotPresent.IsPresent) Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_applying_configuration) Initialize-Environment -checkForUpdates:$false -moduleName $script:moduleName -activity $activity #Multi-Admin # Make sure that ACCESSFILE_DIR_PATH is setup. ACCESSFILE_DIR_PATH is constructed from accessFileDirPath # So, we need to be sure that accessFileDirPath is available before setting the env var. if([version]$global:config[$moduleName]["version"] -ge [version]$script:mocVersionAug) { if($null -eq $global:config[$moduleName]["accessFileDirPath"]){ Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir)) Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName)) } [System.Environment]::SetEnvironmentVariable('ACCESSFILE_DIR_PATH', $global:config[$moduleName]["accessFileDirPath"]) } } function Uninitialize-MocEnvironment { <# .DESCRIPTION Executes steps to teardown the environment for Moc operations. .PARAMETER activity Activity name to use when updating progress #> param ( [Parameter(Mandatory=$true)] [String]$activity ) Uninitialize-Environment -moduleName $script:moduleName -activity $activity } function Initialize-Cloud { <# .DESCRIPTION Provision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_provisioning_cloud) # 1. Provision Moc Agents Install-MocAgents -activity $activity # 2. Wait for cloud nodes to be active Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_provisioning_cloud_resources) $mocLocation = $global:config[$modulename]["cloudLocation"] Wait-ForActiveNodes -location $mocLocation -activity $activity # 3. Provision Cloud MacPool $mocLocation = $global:config[$modulename]["cloudLocation"] if (-not [string]::IsNullOrWhiteSpace($global:config[$modulename]["macPoolStart"])) { New-MocMacPool -name $global:cloudMacPool ` -location $($mocLocation) ` -macPoolStart $global:config[$modulename]["macPoolStart"] ` -macPoolEnd $global:config[$modulename]["macPoolEnd"] | Out-Null } # 4. Provision Global Vipool if (-not [string]::IsNullOrWhiteSpace($global:config[$modulename]["defaultVipPoolName"])) { New-MocVipPool -name $global:config[$modulename]["defaultVipPoolName"] ` -vipPoolStart $global:config[$modulename]["vipPoolStart"] ` -vipPoolEnd $global:config[$modulename]["vipPoolEnd"] ` -location $mocLocation } # 5. Add cloud resources New-MocContainer -name $global:cloudStorageContainer -location $mocLocation -path $global:config[$modulename]["imageDir"] | Out-Null # 6. Add storge container for each CSV for multinode failover cluster setup $createAutoConfigContainers = $global:config[$modulename]["createAutoConfigContainers"] if ($createAutoConfigContainers -And (Test-MultiNodeDeployment)) { $table = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo, State $i = 1 foreach ($row in $table) { if ($row.State -ne "Online") {continue} $scname = "auto-config-container-" $scname = $scname + $i $CSVPath = $row.SharedVolumeInfo.FriendlyVolumeName if (!$CSVPath) { # if volume is not online, we can skip it continue } $CSVPath = Join-Path $CSVPath $scname if (!(Test-Path($CSVPath))) { New-Item -path $CSVPath -ItemType Directory } New-MocContainer -name $scname -location $mocLocation -path $CSVPath $i = $i + 1 } } } function Install-MocAgents { <# .DESCRIPTION Provision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_provisioning_agents) # 1. Provision Nodes if (Test-MultiNodeDeployment) { Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name Initialize-Node -nodeName $nodeName } } else { Initialize-Node -nodeName ($env:computername) } # 2. Provision Cloud Agent Install-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity New-MocLocation -name $global:config[$modulename]["cloudLocation"] | Out-Null $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML") # 4. Provision Nodes if (Test-MultiNodeDeployment) { $failoverCluster = Get-FailoverCluster New-MocCluster -name $failoverCluster.Name -fqdn $($failoverCluster.Name+"."+$failoverCluster.Domain) -location $global:config[$modulename]["cloudLocation"] | Out-Null Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name Install-NodeAgent -nodeName $nodeName -activity $activity Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml } } else { Install-NodeAgent -nodeName ($env:computername) -activity $activity Invoke-NodeCommand "security login --loginpath ""$nodeLoginYaml"" --identity" } } function Install-CloudAgent { <# .DESCRIPTION Provision a Cloud Agent 1. Download Cloud Agent Binary 2. Configure Service 3. Start the Service .PARAMETER cloudAgentName Cloud agent name .PARAMETER activity Activity name to use when updating progress #> param ( [String]$cloudAgentName, [String]$activity = $MyInvocation.MyCommand.Name ) # Install the service as Cluster Gen Service Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_cloudagent, $cloudAgentName)) $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"] New-Item -ItemType Directory -Force -Path $cloudConfigPath | Out-Null Set-SecurePermissionFolder -Path $cloudConfigPath if (Test-MultiNodeDeployment) { $nodes = Get-ClusterNode -ErrorAction Stop $nodes.Name | ForEach-Object { Install-CloudAgentOnNode -nodeName $_ -startupType "Manual" } Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_adding_wssdcloudagent_cluster_generic_service_role, $global:config[$modulename]["clusterRoleName"])) $cloudServiceParameters = Get-CloudAgentServiceParameters Add-FailoverClusterGenericRole -serviceDisplayName "$global:FailoverResourceName" -serviceName wssdcloudagent -staticIpCidr $global:config[$modulename]["cloudServiceCidr"] -clusterGroupName $global:config[$modulename]["clusterRoleName"] -serviceParameters $cloudServiceParameters Request-DnsReplication } else { Install-CloudAgentOnNode -nodeName ($env:computername) -startupType "Automatic" # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdcloudagent" actions= "restart/$script:svcFailureRestartMs/restart/$script:svcFailureRestartMs//0" reset= $script:svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } Start-Service "wssdcloudagent" -WarningAction:SilentlyContinue | Out-Null } # We must wait for the generic service VIP to become usable (i.e. wait for DNS to propogate) otherwise calls to moc will fail and the script will exit prematurely. Wait-ForCloudAgentEndpoint -activity $activity if (Test-MultiNodeDeployment) { if ($null -eq $env:ACCESSFILE_DIR_PATH) { Write-SubStatus -moduleName $moduleName -msg $($MocLocMessage.moc_replicating_cloudconfig) # TODO: Switch to using individual tokens Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = $nodeName = ${_}.Name if ($nodeName -ine $env:computername) { Copy-FileToRemoteNode -remoteNode $nodeName -source $global:accessFileLocation -destination $global:accessFileLocation } } } } } function Initialize-Node { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_node, $nodeName)) Install-MocBinaries -nodeName $nodeName Initialize-HostOs -NodeName $nodeName } function Install-NodeAgent { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_nodeagent, $nodeName)) $cloudFqdn = Get-CloudFqdn # If multi-node deployment, we will write the nodeToCloudLogin file to the new shared working directory # If single-node deployment, we will write the nodeToCloudLogin file to the legacy location if (Test-MultiNodeDeployment) { $nodeToCloudLoginYAMLPath = Get-NodeToCloudLoginYamlFilePath -nodeName $nodeName } else { $nodeToCloudLoginYAMLPath = $global:config[$modulename]["nodeToCloudLoginYAML"] } New-Item -ItemType File -Force -Path $nodeToCloudLoginYAMLPath | Out-Null # Before Moc Feb 2023 release, MocCli security identity create only have --validity-days flag. After the Feb release, we have introduced the --validity-seconds option # to ensure backwards compability, depending on the Moc version the cx is running, we will have an argsDict that only contain --validity-days for # releases before Feb 2023 and will have a argsDict that contain both --validity-days and --validity-seconds. From MocCli, if --validity-seconds is provided # then we will only use --validity-seconds and discard --validity-days. --validity-days is only used shen --validity-seconds is not provided. # the long term solution will be to use expose --validity-days from the powershell perspespective and will expose --validity-seconds from the moccli perspective # we will do the conversion if ([version]$currentMocVersion -lt [version]$script:mocVersionFeb2023) { $nodeIdentity = New-MocIdentity -name $nodeName -validityDays $global:config[$moduleName]["tokenExpiryDays"] -fqdn $cloudFqdn -location $global:config[$modulename]["cloudLocation"] -port $global:config[$modulename]["cloudAgentPort"] -authport $global:config[$modulename]["cloudAgentAuthorizerPort"] } else { $nodeIdentity = New-MocIdentity -name $nodeName -validitySeconds $global:config[$moduleName]["tokenExpirySeconds"] -fqdn $cloudFqdn -location $global:config[$modulename]["cloudLocation"] -port $global:config[$modulename]["cloudAgentPort"] -authport $global:config[$modulename]["cloudAgentAuthorizerPort"] -loginFilePath $nodeToCloudLoginYAMLPath -enableTokenAutoRotate } New-MocRoleAssignmentWhenAvailable -identityName $nodeName -roleName "NodeContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null Set-Content -Path $nodeToCloudLoginYAMLPath -Value $nodeIdentity -ErrorVariable err $providerSpecArgs = $global:VMMSSpec if (Test-MultiNodeDeployment) { $failoverCluster = Get-FailoverCluster $nodeFqdn = $nodeName + "." + $failoverCluster.Domain $providerSpecArgs = $global:failoverClusterSpec } else { # Pick the host domain $nodeFqdn = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName } $nodeCertificateValidityFactor = "" if ($global:config[$modulename]["nodeCertificateValidityFactor"]) { $nodeCertificateValidityFactor = "--certificatevalidityfactor " + $global:config[$modulename]["nodeCertificateValidityFactor"] } $deploymentId = "--deploymentid " + $global:config[$modulename]["deploymentId"] return Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeAgentFullPath = $args[0] $nodeConfigLocation = $args[1] $dotFolderPath = $args[2] $nodeagentfqdn = $args[3] $svcFailureRestartMs = $args[4] $svcFailureResetSecond = $args[5] $svcNodeAgentDependency = $args[6] $nodeToCloudLoginFile = $args[7] $nodeAgentRegistryPath = $args[8] $providerSpec = $args[9] $nodeName = $args[10] $nodeCertificateValidityFactor = $args[11] $deploymentId = $args[12] New-Item -ItemType Directory -Force -Path $nodeConfigLocation | Out-Null $acl = Get-Acl $nodeConfigLocation $acl.SetAccessRuleProtection($true,$false) $adminGroup = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminGroup, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) $acl | Set-Acl $nodeConfigLocation $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdagent\' -force -ErrorAction Ignore $nodeServiceParams = "--service --basedir ""$nodeConfigLocation"" --cloudloginfile ""${nodeToCloudLoginFile}"" --dotfolderpath ""$dotFolderPath"" --nodeagentfqdn $nodeagentfqdn --nodename $nodeName --objectdatastore ""registry"" --wssdproviderspec ""$providerSpec"" $nodeCertificateValidityFactor $deploymentId" New-Service -Name "wssdagent" -BinaryPath """$nodeAgentFullPath"" $nodeServiceParams " -StartupType "Automatic" -DisplayName "MOC NodeAgent Service" | Out-Null # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdagent" actions= "restart/$svcFailureRestartMs/restart/$svcFailureRestartMs//0" reset= $svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } # Make the service dependant on WMI $result = sc.exe config "wssdagent" depend= $svcNodeAgentDependency if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } Start-Service wssdagent -WarningAction:SilentlyContinue | Out-Null # Set Registry Permissions $acl = Get-Acl $nodeAgentRegistryPath $acl.SetAccessRuleProtection($true,$false) $adminGroup = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) $accessRule = New-Object System.Security.AccessControl.RegistryAccessRule($adminGroup,"FullControl","ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) $acl | Set-Acl $nodeAgentRegistryPath } -ArgumentList $global:nodeAgentFullPath, $global:config[$modulename]["nodeConfigLocation"], $global:config[$modulename]["nodeConfigLocation"], $nodeFqdn, $script:svcFailureRestartMs, $script:svcFailureResetSecond, $script:svcNodeAgentDependency, $nodeToCloudLoginYAMLPath, $global:nodeAgentRegistryPath, $providerSpecArgs, $nodeName, $nodeCertificateValidityFactor, $deploymentId } function Update-NodeAgent { <# .DESCRIPTION Method to apply workaround to nodeagent during upgrade .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress .PARAMETER toVersion Moc version to upgrade .PARAMETER fromVersion Moc version from which upgrade is happening #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name, [string]$fromVersion, [string]$toVersion ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_nodeagent, $nodeName)) $providerSpecArgs = $global:VMMSSpec if (Test-MultiNodeDeployment) { $failoverCluster = Get-FailoverCluster $nodeFqdn = $nodeName + "." + $failoverCluster.Domain $providerSpecArgs = $global:failoverClusterSpec } else { $nodeFqdn = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName } if (([version]$fromVersion -lt [version]$script:mocVersionJune) -and ([version]$toVersion -ge [version]$script:mocVersionJune)) { # If upgrading to version greater than or equal to June version # from the version older than June version # add deploymentid $deploymentIdParams = "--deploymentid " + $global:config[$modulename]["deploymentId"] } # If downgrading to less than May version from version greater than equal to may reset deployment ID # If downgrading to less than May version from version less than may, we dont have to reset. # eg: Reset is not needed for downgrading from April to March elseif (([version]$fromVersion -gt [version]$script:mocVersionMay) -and ([version]$toVersion -le [version]$script:mocVersionMay)) { $deploymentIdParams = "" } else { # The existing service configuration is the same # Other versions, nothing to do return } Write-Status -moduleName $moduleName "Migrating NodeAgent on [$nodeName] and patching with the params [$providerSpecArgs] [$extraParams] and [$deploymentIdParams]" # If Multi-node deployment the nodeToCloudLogin file will be stored in the share working directory # If single-node deployment the nodeToCloudLogin file will be stored in the legacy location if (Test-MultiNodeDeployment) { $nodeToCloudLoginYAMLPath = Get-NodeToCloudLoginYamlFilePath -nodeName $nodeName } else { $nodeToCloudLoginYAMLPath = $global:config[$modulename]["nodeToCloudLoginYAML"] } $result = Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeAgentFullPath = $args[0] $nodeConfigLocation = $args[1] $dotFolderPath = $args[2] $nodeagentfqdn = $args[3] $svcFailureRestartMs = $args[4] $svcFailureResetSecond = $args[5] $svcNodeAgentDependency = $args[6] $nodeToCloudLoginFile = $args[7] $nodeAgentRegistryPath = $args[8] $providerSpec = $args[9] $extraParams = $args[10] $deploymentIdParams = $args[11] $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdagent\' -force -ErrorAction Ignore $nodeServiceParams = "--service --basedir ""$nodeConfigLocation"" --cloudloginfile ""${nodeToCloudLoginFile}"" --dotfolderpath ""$dotFolderPath"" --nodeagentfqdn $nodeagentfqdn --objectdatastore ""registry"" --wssdproviderspec ""$providerSpec"" $extraParams $deploymentIdParams" New-Service -Name "wssdagent" -BinaryPath """$nodeAgentFullPath"" $nodeServiceParams " -StartupType "Automatic" -DisplayName "MOC NodeAgent Service" | Out-Null # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdagent" actions= "restart/$svcFailureRestartMs/restart/$svcFailureRestartMs//0" reset= $svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } # Make the service dependant on WMI $result = sc.exe config "wssdagent" depend= $svcNodeAgentDependency if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } } -ArgumentList $global:nodeAgentFullPath, $global:config[$modulename]["nodeConfigLocation"], $global:config[$modulename]["nodeConfigLocation"], $nodeName, $script:svcFailureRestartMs, $script:svcFailureResetSecond, $script:svcNodeAgentDependency ,$nodeToCloudLoginYAMLPath,` $global:nodeAgentRegistryPath, $providerSpecArgs, $extraParams, $deploymentIdParams Write-Status -moduleName $moduleName "Migration completed on [$nodeName] with result [$result]" } function Update-CloudAgent { <# .DESCRIPTION Method to apply workaround to cloudagent during upgrade .PARAMETER cloudAgentName Cloud agent name .PARAMETER activity Activity name to use when updating progress .PARAMETER toVersion Moc version to upgrade .PARAMETER fromVersion Moc version from which upgrade is happening #> param ( [String]$cloudAgentName, [String]$activity = $MyInvocation.MyCommand.Name, [string]$fromVersion, [string]$toVersion ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_cloudagent, $cloudAgentName)) $authArgs = "" $deploymentIdParams ="" $cloudFqdn = Get-CloudFqdn $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"] $certificateValidityFactor = $global:config[$modulename]["certificateValidityFactor"] $cloudConfigLocation = $global:config[$modulename]["cloudConfigLocation"] $useNetworkController = $global:config[$modulename]["useNetworkController"] $networkControllerFqdnOrIpAddress = $global:config[$modulename]["networkControllerFqdnOrIpAddress"] $networkControllerLbSubnetRef = $global:config[$modulename]["networkControllerLbSubnetRef"] $networkControllerLnetRef = $global:config[$modulename]["networkControllerLnetRef"] $networkControllerClientCertificateName = $global:config[$modulename]["networkControllerClientCertificateName"] $ncParameters = " --usenetworkcontroller --networkcontrollerfqdnoripaddress ""$networkControllerFqdnOrIpAddress"" --networkcontrollerlbsubnetref ""$networkControllerLbSubnetRef"" --networkcontrollerlnetref ""$networkControllerLnetRef"" --networkcontrollerclientcertificatename ""$networkControllerClientCertificateName""" if (([version]$fromVersion -lt [version]$script:mocVersionJune) -and ([version]$toVersion -ge [version]$script:mocVersionJune)) { # If upgrading to version greater than or equal to June version # from the version older than June version # add deploymentid $deploymentIdParams = "--deploymentid " + $global:config[$modulename]["deploymentId"] } # If downgrading to less than May version from version greater than equal to may reset deployment ID # If downgrading to less than May version from version less than may, we dont have to reset. # eg: Reset is not needed for downgrading from April to March elseif (([version]$fromVersion -gt [version]$script:mocVersionMay) -and ([version]$toVersion -le [version]$script:mocVersionMay)) { $deploymentIdParams = "" } else { # The existing service configuration is the same # Other versions, nothing to do return } Write-Status -moduleName $moduleName "Migrating CloudAgent and patching with the params [$deploymentIdParams]" if (Test-MultiNodeDeployment) { $nodes = Get-ClusterNode -ErrorAction Stop $clusterRoleName = $global:config[$modulename]["clusterRoleName"] $cloudFqdn = Update-CloudFQDN $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""Cluster-Registry"" --clusterresourcename ""$clusterRoleName"" --certificatevalidityfactor $certificateValidityFactor $deploymentIdParams" if ($useNetworkController) { $cloudServiceParameters = $cloudServiceParameters + $ncParameters } $nodes.Name | ForEach-Object { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installing_cloudagent_service_on, $_)) Invoke-Command -ComputerName $_ -ScriptBlock { $cloudAgentFullPath = $args[0] $cloudServiceParameters = $args[1] $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Manual" -DisplayName "WSSD Cloud Agent Service" | Out-Null } -ArgumentList $global:cloudAgentFullPath, $cloudServiceParameters } Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_adding_wssdcloudagent_cluster_generic_service_role, $global:config[$modulename]["clusterRoleName"])) } else { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""registry"" --certificatevalidityfactor $certificateValidityFactor $deploymentIdParams" if ($useNetworkController) { $cloudServiceParameters = $cloudServiceParameters + $ncParameters } New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Automatic" -DisplayName "WSSD Cloud Agent Service" | Out-Null # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdcloudagent" actions= "restart/$script:svcFailureRestartMs/restart/$script:svcFailureRestartMs//0" reset= $script:svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } } } function Request-DnsReplication { <# .DESCRIPTION This optionally attempts to speed up DNS replication by manually replicating data between DNSs. In order for this method to succeed, the current user needs to have sufficient access permissions to update entries in the DNS server. In case of a failure, the code is skiped and this function is a best-effort. #> try { $forceDnsReplication = Get-ConfigurationValue -module $moduleName -type ([Type][System.Boolean]) -name "forceDnsReplication" if ($forceDnsReplication) { # Only proceed if the Get-DnsServerResourceRecord cmdlet is available (i.e. if the DNS-Server-Tools are installed) if ((Get-Module "DnsServer" -All -ErrorAction Ignore).Count -eq 0) { $wf = Get-WindowsOptionalFeature -FeatureName "DNS-Server-Tools" -Online if ($null -eq $wf) { # no-op return } if ($wf.State -ine "Enabled") { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_installing_missing_feature) Enable-WindowsOptionalFeature -Online -FeatureName "DNS-Server-Tools" -All -NoRestart -WarningAction SilentlyContinue } } Import-Module "DnsServer" # Get the Ip associated with the cluster role and the host NIC associated with the vnetName network $ClusterResourceIP = (Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Ignore | Get-ClusterResource -ErrorAction Ignore | Where-Object { $_.ResourceType -eq "IP Address" } | Get-ClusterParameter -name Address -ErrorAction Ignore).Value # Get the external route with the lowest metric $externalRoute = (Get-NetRoute "0.0.0.0/0" -ErrorAction Ignore | Sort-Object -Property InterfaceMetric) | select -First if ($ClusterResourceIP -ne $null -and $externalRoute -ne $null) { $externalInterfaceIpConfig = $externalRoute | Get-NetAdapter | Get-NetIPConfiguration $dnsZone = $externalInterfaceIpConfig.NetProfile.Name $dnsServers = ($externalInterfaceIpConfig.DNSServer | Where-Object { $_.AddressFamily -eq 2 }).ServerAddresses foreach ($dnsServer in $dnsServers) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_registering_cloudagent_on_dns_server, $dnsServer)) Get-DnsServerResourceRecord -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -ErrorAction Ignore | Remove-DnsServerResourceRecord -ComputerName $dnsServer -Force -ZoneName $dnsZone Add-DnsServerResourceRecordA -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -AllowUpdateAny -IPv4Address $ClusterResourceIP -TimeToLive 01:00:00 } } } } catch { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_could_not_force_dns_replication) } } #endregion #region Verification Functions function Test-CloudConfiguration { <# .DESCRIPTION A basic sanity test of cloud and all node configuration. #> if (Test-MultiNodeDeployment) { # Check for Cloud Agent Gen App Service Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null # We only test DNS Name when cx is using non default cluster role name $useUpdateFailoverClusterCreationFlow = (Get-MocConfig)["useUpdatedFailoverClusterCreationLogic"] if (-not $useUpdateFailoverClusterCreationFlow) { Get-ClusterResource -Name $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Test-NodeConfiguration -nodeName $_.Name } } else { Test-NodeConfiguration -nodeName ($env:computername) } Test-CloudResource } function Test-NodeConfiguration { <# .DESCRIPTION A basic sanity test to make sure that a node is ready to deploy kubernetes. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Test-ForWindowsFeatures -nodeName $nodeName Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_performing_sanity_checks, $nodeName)) Test-MocInstallation -nodeName $nodeName Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_for_ssh_key) if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_does_not_exist, $global:config[$modulename]["sshPrivateKey"])) } Test-Process -processName "wssdagent" -nodeName $nodeName Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_node_resource_is_provisioned) Get-MocNode -name $nodeName -location $global:config[$modulename]["cloudLocation"] | Out-Null } function Test-CloudResource { <# .DESCRIPTION A basic sanity test to ensure that cloudagent has been provisioned with the expected resources. #> Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_node_resource_is_provisioned) Get-MocLocation -name $global:config[$modulename]["cloudLocation"] | Out-Null Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_container_resource_is_provisioned) Get-MocContainer -name $global:cloudStorageContainer -location $global:config[$modulename]["cloudLocation"] | Out-Null } function Test-HostNetworking { <# .DESCRIPTION Sanity check the host networking configuration. .PARAMETER nodeName The node to execute on. .PARAMETER vswitchName The name of the vnet switch to be checked. #> param ( [parameter(Mandatory=$true)] [string]$nodeName, [parameter(Mandatory=$true)] [string]$vswitchName ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_checking_host_networking_configuration, $nodeName)) $isMultinode = Test-MultiNodeDeployment Invoke-Command -ComputerName $nodeName -ScriptBlock { $vswitchName = $args[0] $nodeName = $args[1] $isMultinode = $args[2] $MocLocMessage = $args[3] $GenericLocMessage = $args[4] if ([string]::IsNullOrWhitespace($vswitchName)) { throw $($MocLocMessage.moc_no_vswitchname) } write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_checking_for_virtual_switch_with_name, $vswitchName, $nodeName)) if($isMultinode) { $existing = Get-VMSwitch -SwitchType External -Name $vswitchName -ErrorAction Ignore if ($null -eq $existing) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_external_vswitchname, $vswitchName, $nodeName)) } } else { $existing = Get-VMSwitch -Name $vswitchName -ErrorAction Ignore if ($null -eq $existing) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_missing_vswitchname, $vswitchName, $nodeName)) } } return } -ArgumentList $vswitchName, $nodeName, $isMultinode, $MocLocMessage, $GenericLocMessage } function Test-MocInstallation { <# .DESCRIPTION Sanity checks an installation to make sure that all expected binaries are present. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-SubStatus -moduleName $moduleName $($GenericLocMessage.generic_testing_expected_binaries) Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath Test-Binary -nodeName $nodeName -binaryName $global:cloudAgentFullPath Test-Binary -nodeName $nodeName -binaryName $global:nodeCtlFullPath Test-Binary -nodeName $nodeName -binaryName $global:cloudCtlFullPath } function Wait-ForConnectionFromNodes { <# .DESCRIPTION Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it. #> param ( [int]$sleepDuration=20, [int]$timeout=1800 #seconds in 30min ) $timer=[Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { try { Test-CloudDnsFromNodes } catch { $err = $_ Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $err.Exception.Message)) Start-Sleep $sleepDuration continue } return } if ($null -ne $err) { throw $err } } function Test-CloudDnsFromNodes { <# .DESCRIPTION Ensure that all the nodes can resolve the dns name of cloudagent. .PARAMETER cloudFqdn Fqdn to attemp to resolve and connect .PARAMETER ports List of ports to test #> param ( [String] $cloudFqdn, [AllowNull()][String[]] $ports ) if (Test-MultiNodeDeployment) { $nodes = Get-ClusterNode -ErrorAction SilentlyContinue if([String]::IsNullOrEmpty($cloudFqdn)){ $cloudFqdn = $(Get-CloudFqdn) } if(-not $PSBoundParameters.ContainsKey("ports")){ $cloudPorts = ($global:config[$modulename]["cloudAgentPort"], $global:config[$modulename]["cloudAgentAuthorizerPort"]) } $nodes | ForEach-Object { $nodeName = $_.Name try { Test-MocApiServer -cloudFqdn $cloudFqdn -ports $cloudPorts -nodeName $nodeName } catch [Exception] { throw [CustomException]::new($([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_agent_cannot_reach_apiserver , $nodeName, $cloudFqdn), $_.Exception)), ([ErrorTypes]::IsInfraErrorFlag)) } } } } function Wait-ForCACertificateUpdate { <# .DESCRIPTION Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it. #> param ( [int]$sleepDuration=20, [int]$timeout=1800, #seconds in 30min [parameter(Mandatory=$true)] [DateTime]$currentDate ) $timer=[Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { Write-Status -moduleName $moduleName $($MocLocMessage.moc_waiting_for_ca_certificate_update) $result = Get-MocCertificate -name $global:cloudAgentCACertName | ConvertFrom-Json $notBeforeDate = (([System.DateTimeOffset]::FromUnixTimeSeconds($result.tags[0].NotBeforeEpoch)).DateTime).ToUniversalTime() # Add 5 minutes to Offset certificate 5 minutes during creation $notBeforeDate = $notBeforeDate.AddMinutes(5) if ($notBeforeDate -lt $currentDate) { Start-Sleep $sleepDuration continue } return } throw [CustomException]::new($([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_ca_certificate_failed), $_.Exception)), ([ErrorTypes]::IsErrorFlag)) } function Wait-ForServerCertificateUpdate { <# .DESCRIPTION Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it. #> param ( [int]$sleepDuration=20, [int]$timeout=1800, #seconds in 30min [parameter(Mandatory=$true)] [DateTime]$currentDate ) $timer=[Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { Write-Status -moduleName $moduleName $($MocLocMessage.moc_waiting_for_server_certificate_update) $result = Get-MocCertificate -name $global:cloudAgentServerCertName | ConvertFrom-Json $notBeforeDate = (([System.DateTimeOffset]::FromUnixTimeSeconds($result.tags[0].NotBeforeEpoch)).DateTime).ToUniversalTime() if ($notBeforeDate -lt $currentDate) { Start-Sleep $sleepDuration continue } return } throw [CustomException]::new($([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_server_certificate_failed), $_.Exception)), ([ErrorTypes]::IsErrorFlag)) } #endregion #region Configuration Helper Functions function Initialize-HostOs { <# .DESCRIPTION Configures the host operating system .PARAMETER nodeName The node to execute on. #> param ( [String]$NodeName ) Test-ForWindowsFeatures -NodeName $NodeName $vnet = Get-VNetConfiguration -module $moduleName if ($vnet) { Test-HostNetworking -NodeName $NodeName -vswitchName $vnet.VswitchName } Initialize-Firewall -NodeName $NodeName } function Initialize-Firewall { <# .DESCRIPTION Adds Windows Firewall exceptions to allow inbound network connections to the agents. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuring_windows_firewall, $nodeName)) $firewallRules = Get-FirewallRules Invoke-Command -ComputerName $nodeName -ScriptBlock { $rules = $args[0] foreach($rule in $rules) { $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction Ignore if (!$fw) { New-NetFirewallRule -DisplayName $($rule[0]) -Protocol $($rule[1]) -LocalPort $($rule[2]) | Out-Null } } } -ArgumentList (, $firewallRules) } function Initialize-ImageDirectory { <# .DESCRIPTION Creates a directory to store images and configures the required directory permissions. .PARAMETER nodeName The node to execute on. #> param ( [String] $nodeName ) Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuring_image_directory_permissions, $nodeName)) # In order to avoid double-hop issues with network file share, we configure the # file share permission directly (instead of doing it from the remote powershell session) if ($global:config[$modulename]["imageDir"].StartsWith("\\")) { New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null $acl = get-acl $global:config[$modulename]["imageDir"] $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($AccessRule) $acl | set-acl $global:config[$modulename]["imageDir"] } else { Invoke-Command -ComputerName $nodeName -ScriptBlock { $imageDir = $args[0] New-Item -ItemType Directory -Force -Path $imageDir | Out-Null $acl = get-acl $imageDir $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($AccessRule) $acl | set-acl $imageDir } -ArgumentList $global:config[$modulename]["imageDir"] } } function Initialize-Directories { <# .DESCRIPTION Creates work directories #> Write-Status -moduleName $moduleName $($MocLocMessage.moc_configuring_directories) New-Item -ItemType Directory -Force -Path $global:config[$modulename]["workingDir"] | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["installationPackageDir"] | Out-Null New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["cloudConfigLocation"] | Out-Null if (-not (Test-MultiNodeDeployment)) { Initialize-ImageDirectory -nodeName ($env:computername) } if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_sshkey_missing, $global:config[$modulename]["sshPrivateKey"])) New-Item -ItemType Directory -Force -Path ([IO.Path]::GetDirectoryName($global:config[$modulename]["sshPrivateKey"])) | Out-Null ssh-keygen -q -f $($global:config[$modulename]["sshPrivateKey"]) -t rsa -N '""' } } function Enable-MocOfflineDownload { <# .DESCRIPTION Turns on offline downloading. Changes the -offlineDownload flag in Set-MocConfig to true. .PARAMETER stagingShare Path that the bits will be downloaded to. .PARAMETER offsiteTransferCompleted Set offsiteTransferCompleted flag to use the offline downloaded artifacts. .PARAMETER activity Activity name to use when writing progress #> param( [Parameter()] [String] $stagingShare, [Parameter()] [bool] $offsiteTransferCompleted = $false, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $activity = $MyInvocation.MyCommand.Name trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity if (-not [string]::IsNullOrWhiteSpace($stagingShare)) { if ((-not [string]::IsNullOrWhiteSpace(($global:config[$modulename]["stagingShare"]))) -and ($global:config[$modulename]["stagingShare"] -ne $stagingShare)) { $errorMsg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.Moc_stagingshare_updated, $global:config[$modulename]["stagingShare"], $stagingShare)) Write-Warning $errorMsg } $global:config[$modulename]["stagingShare"] = $stagingShare } if ([string]::IsNullOrWhiteSpace($global:config[$modulename]["stagingShare"])) { throw $($GenericLocMessage.generic_staging_share_unspecified) } Set-MocConfigValue -name "stagingShare" -value $global:config[$modulename]["stagingShare"] Set-MocConfigValue -name "offlineDownload" -value $true Set-MocConfigValue -name "offsiteTransferCompleted" -value $offsiteTransferCompleted Uninitialize-MocEnvironment -activity $activity } function Disable-MocOfflineDownload { <# .DESCRIPTION Turns off offline downloading. Changes the -offlineDownload flag in Set-MocConfig to false .PARAMETER activity Activity name to use when writing progress #> $activity = $MyInvocation.MyCommand.Name trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity Uninitialize-MocEnvironment -activity $activity Set-MocConfigValue -name "offlineDownload" -value $False Set-MocConfigValue -name "offsiteTransferCompleted" -value $False } function Get-MocRelease { <# .DESCRIPTION Download the Moc release content .PARAMETER Version Version .PARAMETER path Path that the bits will be downloaded to if provided .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter(Mandatory=$true)] [String] $version, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $activity = $MyInvocation.MyCommand.Name trap { Uninitialize-MocEnvironment -activity $activity throw $_ } try { Initialize-MocEnvironment -activity $activity } catch { $offlineDownload = $true } if ($global:config[$modulename]["offlineDownload"] -eq $true) { $offlineDownload = $true } if (-not [string]::IsNullOrWhiteSpace($path)) { if ((-not [string]::IsNullOrWhiteSpace(($global:config[$modulename]["stagingShare"]))) -and ($global:config[$modulename]["stagingShare"] -ne $path)) { $errorMsg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $AksHciLocMessage.Moc_stagingshare_updated, $global:config[$modulename]["stagingShare"], $stagingShare)) Write-Warning $errorMsg } $global:config[$modulename]["stagingShare"] = $path } if (($offlineDownload) -and [string]::IsNullOrWhiteSpace($global:config[$modulename]["stagingShare"])) { throw $($GenericLocMessage.generic_staging_share_unspecified) } Get-MocReleaseContent -version $version -activity $activity if (-not ($global:config[$modulename]["useStagingShare"]) -and -not ($offlineDownload -and $global:config[$moduleName]["offsiteTransferCompleted"])) { Test-AuthenticodeBinaries -workingDir $global:config[$modulename]["installationPackageDir"] -binaries $script:mocBinaries } Uninitialize-MocEnvironment -activity $activity } function Get-MocReleaseContent { <# .DESCRIPTION Download all required files and packages for the Moc release .PARAMETER version Version of Moc .PARAMETER wssdDir Directory for wssd installation .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $version, [Parameter()] [string] $wssdDir = $global:config[$moduleName]["installationPackageDir"], [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $wssdDir = $wssdDir -replace "\/", "\" Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_discovering_release_content) $productRelease = Get-ProductRelease -version $version -moduleName $moduleName # find requested moc release foreach($releaseStream in $productRelease.ProductStreamRefs) { foreach($subProductRelease in $releaseStream.ProductReleases) { if ($subProductRelease.ProductName -ieq $script:productName) { $versionManifestPath = [io.Path]::Combine($wssdDir, "moc-release.json") Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_download_release_content, $wssdDir)) $downloadVersion = $subProductRelease.Version if (($global:config[$moduleName]["useStagingShare"]) -or ($global:config[$modulename]["offlineDownload"] -and $global:config[$moduleName]["offsiteTransferCompleted"])) { $downloadVersion = $version } $downloadParams = Get-ReleaseDownloadParameters -name $subProductRelease.ProductStream -version $downloadVersion -destination $wssdDir -parts $global:smallBinConcurrentDownloads -moduleName $moduleName $releaseInfo = Get-DownloadSdkRelease @downloadParams if (-not ($global:config[$moduleName]["useStagingShare"]) -and -not ($global:config[$moduleName]["offlineDownload"] -and -not $global:config[$moduleName]["offsiteTransferCompleted"])) { if ($releaseInfo.Files.Count -ne 1) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_file_count, $releaseInfo.Files.Count)), ([ErrorTypes]::IsErrorFlag)) } $packagename = $releaseInfo.Files[0] -replace "\/", "\" # Temporary until cross-platform signing is available $auth = Get-AuthenticodeSignature -filepath $packagename if (($global:expectedAuthResponse.status -ne $auth.status) -or ($auth.SignatureType -ne $global:expectedAuthResponse.SignatureType)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_authenticode_failed, $moduleName, $($global:expectedAuthResponse.status), $($global:expectedAuthResponse.SignatureType), $($auth.status), $($auth.SignatureType))) } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_expanding_package, $packagename, $wssdDir)) $expandoutput = expand.exe $packagename $wssdDir -f:* Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_expand_result, $expandoutput)) } $versionJson = $subProductRelease | ConvertTo-Json -depth 100 set-content -path $versionManifestPath -value $versionJson -encoding UTF8 return } } } throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_release_content, $version)), ([ErrorTypes]::IsErrorFlag)) } function Get-NodeVirtualizationLogs { param ( [Parameter(Position=0, Mandatory=$false)] [String]$SystemDrive = $null, [Parameter(Position=1, Mandatory=$false)] [String]$LogDir = $null ) # vm logs Get-ComputerInfo >> $LogDir"\node_system_info.txt" Get-VM | Format-List * >> $LogDir"\node_hyperv_vm.txt" Get-VM | Get-VMNetworkAdapter | Format-List * >> $LogDir"\node_hyperv_vnic.txt" Get-VM | Get-VMNetworkAdapterVlan | Format-List * >> $LogDir"\node_hyperv_vnic_vlan.txt" Get-VMSwitch | Format-List * >> $LogDir"\node_hyperv_vswitch.txt" get-VM | Get-VMHardDiskDrive | Format-List * >> $LogDir"\node_hyperv_vhds.txt" $vhdFile = [io.Path]::Combine($LogDir, "node_hyperv_vhd.txt") New-Item -ItemType File -Path $vhdFile -Force | Out-Null foreach ($vm in Get-VM) { $vm.Name | Out-File -FilePath $vhdFile -Append $vm.VMId | Out-File -FilePath $vhdFile -Append $vm.VMId | Get-VHD | Format-List * | Out-File -FilePath $vhdFile -Append } Copy-Item "$SystemDrive\Windows\System32\Winevt\Logs\Application.evtx" -destination $LogDir Copy-Item "$SystemDrive\Windows\System32\Winevt\Logs\System.evtx" -destination $LogDir Copy-Item "$SystemDrive\Windows\System32\Winevt\Logs\Microsoft-Windows-Hyper-V*.evtx" -destination $LogDir } function Get-NodeHostNetworkingLogs { param ( [Parameter(Position=0, Mandatory=$false)] [String]$LogDir = $null, [Parameter(Position=1, Mandatory=$false)] [Switch]$IsForMultinode ) # single node ipconfig /all >> $LogDir"\ipconfig.txt" get-netadapter | ForEach-Object {$ifindex=$_.IfIndex; $ifName=$_.Name; netsh int ipv4 sh int $ifindex | Out-File -FilePath "$LogDir/${ifName}_int.txt" -Encoding ascii} # multinode if ($IsForMultinode.IsPresent) { route print >> $LogDir"\route.txt" arp -av >> $LogDir"\arp.txt" netstat -afio >>$LogDir"\netstat-afio.txt" netstat -b >>$LogDir"\netstat-b.txt" } } function Get-MocLogs { <# .DESCRIPTION Collects the logs from the deployment. When it's called without any of "VirtualMachineLogs", "AgentLogs", "NodeVirtualizationLogs", or "All" switches, it collects the "default logs". "default logs" includes... 1. VirtualMachineLogs 2. AgentLogs (but only two latest agent-log-* files) 3. NodeVirtualizationLogs 4. failover cluster logs .PARAMETER Path Path to store the logs .PARAMETER activity Activity name to use when writing progress .PARAMETER VirtualMachineLogs log type switch to get the logs from the vm's (LB vm if unstacked deployment and management-cluster vm) .PARAMETER AgentLogs log type switch to get logs of the wssdagent and wssdcloudagent on all nodes .PARAMETER NodeVirtualizationLogs log type switch to get Windows Event Logs and node Virtualization logs on all nodes .PARAMETER HostNetworkingLogs log type switch to get network Logs on each host .PARAMETER MocStore log type switch to get store of cloud agent and node agent. it will call "admin recovery backup" from mocctl and nodectl .PARAMETER Detail Switch to collect Detailed logs. it means following differences 1. Includes all types of logs * Mnimum required logs * NodeVirtualizationLogs * HostNetworkingLogs * "System logs" which is only available on debug mode 2. Extends timespan of Get-ClusterLogs from 1 hour to 1 day. 3. collect all agent-log-* files from cloudagent and nodeagent(s) .PARAMETER LogTimeSpan Timespan value for cluster logs #> [CmdletBinding()] param ( [Parameter()] [String] $path, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name, [Parameter(Mandatory=$false)] [Switch]$VirtualMachineLogs, [Parameter(Mandatory=$false)] [Switch]$AgentLogs, [Parameter(Mandatory=$false)] [Alias("EventLogs")] [Switch]$NodeVirtualizationLogs, [Parameter(Mandatory=$false)] [Switch]$HostNetworkingLogs, [Parameter(Mandatory=$false)] [Switch]$MocStore, [Parameter(Mandatory=$false)] [Switch]$Detail, [Parameter(Mandatory=$false)] [Timespan]$LogTimeSpan = (New-TimeSpan -Hours 1) # Default value of 1 hour ) trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity if (!$path) { $path = [io.Path]::Combine([io.Path]::GetTempPath(), [io.Path]::GetRandomFileName()) } <# $defaultSwitch is used to collect the minimum required logs when no specific log type switch is presented. Minimum require logs are followings * VirtualMachineLogs * MocStore * AgentLogs * other logs without log type switches * MocConfig.txt * moduleinfo.txt * multiple log files like "failover_cluster*.txt" * registry dump on each cluster node #> $defaultSwitch = $true if ($VirtualMachineLogs.IsPresent -or $AgentLogs.IsPresent -or $NodeVirtualizationLogs.IsPresent -or $HostNetworkingLogs.IsPresent -or $MocStore.IsPresent -or $Detail.IsPresent) { $defaultSwitch = $false } $logDir = [io.Path]::Combine($path, "moc") New-Item -ItemType Directory -Force -Path $logDir | Out-Null Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_collecting_configuration) $global:config[$moduleName] > $logDir"\MocConfig.txt" Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_collecting_module_info) Get-Command -Module Moc | Sort-Object -Property Source > $($logDir+"\moduleinfo.txt") Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_collecting_logs, $logDir)) Wait-ForCloudAgentEndpoint -timeout 5 -activity $activity if ($defaultSwitch -or $VirtualMachineLogs.IsPresent -or $Detail.IsPresent) { try { Get-GuestVirtualMachineLogs -logDirectoryName $logDir -activity $activity } catch [Exception] { Write-Status -moduleName $moduleName -msg $($GenericLocMessage.generic_exception) Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() } } if ($defaultSwitch -or $MocStore.IsPresent -or $Detail.IsPresent) { Invoke-MocBackup -path ([Io.Path]::Combine($logDir, "store")) } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_cloud_logs) $log_cloud_agent_dir = [io.Path]::Combine($logDir, "cloudlogs") if ($defaultSwitch -or $AgentLogs.IsPresent -or $Detail.IsPresent) { try { $fPath = $global:config[$moduleName]["cloudConfigLocation"]+"\log\*" if ((Test-Path $fPath)) { if (!(Test-Path $log_cloud_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_cloud_agent_dir | Out-Null } if ($Detail.IsPresent -or $AgentLogs.IsPresent) { Copy-FileLocal -Source $fPath -Destination $log_cloud_agent_dir } else { Get-ChildItem $fPath | Where-Object { $_.Name -notlike "agent-log-*" } | Copy-Item -Destination $log_cloud_agent_dir # Add only last written two agent-log files by default $cloudAgentLogs = Get-ChildItem $fPath | Where-Object { $_.Name -like "agent-log-*" } | sort LastWriteTime -Descending | select-object -First 2 Copy-Item -Path $cloudagentlogs -Destination $log_cloud_agent_dir } } } catch [Exception] { Write-Status -moduleName $moduleName -msg $($MocLocMessage.moc_failed_to_capture_cloudagent_logs) Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() if (($global:config[$moduleName]["cloudConfigLocation"] -ieq $global:defaultCloudConfigLocation) -and (Test-MultiNodeDeployment)) { Write-SubStatus -moduleName $moduleName -msg $($MocLocMessage.moc_set_cloudconfiglocation_param) } } } if (Test-MultiNodeDeployment) { if ($defaultSwitch -or $Detail.IsPresent) { if (!(Test-Path $log_cloud_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_cloud_agent_dir | Out-Null } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_cluster_logs) Get-Cluster -ErrorAction Ignore | Select-Object -Property * >> $log_cloud_agent_dir"\failover_cluster.txt" Get-ClusterGroup -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clustergroup.txt" Get-ClusterResource -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clusterresource.txt" Get-ClusterNetwork -ErrorAction Ignore | Select-Object -Property * >> $log_cloud_agent_dir"\failover_clusternetwork.txt" Get-ClusterSharedVolume -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clustercsv.txt" if ($Detail.IsPresent) { $failoverTestClusterOutput = Test-Cluster -ErrorAction Ignore if ($failoverTestClusterOutput -ne $null -and $failoverTestClusterOutput.GetType() -eq [System.IO.FileInfo]) { cp $failoverTestClusterOutput $log_cloud_agent_dir"\failover_testcluster.html" } } if ($Detail.IsPresent) { $LogTimeSpan = (New-TimeSpan -Days 1) } Get-ClusterLog -UseLocalTime -Destination $log_cloud_agent_dir -TimeSpan $LogTimeSpan.TotalMinutes | Out-Null } $nodes = Get-ClusterNode -ErrorAction Ignore foreach ($node in $nodes) { try { $nodeName = $node.Name $log_node_agent_dir = [io.Path]::Combine($logDir, $nodeName) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_gathering_logs_for_node, $nodeName)) $sourcesuffix = $($global:config[$moduleName]["nodeConfigLocation"]+"\log\*") $remotePath = "\\$nodeName\" + ($sourcesuffix).Replace(":", "$") if ($defaultSwitch -or $AgentLogs.IsPresent -or $Detail.IsPresent) { if ((Test-Path $remotePath)) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } # add directory for node configuration copy $node_agent_dir = [io.Path]::Combine($log_node_agent_dir, "configuration") New-Item -ItemType Directory -Force -Path $node_agent_dir | Out-Null if ($Detail.IsPresent -or $AgentLogs.IsPresent) { Copy-FileLocal -source $remotePath -destination $node_agent_dir -recurse } else { Get-ChildItem $remotePath | Where-Object { $_.Name -notlike "agent-log-*" } | Copy-Item -Destination $node_agent_dir -recurse # Add only last written two agent-log files by default $nodeAgentLogs = Get-ChildItem $remotePath | Where-Object { $_.Name -like "agent-log-*" } | sort LastWriteTime -Descending | select-object -First 2 Copy-Item -Path $nodeAgentLogs -Destination $node_agent_dir } } } $remote_invoke_arguments = [pscustomobject]@{ nodeAgentRegistryPath = $global:nodeAgentRegistryPath defaultSwitch = $defaultSwitch detailLogs = $Detail.IsPresent mocStore = $MocStore.IsPresent nodeVirtualizationSwitch = $NodeVirtualizationLogs.IsPresent hostNetworkSwitch = $HostNetworkingLogs.IsPresent nodeAgentPath = $global:nodeCtlFullPath nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML") } $nodeDir = Invoke-Command -ComputerName $nodeName -ScriptBlock { # functions are passed as string, it would be created as function in later code param($arguments, $getNodeVirtualizationLogs, $getNodeHostNetworkingLogs) $parent = [System.IO.Path]::GetTempPath() [string] $guidString = [System.Guid]::NewGuid() $tempDir = [io.Path]::Combine($parent, $guidString) # Registry dump if ($arguments.defaultSwitch -or $arguments.detailLogs) { if (!(Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } if ((Test-Path $arguments.nodeAgentRegistryPath)) { Get-Childitem $arguments.nodeAgentRegistryPath | foreach-object { $version= Split-Path -Path $_.Name -Leaf $registryPath = $arguments.nodeAgentRegistryPath + "\${version}\*" Get-ItemProperty $registryPath > $tempDir"\${version}_nodeagent_registry.txt" -ErrorAction Ignore } } } # node agent store if ($arguments.defaultSwitch -or $arguments.mocStore -or $arguments.detailLogs) { if (!(Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } $storeDir = [io.Path]::Combine($tempDir, "store") New-Item -ItemType Directory -Path $storeDir -Force | Out-Null $command = $arguments.nodeAgentPath $loginfile = $arguments.nodeLoginYaml $nodectlLogin = "& '$command' security login --loginpath '$loginfile' --identity" Invoke-Expression $nodectlLogin $nodectlBackup = "& '$command' admin recovery backup --path ""$storeDir"" " Invoke-Expression $nodectlBackup } if ($arguments.detailLogs) { if (!(Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } $systemLogDir = [io.Path]::Combine($tempDir, "systemLogs") New-Item -ItemType Directory -Path $systemLogDir | Out-Null try { # Expected to fail if nodeagent is not started with debug flag Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $systemLogDir/"nodeagent-routine.txt" -ErrorAction Ignore } catch {} try { # CloudAgent will only be active on one node, so we expect it to fail in most cases # Expected to fail if cloudagent is not started with debug flag Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $systemLogDir/"cloudagent-routine.txt" -ErrorAction Ignore | Out-Null } catch {} } if ($arguments.hostNetworkSwitch -or $arguments.detailLogs) { if (!(Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } $hostNetworkingLogsDir = [io.Path]::Combine($tempDir, "hostNetworkingLogs") New-Item -ItemType Directory -Path $hostNetworkingLogsDir | Out-Null # call Get-NodeHostNetworkingLogs on remote host [ScriptBlock]::Create($getNodeHostNetworkingLogs).Invoke($hostNetworkingLogsDir, $true) } if ($arguments.nodeVirtualizationSwitch -or $arguments.detailLogs) { if (!(Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } $nodeVirtualizationLogsDir = [io.Path]::Combine($tempDir, "nodeVirtualizationLogs") New-Item -ItemType Directory -Path $nodeVirtualizationLogsDir | Out-Null # call Get-NodeVirtualizationLogs on remote host [ScriptBlock]::Create($getNodeVirtualizationLogs).Invoke($env:SystemDrive, $nodeVirtualizationLogsDir) } return $tempDir } -ArgumentList $remote_invoke_arguments, ${function:Get-NodeVirtualizationLogs}, ${function:Get-NodeHostNetworkingLogs} $remotePath = "\\$nodeName\" + ($nodeDir).Replace(":", "$") if ((Test-Path $remotePath)) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } Copy-FileLocal -source $remotePath"\*" -destination $log_node_agent_dir -recurse } # cleanup temporary directory used on each node Invoke-Command -ComputerName $nodeName -ScriptBlock { if ((Test-Path $args[0])) { Remove-Item $args[0] -Recurse } } -ArgumentList $nodeDir } catch [Exception] { # An exception was thrown, write it out and exit Write-Status -moduleName $moduleName -msg $($GenericLocMessage.generic_exception) Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() } } } else { # 1Node setup Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_local_node_logs) $nodeName = $(hostname) $log_node_agent_dir = [io.Path]::Combine($logDir, $nodeName) if ($defaultSwitch -or $AgentLogs.IsPresent -or $Detail.IsPresent) { $tPath = $global:config[$moduleName]["nodeConfigLocation"]+"\log\*" if ((Test-Path $tPath)) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } # add directory for node configuration copy $node_agent_dir = [io.Path]::Combine($log_node_agent_dir, "configuration") New-Item -ItemType Directory -Force -Path $node_agent_dir | Out-Null if ($Detail.IsPresent -or $AgentLogs.IsPresent) { Copy-FileLocal -source $tPath -destination $node_agent_dir -recurse } else { # add only last two agent-log files by default Get-ChildItem $tPath | Where-Object { $_.Name -notlike "agent-log-*" } | Copy-Item -Destination $node_agent_dir -recurse # Add only last written two agent-log files by default $nodeAgentLogs = Get-ChildItem $tPath | Where-Object { $_.Name -like "agent-log-*" } | sort LastWriteTime -Descending | select-object -First 2 Copy-Item -Path $nodeAgentLogs -Destination $node_agent_dir } } } # Registry dump if ($defaultSwitch -or $Detail.IsPresent) { if ((Test-Path $global:nodeAgentRegistryPath)) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } Get-Childitem $global:nodeAgentRegistryPath | foreach-object { $version= Split-Path -Path $_.Name -Leaf Get-ItemProperty "${global:nodeAgentRegistryPath}\${version}\*" > $log_node_agent_dir"\${version}_nodeagent_registry.txt" -ErrorAction Ignore } } } if ($defaultSwitch -or $MocStore.IsPresent -or $Detail.IsPresent) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } $storeDir = [io.Path]::Combine($log_node_agent_dir, "store") New-Item -ItemType Directory -Path $storeDir | Out-Null Invoke-NodeCommand $(" admin recovery backup --path ""$storeDir"" ") } if ($Detail.IsPresent) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } $systemLogDir = [io.Path]::Combine($log_node_agent_dir, "systemLogs") New-Item -ItemType Directory -Path $systemLogDir | Out-Null try { # Expected to fail if nodeagent is not started with debug flag Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $systemLogDir/"nodeagent-routine.txt" -ErrorAction Ignore } catch {} try { # Expected to fail if cloudagent is not started with debug flag Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $systemLogDir/"cloudagent-routine.txt" -ErrorAction Ignore } catch {} } if ($HostNetworkingLogs.IsPresent -or $Detail.IsPresent) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } # Host networking logs $hostNetworkingLogsDir = [io.Path]::Combine($log_node_agent_dir, "hostNetworkingLogs") New-Item -ItemType Directory -Path $hostNetworkingLogsDir | Out-Null Get-NodeHostNetworkingLogs $hostNetworkingLogsDir } if ($NodeVirtualizationLogs.IsPresent -or $Detail.IsPresent) { if (!(Test-Path $log_node_agent_dir)) { New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null } # Event Logs $nodeVirtualizationLogsDir = [io.Path]::Combine($log_node_agent_dir, "nodeVirtualizationLogs") New-Item -ItemType Directory -Path $nodeVirtualizationLogsDir | Out-Null Get-NodeVirtualizationLogs $env:SystemDrive $virtualizationLogDir } } Uninitialize-MocEnvironment -activity $activity return $path } function Get-MocEventLog { <# .DESCRIPTION Gets all the event logs from AksHci Module #> Get-WinEvent -ProviderName $moduleName -ErrorAction Ignore } function Get-MocLatestVersion { <# .DESCRIPTION Get the current MOC release version .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $activity = $MyInvocation.MyCommand.Name Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_discovering_latest_version) $catalog = Get-LatestCatalog -moduleName $moduleName $productRelease = $catalog.ProductStreamRefs[0].ProductReleases[0] # find latest moc stack release foreach($subProductStream in $productRelease.ProductStreamRefs) { foreach($subProductRelease in $subProductStream.ProductReleases) { if ($subProductRelease.ProductName -ieq $script:productName) { return $subProductRelease.Version } } } throw [CustomException]::new($($MocLocMessage.moc_no_latest_version), ([ErrorTypes]::IsErrorFlag)) } function Get-NodeToCloudLoginYamlFilePath { <# .DESCRIPTION Get the node to cloud agent login yaml file path .PARAMETER nodeName The node's name #> param( [parameter(mandatory=$true)] [string] $nodeName ) # if the nodeToCloudLoginYAMLDir is "", then we have to first initialize the directory name # this means cx is using legacy setup and running update-mocInternal or repair-moc if ([string]::IsNullOrEmpty($global:config[$moduleName]["nodeToCloudLoginYAMLDir"])) { Set-MocConfigValue -name "nodeToCloudLoginYAMLDir" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:nodeConfigDirectoryName)) } # If the nodeToCloudLoginYAMLDir does not exist, we will create the directory and assign appropriate ACL to it if (!(Test-Path -Path $global:config[$moduleName]["nodeToCloudLoginYAMLDir"])) { Invoke-CreateNodeToCloudLoginYAMLDir -nodeConfigLocation $global:config[$moduleName]["nodeToCloudLoginYAMLDir"] } $nodeToCloudLoginYAMLFileName = $nodeName + "-" + $global:nodeToCloudloginYAMLName $nodeToCloudLoginYAMLPath = [io.PATH]::Combine($global:config[$moduleName]["nodeToCloudLoginYAMLDir"],$nodeToCloudLoginYAMLFileName) return $nodeToCloudLoginYAMLPath } function Invoke-CreateNodeToCloudLoginYAMLDir { <# .DESCRIPTION creates the upper level folder for the node to cloud login yaml and then sets the ACL on the nodeToCloudLoginYAML Directory (i.e. workingDir\wssdagent) #> param( [Parameter(Mandatory=$true)] [string] $nodeConfigLocation ) New-Item -ItemType Directory -Force -Path $nodeConfigLocation | Out-Null $acl = Get-Acl $nodeConfigLocation $acl.SetAccessRuleProtection($true, $false) $adminGroup = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminGroup, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) $acl | Set-Acl $nodeConfigLocation } function Reset-Firewall { <# .DESCRIPTION Removes Windows Firewall exceptions that were previously added for agent communication. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cleanup_windows_firewall, $nodeName)) -moduleName $moduleName $firewallRules = Get-FirewallRules Invoke-Command -ComputerName $nodeName -ScriptBlock { $rules = $args[0] foreach($rule in $rules) { $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction Ignore if ($fw) { $fw | Remove-NetFirewallRule } } } -ArgumentList (, $firewallRules) } function Reset-Host { <# .DESCRIPTION Cleans up the host by removing resources from nodeagent and cloudagent and stopping the processes. .PARAMETER removeAll Removes all artifacts creates by the deployment script. .PARAMETER skipImageDeletion This parameter skips deletion of downloaded VHD images and leaves them present in the configured image storage location. This is intended to make redeployment faster and avoid unnecessary downloads of the VHDs. .PARAMETER skipConfigDeletion This parameter skips deletion of the module configuration. .PARAMETER activity Activity name to use when writing progress #> param ( [Switch]$removeAll, [Switch]$skipImageDeletion, [Switch]$skipConfigDeletion, [String]$activity = $MyInvocation.MyCommand.Name ) Uninstall-Cloud -activity $activity if ([string]::IsNullOrWhiteSpace($global:config[$moduleName]["cloudConfigLocation"]) -or [string]::IsNullOrWhiteSpace($global:config[$moduleName]["imageDir"])) { # This case happens during Initialize Node where the cloud config location and image dir paths don't exist return } $hosts = $env:computername if (Test-MultiNodeDeployment) { $hosts = (Get-ClusterNode -ErrorAction Ignore).Name } # In order to avoid double-hop issues with network file share, we remove the # files directly (instead of doing it from the remote powershell session) if ($removeAll.IsPresent -And $global:config[$moduleName]["cloudConfigLocation"].StartsWith("\\")) { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_removing_cloudagent_directory) Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction Ignore } if ($removeAll.IsPresent -And $global:config[$moduleName]["imageDir"].StartsWith("\\")) { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_removing_image_directory) if ($skipImageDeletion.IsPresent) { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_downloaded_images_preserved) Get-ChildItem -Path $global:config[$moduleName]["imageDir"] -Directory -ErrorAction Ignore | Remove-Item -Force -Recurse -ErrorAction Ignore } else { Remove-Item -Path $global:config[$moduleName]["imageDir"] -Force -Recurse -ErrorAction Ignore } } # Before we remove workingDir, ensure that this deployment machine is not using that directory as the current working dir. Deleting it while it is the working directory # can cause unexpected behavior later on (e.g. download agent failure in redeployment) $cwd = Get-Location | Select-Object -ExpandProperty Path if ($cwd -ieq $global:config[$moduleName]["installationPackageDir"]) { Set-Location -Path .. } $inputArgs = @( $([io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], $global:yamlDirectoryName)), $global:config[$moduleName]["workingDir"], $removeAll.IsPresent, $global:config[$moduleName]["imageDir"], $skipImageDeletion.IsPresent, $global:installDirectory, $skipConfigDeletion.IsPresent, $GenericLocMessage ) $hosts | ForEach-Object { Write-StatusWithProgress -activity $activity -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_cleaning_up_files, $_)) $inputArgsIter = @($_) $inputArgsIter += $inputArgs Invoke-Command -ComputerName $_ -ScriptBlock { $hostname = $args[0] $yamlLocation = $args[1] $workingDir = $args[2] $removeAll = $args[3] $imageDir = $args[4] $skipImageDeletion = $args[5] $installDir= $args[6] $skipConfigDeletion = $args[7] $GenericLocMessage = $args[8] write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_yaml_on_hostname, $hostname)) Remove-Item -Path $yamlLocation -Force -Recurse -ErrorAction Ignore if ($removeAll) { write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_image_directory_on_hostname, $hostname)) if ($skipImageDeletion) { write-verbose $($GenericLocMessage.generic_downloaded_images_preserved) Get-ChildItem -Path $imageDir -Directory -ErrorAction Ignore | Remove-Item -Force -Recurse -ErrorAction Ignore } else { if ($imageDir) { Remove-Item -Path $imageDir -Force -Recurse -ErrorAction Ignore } } if (-not $skipConfigDeletion) { if ($workingDir) { # Keeping the working dir as part of config deletion, since config is now part of workingDir Remove-Item -Path $workingDir -Force -Recurse -ErrorAction Ignore } } write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_installation_directory_contents, $hostname)) Remove-Item -Path $installDir -Force -Recurse -ErrorAction Ignore } } -ArgumentList $inputArgsIter } if (-not $skipConfigDeletion.IsPresent) { Reset-Configuration -moduleName $moduleName } } function Uninstall-Cloud { <# .DESCRIPTION Deprovision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_deprovisioning_cloud) $deprovisionCloudResources = $true if (Test-MultiNodeDeployment) { if ($global:config[$moduleName]["clusterRoleName"]) { $cg = Get-ClusterGroup -name $global:config[$moduleName]["clusterRoleName"].ToString() -ErrorAction Ignore if ($null -eq $cg -or $cg.State -ieq "Failed") { # Skip clean-up of cloud resources if the cluster is in a failed state $deprovisionCloudResources = $false } } } if (!(Test-Path $global:cloudCtlFullPath)) { $deprovisionCloudResources = $false } # 1. Cleanup Cloud Resources if ($deprovisionCloudResources) { $mocLocation = $($global:config[$modulename]["cloudLocation"]) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_groups) try { Reset-MocGroup -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_gallery) try { Reset-MocGalleryImage -location $mocLocation } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_container) try { Reset-MocContainer -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_vippool) try { Reset-MocVipPool -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } if (Test-MultiNodeDeployment) { try { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_cluster) Reset-MocCluster -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } } else { try { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_node) Reset-MocNode -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*No connection could be made because the target machine actively refused it*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_mac_pools) try { Reset-MocMacPool -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_locations) try { Reset-MocLocation } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } } # 2. Deprovision Cloud Agents Uninstall-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity # 3. DeProvision Node Agent if (Test-MultiNodeDeployment) { # Try to get the nodes, but don't fail if the cluster is in a bad state. # Since we don't want Get-ClusterNode to swallow any error from the ForEach loop. # So, we have to keep the Get-ClusterNode separate from the pipe/foreach loop. $nodes = Get-ClusterNode -ErrorAction Ignore $nodes | ForEach-Object { Uninstall-Node -activity $activity -nodeName $_.Name } } else { Uninstall-Node -activity $activity -nodeName ($env:computername) } } function Uninstall-CloudAgent { <# .DESCRIPTION Deprovision a Cloud Agent 1. Stop the Service 2. Remove Cluster Service .PARAMETER cloudAgentName Cloud agent name .PARAMETER activity Activity name to use when updating progress #> param ( [String]$cloudAgentName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_cloudagent, $cloudAgentName)) Stop-AllProcesses -processName "wssdcloudagent" # Remove the Cluster Gen Service if (Test-MultiNodeDeployment) { if ($global:config[$moduleName]["clusterRoleName"]) { # Try to get the groups, but don't fail if the cluster is in a bad state. # Since we don't want Get-ClusterGroup to swallow any error from the ForEach loop. # So, we have to keep the Get-ClusterGroup separate from the pipe/foreach loop. $nodes = Get-ClusterGroup $global:config[$moduleName]["clusterRoleName"].ToString() -ErrorAction Ignore $nodes | ForEach-Object { Remove-ClusterGroup -InputObject $_ -Force -RemoveResources -ErrorAction Ignore } } else { # The config is lost, try to cleanup orphaned resource Get-ClusterGroup -Name "ca-*" | Remove-ClusterGroup -RemoveResources -Force -ErrorAction Ignore -Verbose } # Remove Cluster group affinity rule $useUpdateFailoverClusterCreationFlow = (Get-MocConfig)["useUpdatedFailoverClusterCreationLogic"] if ($useUpdateFailoverClusterCreationFlow) { Remove-ClusterAffinityRule -Name $global:affinityRuleName -ErrorAction Ignore } $nodes = Get-ClusterNode -ErrorAction Ignore $nodes.Name | ForEach-Object { Uninstall-CloudAgentService -nodeName $_ } } else { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { Stop-Service WssdCloudAgent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } try { write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_directory, $(hostname))) Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction Ignore write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_registry, $(hostname))) Remove-Item -Path $global:cloudAgentRegistryPath -Recurse -Force -ErrorAction Ignore } catch { Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } } } function Uninstall-NodeAgent { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_nodeagent, $nodeName)) $nodeConfigLocation = Get-ConfigurationValue -module $moduleName -name "nodeConfigLocation" if ([string]::IsNullOrWhiteSpace($nodeConfigLocation)) { # This might happen in cases where the configuration is lost. # We make best effort to cleanup the default location $nodeConfigLocation = $global:defaultNodeConfigLocation } Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeConfigLocation = $args[0] $registryLocation = $args[1] $GenericLocMessage = $args[2] Get-Service WssdAgent -ErrorAction Ignore | ForEach-Object { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'" if ($null -ne $service) { Stop-Service WssdAgent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } } write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_nodeagent_directory, $(hostname))) Remove-Item -Path $nodeConfigLocation -Force -Recurse -ErrorAction Ignore write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_nodeagent_registry, $(hostname))) Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction Ignore } -ArgumentList @($nodeConfigLocation, $global:nodeAgentRegistryPath, $GenericLocMessage) } function Uninstall-Node { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_node, $nodeName)) Reset-Firewall -NodeName $nodeName Uninstall-NodeAgent -nodeName $nodeName Uninstall-MocBinaries -nodeName $nodeName Invoke-Command -ComputerName $nodeName -ScriptBlock { $tmp = $args[0] $GenericLocMessage = $args[1] write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove, $tmp, $(hostname))) Remove-Item -Path $tmp -Force -Recurse -ErrorAction Ignore } -ArgumentList @($global:mocMetadataRoot, $GenericLocMessage) } #region moc operations #region moc macpool function Set-MocYaml { <# .DESCRIPTION Sets the content of the input to a file and return the path .PARAMETER yamlData input string #> param ( [Parameter(Mandatory=$true)] [String]$yamlData ) if ([string]::IsNullOrWhiteSpace($yamlData)) { throw [CustomException]::new($($GenericLocMessage.generic_invalid_yaml_input), ([ErrorTypes]::IsErrorFlag)) } $yamlFile = [io.Path]::GetTempFileName() + ".yaml" if ( $global:config -and $global:config[$moduleName] -and (-not [string]::IsNullOrWhiteSpace($global:config[$moduleName]["installationPackageDir"])) ) { $yamlFile = [io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], $global:yamlDirectoryName, ([io.Path]::GetRandomFileName() + ".yaml")) } Set-Content -Path $yamlFile -Value $yamlData -ErrorVariable err if (!$err -and $err.count -gt 0) { throw $err } Write-SubStatus -moduleName $moduleName -msg $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_set_yaml, $yamlFile, $yamlData)) return $yamlFile } function New-MocMacPool { <# .DESCRIPTION Adds a macpool resource to moc . .PARAMETER name name of macpool .PARAMETER location location .PARAMETER macPoolStart First MAC address in the pool .PARAMETER macPoolEnd Last MAC address in the pool .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [string]$name, [Parameter(Mandatory=$true)] [string]$macPoolStart, [Parameter(Mandatory=$true)] [string]$macPoolEnd, [Parameter(Mandatory=$true)] [string]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{"--name" = $name; "--start-mac"= $macPoolStart; "--end-mac" = $macPoolEnd; "--location" = $location } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " network macpool create" -argDictionary $argsDict } function Get-MocMacPool { <# .DESCRIPTION Removes a macpool resource from moc. .PARAMETER name Name of the macpool .PARAMETER location Location for the macpool .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network macpool list --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" network macpool show --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocMacPool { <# .DESCRIPTION Removes a macpool resource from moc. .PARAMETER name Name of the macpool .PARAMETER location Location for the macpool .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network macpool delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocMacPool { <# .DESCRIPTION Removes the macpool resource from moc. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand " network macpool list --output tsv --query ""[*].name"" --location $location" | ForEach-Object { $entityName = $_ if ($entityName -ine "Default") { if($timeoutSeconds) { Remove-MocMacPool -name $entityName -location $location -timeout $timeoutSeconds } else { Remove-MocMacPool -name $entityName -location $location } } } } #endregion moc macpool #region moc location function New-MocLocation { <# .DESCRIPTION Adds a location resource to moc . .PARAMETER name Name of the location .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_cloud_location, $name)) $argsDict = @{ "--name" = $name } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " cloud location create" -argDictionary $argsDict } function Get-MocLocation { <# .DESCRIPTION Removes a Location resource from moc. .PARAMETER name Name of the Location .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud location list" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" cloud location show --name ""$name"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocLocation { <# .DESCRIPTION Removes a Location resource from moc. .PARAMETER name Name of the Location .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud location delete --name ""$name"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocLocation { <# .DESCRIPTION Remove all moc locations. #> Invoke-MocCommand " cloud location list --output tsv --query ""[*].name""" | ForEach-Object { $entityName = $_ if ($entityName -ine "Reserved") { if($timeoutSeconds) { Remove-MocLocation -name $entityName -timeout $timeoutSeconds } else { Remove-MocLocation -name $entityName } } } } #endregion moc location #region moc group function New-MocGroup { <# .DESCRIPTION Adds a group resource to moc . .PARAMETER name Name of the group .PARAMETER location Location for the group .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--location" = $location} if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " cloud group create" -argDictionary $argsDict } function Get-MocGroup { <# .DESCRIPTION Removes a Group resource from moc. .PARAMETER name Name of the Group .PARAMETER location Location for the Group .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud group list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } else { Invoke-MocShowCommand $(" cloud group show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocGroup { <# .DESCRIPTION Removes a Group resource from moc. .PARAMETER name Name of the Group .PARAMETER location Location for the Group .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud group delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocGroup { <# .DESCRIPTION Remove all moc groups. .PARAMETER location The location to reset the entity .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud group list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocGroup -name $entityName -location $location -timeout $timeoutSeconds } else { Remove-MocGroup -name $entityName -location $location } } } } #endregion moc group #region moc container function New-MocContainer { <# .DESCRIPTION Adds a container resource to moc . .PARAMETER name Name of the container .PARAMETER path path to create the container .PARAMETER location Location of the container .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$path, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $path = Update-DirectoryPath -directoryPath $path $argsDict = @{ "--name" = $name; "--path" = $path; "--location" = $location } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " storage container create" -argDictionary $argsDict } function Get-MocContainer { <# .DESCRIPTION Removes a Container resource from moc. .PARAMETER name Name of the Container .PARAMETER location Location for the Container .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" storage container list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" storage container show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocContainer { <# .DESCRIPTION Removes a Container resource from moc. .PARAMETER name Name of the Container .PARAMETER location Location for the Container .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" storage container delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocContainer { <# .DESCRIPTION Remove all moc storage containers. .PARAMETER location The location to reset the entity .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" storage container list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if ($timeoutSeconds) { Invoke-MocCommand $(" storage container delete --name ""$entityName"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocCommand $(" storage container delete --name ""$entityName"" --location $location") } } } } #endregion moc container #region moc vippool function New-MocVipPool { <# .DESCRIPTION Adds a vip pool resource to moc . .PARAMETER name name of vippool .PARAMETER location location .PARAMETER vipPoolStart First vip address in the pool .PARAMETER vipPoolEnd Last vip address in the pool .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [string]$name, [Parameter(Mandatory=$true)] [string]$vipPoolStart, [Parameter(Mandatory=$true)] [string]$vipPoolEnd, [Parameter(Mandatory=$true)] [string]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--start-ip" = $vipPoolStart; "--end-ip" = $vipPoolEnd; "--location" = $location } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " network vippool create" -argDictionary $argsDict } function Get-MocVipPool { <# .DESCRIPTION Get a VipPool resource from moc. .PARAMETER name Name of the VipPool .PARAMETER location Location for the VipPool .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network vippool list --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" network vippool show --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocVipPool { <# .DESCRIPTION Removes a VipPool resource from moc. .PARAMETER name Name of the VipPool .PARAMETER location Location for the VipPool .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network vippool delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocVipPool { <# .DESCRIPTION Remove all moc network vippools. .PARAMETER location The location to reset the entity .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network vippool list --output tsv --query ""[*].name"" --location "+$location) | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Invoke-MocCommand $(" network vippool delete --name ""$entityName"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } } #endregion moc vippool #region moc node function New-MocNode { <# .DESCRIPTION Adds a node resource to moc . .PARAMETER name Name of the node .PARAMETER location Location for the node .PARAMETER fqdn fqdn of the node .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$fqdn, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--fqdn" = $fqdn; "--port" = 45000; "--authorizer-port" = 45001; "--location" = $location } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " cloud node create" -argDictionary $argsDict } function Get-MocNode { <# .DESCRIPTION Get a Node(s) resource from moc. .PARAMETER name Name of the Node .PARAMETER location Location for the Node .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud node list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } else { Invoke-MocShowCommand $(" cloud node show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } } function Remove-MocNode { <# .DESCRIPTION Removes a Node resource from moc. .PARAMETER name Name of the Node .PARAMETER location Location for the Node .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud node delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocNode { <# .DESCRIPTION Remove all moc nodes. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud node list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocNode -name $entityName -location $location -timeout $timeoutSeconds } else { Remove-MocNode -name $entityName -location $location } } } } #endregion moc node #region moc physical node function Confirm-NodeStatus { <# .DESCRIPTION sanity check before New-MocPhysicalNode and Remove-MocPhysicalNode .PARAMETER nodeName The name of the node in the FC .PARAMETER checkConfiguration To check if the node is configured and running #> param ( [String]$nodeName, [switch]$checkConfiguration ) # check if node in FC $node = Get-ClusterNode -ErrorAction Ignore | Where-Object { $_.Name -eq $nodeName -and $_.State -ieq 'Up'} if (($null -eq $node)) { throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_not_in_cluster, $nodeName)), ([ErrorTypes]::IsInfraErrorFlag)) } if (!($checkConfiguration.IsPresent)) { return } # check if node is configured Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath Test-NodeConfiguration -nodeName $nodeName } function New-MocPhysicalNode { <# .DESCRIPTION FRU scenario, orchestrates the onboarding of a new node .PARAMETER nodeName The name of the node in the FC .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) if (!(Test-MultiNodeDeployment)) { throw [CustomException]::new( $($MocLocMessage.moc_new_node_support), ([ErrorTypes]::IsUserErrorFlag)) } try { $nodeTmp = Get-MocNode -name $nodeName -location $global:config[$modulename]["cloudLocation"] } catch { # Ignore the exception } if ($nodeTmp) { # Already part of the deployment throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_already_present, $nodeName)), ([ErrorTypes]::IsUserErrorFlag)) } Confirm-NodeStatus -nodeName $nodeName $workingDir = $global:config[$modulename]["workingDir"] Save-ConfigurationDirectoryNode -nodeName $nodeName -moduleName $moduleName -WorkingDir $workingDir # node setup Initialize-Node -nodeName $nodeName Install-CloudAgentOnNode -nodeName $nodeName -startupType "Manual" $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML") Install-NodeAgent -nodeName $nodeName -activity $activity Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml # check if agent is installed successfully and registered with CA Confirm-NodeStatus -nodeName $nodeName -checkConfiguration # wait for all nodes to become active $mocLocation = $global:config[$modulename]["cloudLocation"] Wait-ForActiveNodes -location $mocLocation -activity $activity } function Remove-MocPhysicalNode { <# .DESCRIPTION FRU scenario, orchestrates the offboarding of a node that has failed .PARAMETER nodeName The name of the node in the FC .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) if (!(Test-MultiNodeDeployment)) { throw [CustomException]::new( $($MocLocMessage.moc_remove_node_support), ([ErrorTypes]::IsUserErrorFlag)) } if ($nodeName -ieq $(hostname)) { throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_removal_not_allowed, $(hostname))), ([ErrorTypes]::IsUserErrorFlag)) } # Check if cloudagent is running in the node $clusterName = $global:config[$moduleName]["clusterRoleName"] $resourceGroup = Get-ClusterGroup -Name $clusterName if ($nodeName -ieq $resourceGroup.OwnerNode) { # Move cloudagent to another node in the cluster Move-ClusterGroup -Name $clusterName Wait-ForCloudAgentEndpoint -timeout 60 -activity $activity $resourceGroup = Get-ClusterGroup -Name $clusterName # cloudagent is still running in the same node would mean it is a cluster comprising of single node if ($nodeName -ieq $resourceGroup.OwnerNode) { throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_removal_not_allowed, $(hostname))), ([ErrorTypes]::IsUserErrorFlag)) } } Remove-MocIdentity -name $nodeName # check if resources have migrated $mocLocation = $($global:config[$modulename]["cloudLocation"]) try { Remove-MocNode -name $nodeName -location $mocLocation } catch { Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } # Remove the cloud agent service try { Uninstall-CloudAgentService -nodeName $nodeName } catch { # swallow error if node is unreachable and uninstall is not successful Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } # No need to validate the node. # We can let the user call it to cleanup any residue try { Uninstall-Node -nodeName $nodeName -activity $activity } catch { # swallow error if node is unreachable and uninstall is not successful Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } Delete-ConfigurationDirectoryNode -nodeName $nodeName -moduleName $moduleName } #endregion moc physical node #region moc cluster function New-MocCluster { <# .DESCRIPTION Adds a cluster resource to moc . .PARAMETER name Name of the cluster .PARAMETER location Location for the cluster .PARAMETER fqdn Fqdn of the cluster .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$fqdn, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{"--name" = $name; "--fqdn" = $fqdn; "--location" = $location } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " cloud cluster create" -argDictionary $argsDict } function Get-MocCluster { <# .DESCRIPTION Get a Cluster(s) resource from moc. .PARAMETER name Name of the Cluster .PARAMETER location Location for the Cluster .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud cluster list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" cloud cluster show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } } function Remove-MocCluster { <# .DESCRIPTION Removes a Cluster resource from moc. .PARAMETER name Name of the Cluster .PARAMETER location Location for the Cluster .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud cluster delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocCluster { <# .DESCRIPTION Remove all moc clusters. .PARAMETER location The location to reset the entity .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" cloud cluster list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocCluster -name $entityName -location $location -timeout $timeoutSeconds } } } #endregion moc cluster #region moc galleryimage function New-MocGalleryImage { <# .DESCRIPTION Adds a galleryimage resource to cloudagent. .PARAMETER name Name of the galleryimage. .PARAMETER location Location for the galleryimage. .PARAMETER imagePath Hostname or IP of the galleryimage. .PARAMETER container Container that galleryimage will use .PARAMETER timeoutSeconds timeout in seconds .PARAMETER imageSourceSfs Switch indicating image source if sfs .PARAMETER release Release name of the image if imageSource is sfs .PARAMETER version Version of the image if imageSource is sfs .PARAMETER numDownloads Number of parallel downloads of the image if imageSource is sfs .PARAMETER destinationDir Destination Dir of the image if imageSource is sfs .PARAMETER hyperVGeneration hyperv generation of the image #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$container, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [Parameter(Mandatory=$false)] [System.IO.FileInfo]$imagePath, [Parameter(Mandatory=$false)] [Parameter(ParameterSetName = 'sfs')] [switch]$imageSourceSfs, [Parameter(ParameterSetName = 'sfs')] [String]$release, [Parameter(ParameterSetName = 'sfs')] [String]$version, [Parameter(ParameterSetName = 'sfs')] [ValidateRange(1,10)] [Int]$numDownloads, [Parameter(Mandatory=$false)] [ValidateSet('HyperVGeneration_V1', 'HyperVGeneration_V2')] [string]$hyperVGeneration ) $argsDict = @{"--name" = $name; "--container-name" = $container; "--location" = $location} if ($imagePath -and ($imageSourceSfs -or $PSCmdlet.ParameterSetName -ieq "sfs")) { throw $($MocLocMessage.moc_imgpath_imgsourcesfs_flags_present) } if ($PSCmdlet.ParameterSetName -ieq "sfs") { $argsDict += @{"--image-source" = "sfs"; "--release" = $release; "--version" = $version; "--num-downloads" = $numDownloads} } else { if ($imagePath) { $argsDict["--image-path"] = $imagePath } } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } if (-Not [string]::IsNullOrWhiteSpace($hyperVGeneration)) { $argsDict["--hyperv-generation"] = $hyperVGeneration } Invoke-MocCommand " compute galleryimage create" -argDictionary $argsDict } function Get-MocGalleryImage { <# .DESCRIPTION Removes a GalleryImage resource from cloudagent. .PARAMETER name Name of the GalleryImage .PARAMETER location Location for the GalleryImage .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute galleryimage list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } else { Invoke-MocShowCommand $(" compute galleryimage show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocGalleryImage { <# .DESCRIPTION Removes a GalleryImage resource from cloudagent. .PARAMETER name Name of the GalleryImage .PARAMETER location Location for the GalleryImage .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute galleryimage delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocGalleryImage { <# .DESCRIPTION Remove all compute galleryimages. .PARAMETER location The location to reset the entity .PARAMETER skipImages Optional list of galleryimages to avoid removing #> param ( [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$false)] [String[]]$skipImages ) Invoke-MocCommand $(" compute galleryimage list --output tsv --query ""[*].name"" --location $location ") | ForEach-Object { $entityName = $_ if ($entityName -ine "No GalleryImage Resources") { foreach ($image in $skipImages) { if ($entityName -ieq $image) { return } } if($timeoutSeconds) { Remove-MocGalleryImage -name $entityName -location $location -timeout $timeoutSeconds } else { Remove-MocGalleryImage -name $entityName -location $location } } } } #endregion moc galleryimage #region moc network interface function New-MocNetworkInterface { <# .DESCRIPTION Adds a vnic resource to moc . .PARAMETER name Name of the vnic .PARAMETER virtualNetworkName The vnet that the vnic should be connected to .PARAMETER group The name of the group in which the vnic resides .PARAMETER ipAddress The ipAddress of the networkInterface .PARAMETER loadBalancerBackend The loadBalancer Backend to which this network interface should be associated to .PARAMETER macAddress The macAddress of the networkInterface .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$virtualNetworkName, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [String]$ipAddress, [Parameter(Mandatory=$false)] [String]$loadBalancerBackend, [Parameter(Mandatory=$false)] [String]$macAddress, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--vnet-name" = $virtualNetworkName; "--group" = $group } if ($macAddress) { $argsDict["--mac-address"] = $macAddress } if ($ipAddress) { $argsDict["--private-ip-address"] = $ipAddress } if ($loadBalancerBackend) { $argsDict["--lb-name"] = $loadBalancerBackend } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " network vnic create" -argDictionary $argsDict } function Get-MocNetworkInterface { <# .DESCRIPTION Removes a NetworkInterface resource from moc. .PARAMETER name Name of the NetworkInterface .PARAMETER group group for the NetworkInterface .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network vnic list --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" network vnic show --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocNetworkInterface { <# .DESCRIPTION Removes a NetworkInterface resource from moc. .PARAMETER name Name of the NetworkInterface .PARAMETER group group for the NetworkInterface .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network vnic delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocNetworkInterface { <# .DESCRIPTION Remove all moc networkinterface. .PARAMETER group group for the NetworkInterface .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network vnic list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocNetworkInterface -name $entityName -group $group -timeout $timeoutSeconds } else { Remove-MocNetworkInterface -name $entityName -group $group } } } } #endregion moc network interface #region moc loadbalancer function New-MocLoadBalancer { <# .DESCRIPTION Adds a loadbalancer resource to moc . .PARAMETER name Name of the loadbalancer .PARAMETER virtualNetworkName The vnet that the loadbalancer should be connected to .PARAMETER group The name of the group in which the loadbalancer resides .PARAMETER backendPoolName The backendPoolName .PARAMETER frontendPort The frontendPort for the loadbalancer .PARAMETER backendPort The backendPort for the loadbalancer .PARAMETER protocol The protocol for the loadbalancer .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$virtualNetworkName, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$backendPoolName, [Parameter(Mandatory=$true)] [int]$frontendPort, [Parameter(Mandatory=$true)] [int]$backendPort, [Parameter(Mandatory=$true)] [ValidateSet('TCP', 'UDP')] [string]$protocol, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{"--name" = $name; "--vnet-name" = $virtualNetworkName; "--backend-pool-name" = $backendPoolName; "--frontend-port" = $frontendPort; "--backend-port" = $backendPort; "--protocol" = $protocol; "--group" = $group } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " network loadbalancer create" -argDictionary $argsDict } function Get-MocLoadBalancer { <# .DESCRIPTION Get a LoadBalancer resource from moc. .PARAMETER name Name of the LoadBalancer .PARAMETER group group for the LoadBalancer .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network loadbalancer list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" network loadbalancer show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocLoadBalancer { <# .DESCRIPTION Removes a LoadBalancer resource from moc. .PARAMETER name Name of the LoadBalancer .PARAMETER group group for the LoadBalancer .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" network loadbalancer delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocLoadBalancer { <# .DESCRIPTION Remove all moc loadbalancer. .PARAMETER group group for the LoadBalancer #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network loadbalancer list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocLoadBalancer -name $entityName -group $group -timeout $timeoutSeconds } else { Remove-MocLoadBalancer -name $entityName -group $group } } } } #endregion moc network interface #region moc virtual network function New-MocIPPool { <# .DESCRIPTION Adds a vnet resource to moc . .PARAMETER name Name of the vnet .PARAMETER type Type of the vnet .PARAMETER startIpAddress The name of the group in which the vnet resides .PARAMETER endIPAddress name of the macpool #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [ValidateSet('vm', 'vippool')] [string]$type, [Parameter(Mandatory=$false)] [string]$startIpAddress, [Parameter(Mandatory=$false)] [string]$endIPAddress ) return @" - name: $name type: $type start: $startIpAddress end: $endIPAddress "@ } function New-MocVirtualNetwork { <# .DESCRIPTION Adds a vnet resource to moc . .PARAMETER name Name of the vnet .PARAMETER type Type of the vnet .PARAMETER group The name of the group in which the vnet resides .PARAMETER macPool name of the macpool .PARAMETER ipPools list of IPPools .PARAMETER vlanID Non Zero value, if vlan is required .PARAMETER tags Additional tags for vnet creation .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$false)] [string]$type, [Parameter(Mandatory=$false)] [string]$macPool, [Parameter(Mandatory=$false)] [string[]]$ipPools, [Parameter(Mandatory=$false)] [uint32]$vlanID, [Parameter(Mandatory=$false)] [Hashtable]$tags, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--type" = $type; "--group" = $group } if ([string]::IsNullOrWhiteSpace($type)) { $type = "Transparent" if ($name -eq "Default Switch") { $type = "ICS" } $argsDict["--type"] = $type } if (-not [string]::IsNullOrWhiteSpace($macPool)) { $argsDict["--mac-pool"] = $macPool } if ($vlanID -gt 0) { $argsDict["--vlan-id"] = $vlanID } if ($ipPools -and $ipPools.Count -gt 0) { $argsDict["--ip-pools"] = $ipPools } if ($tags -and $tags.Count -gt 0) { [string[]] $tagsVal = @() foreach ($key in $tags.Keys) { $val = $tags[$key] $tagsVal += ("{0}={1}" -f $key, $val) } $argsDict["--tags"] = $tagsVal } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " network vnet create" -argDictionary $argsDict } function Get-MocVirtualNetwork { <# .DESCRIPTION Get a VirtualNetwork resource from moc. .PARAMETER name Name of the VirtualNetwork .PARAMETER group group for the VirtualNetwork #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network virtualnetwork list --group "+$group) } else { Invoke-MocShowCommand $(" network virtualnetwork show --name ""$name"" --group "+$group) } } function Remove-MocVirtualNetwork { <# .DESCRIPTION Removes a VirtualNetwork resource from moc. .PARAMETER name Name of the VirtualNetwork .PARAMETER group group for the VirtualNetwork #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network virtualnetwork delete --name ""$name"" --group $group") } function Reset-MocVirtualNetwork { <# .DESCRIPTION Remove all moc virtualnetwork. .PARAMETER group group for the virtualnetwork #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network virtualnetwork list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVirtualNetwork -name $entityName -group $group } } } #endregion moc network interface #region moc virtualmachine function New-MocVirtualMachine { <# .DESCRIPTION Create a moc virtualmachine .PARAMETER name Name of the virtualmachine .PARAMETER group The name of the group in which the virtualmachine resides .PARAMETER galleryImageName The name of the galleryImageName to use for image .PARAMETER diskName The name of the VHD to use as OsDisk for the VM, if galleryImageName is empty .PARAMETER osType The type of operating system .PARAMETER networkInterfaces List of networkinterfaces to connect the virtualmachine .PARAMETER virtualHardDisks List of virtualHardDisks to connect the virtualmachine .PARAMETER storageContainerName The name of the storageContainerName to store the virtualmachine data .PARAMETER disableHA switch to disable high availability .PARAMETER computerName name of the computer to set on the VM. .PARAMETER adminUser administrator username .PARAMETER adminPass administrator Password in Clear text .PARAMETER bootstrapType bootstrap type [CloudInit/WindowsAnswerFile] .PARAMETER tags hashtable of tags .PARAMETER timeoutSeconds timeout in seconds .PARAMETER disableSecureBoot switch to disable secure boot #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$false, ParameterSetName = 'UsingGalleryImageName')] [string]$galleryImageName, [Parameter(Mandatory=$false, ParameterSetName = 'UsingDiskName')] [string]$diskName, [Parameter(Mandatory=$true)] [ValidateSet('Windows', 'Linux')] [string]$osType, [Parameter(Mandatory=$false)] [string[]]$networkInterfaces, [Parameter(Mandatory=$false)] [string[]]$virtualHardDisks, [Parameter(Mandatory=$false)] [string]$storageContainerName = "", [Parameter(Mandatory=$false)] [switch]$disableHA, [Parameter(Mandatory=$true)] [string]$computerName, [Parameter(Mandatory=$true)] [string]$adminUser, [Parameter(Mandatory=$true)] [string]$adminPass, [Parameter(Mandatory=$true)] [ValidateSet('CloudInit', 'WindowsAnswerFiles')] [string]$bootstrapType, [Parameter(Mandatory=$false)] [Hashtable]$tags, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [Parameter(Mandatory=$false)] [switch]$disableSecureBoot ) [string[]] $boolFlags = @() # Construct OsProfile related Info switch ($osType) { 'Windows' { $boolFlags += "--enable-auto-update" } 'Linux' { $boolFlags += "--disable-password-auth" } } $argsDict = @{ "--name" = $name; "--computer-name" = $computerName; "--admin-username" = $adminUser; "--admin-password" = $adminPass; "--os-bootstrap-engine" = $bootstrapType; "--os-type" = $osType; "--group" = $group } if (-not [string]::IsNullOrEmpty($galleryImageName)) { $argsDict["--image-name"] = $galleryImageName } elseif (-not [string]::IsNullOrEmpty($diskName)) { $argsDict["--os-disk-uri"] = $diskName } if ($networkInterfaces) { $argsDict["--network-interfaces"] = $networkInterfaces } if ($virtualHardDisks -and $virtualHardDisks.Count -gt 0) { $argsDict["--data-disk-uris"] = $virtualHardDisks } if (-not [string]::IsNullOrEmpty($storageContainerName)) { $argsDict["--storage-container-name"] = $storageContainerName } if ($disableHA.IsPresent) { $boolFlags += "--disable-high-availability" } if ($disableSecureBoot.IsPresent) { $boolFlags += "--disable-secure-boot" } if ($tags -and $tags.Count -gt 0) { [string[]] $tagsVal = @() foreach ($key in $tags.Keys) { $val = $tags[$key] $tagsVal += ("{0}={1}" -f $key, $val) } $argsDict["--tags"] = $tagsVal } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " compute vm create" -argDictionary $argsDict -boolFlags $boolFlags } function Get-MocVirtualMachine { <# .DESCRIPTION Get VirtualMachine(s) from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute vm list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" compute vm show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Start-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm start --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Stop-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm stop --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Restart-MocVirtualMachine { <# .DESCRIPTION Restart a VirtualMachine .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm restart --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Get-MocVirtualMachineSizes { <# .DESCRIPTION List sizes for a moc VirtualMachine #> Invoke-MocCommand $(" compute vm list-sizes" ) } function Resize-MocVirtualMachine { <# .DESCRIPTION Resize a VirtualMachine .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER newSize newSize for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$newSize, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size $newSize" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Resize-MocVirtualMachineCustom { <# .DESCRIPTION Resize a VirtualMachine with Custom Size .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER cpuCount new cpuCount for the VirtualMachine .PARAMETER memoryMB new memoryMB for the VirtualMachine .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int]$cpuCount, [Parameter(Mandatory=$true)] [int]$memoryMB, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size Custom --cpucount $cpuCount --memorymb $memoryMB" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Connect-MocVirtualHardDisk { <# .DESCRIPTION Connect a VirtualMachine to VirtualHardDisk .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm disk attach --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Disconnect-MocVirtualHardDisk { <# .DESCRIPTION Disconnect a VirtualMachine from VirtualHardDisk .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm disk detach --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Connect-MocNetworkInterface { <# .DESCRIPTION Connect a VirtualMachine to NetworkInterface .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm nic add --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Disconnect-MocNetworkInterface { <# .DESCRIPTION Disconnect a VirtualMachine from NetworkInterface .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vm nic remove --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocVirtualMachine { <# .DESCRIPTION Remove all moc virtual machines. .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVirtualMachine -name $entityName -group $group } } } #endregion moc virtualmachine #region moc virtualmachine function New-MocVMSS { <# .DESCRIPTION Adds a vmss resource to moc . .PARAMETER name Name of the vmss .PARAMETER galleryImageName name of the image to use for vmss .PARAMETER replicaCount replica count of vmss .PARAMETER osType OperatingSystem type of the vmss .PARAMETER group The name of the group in which the vmss resides .PARAMETER virtualNetworkName The name of the virtualNetworkName to connect the vmss to .PARAMETER storageContainerName The name of the storageContainerName to store the vmss data .PARAMETER disableHA switch to disable high availability .PARAMETER computerNamePrefix name of the computer to set on the VM. .PARAMETER adminUser administrator username .PARAMETER adminPass administrator Password in Clear text .PARAMETER sshPublicKey sshPublicKey to use to set certificate based ssh auth .PARAMETER bootstrapType bootstrap type [CloudInit/WindowsAnswerFile] .PARAMETER tags hashtable of tags #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$galleryImageName, [Parameter(Mandatory=$true)] [int]$replicaCount, [Parameter(Mandatory=$true)] [ValidateSet('Windows', 'Linux')] [string]$osType, [Parameter(Mandatory=$false)] [string]$virtualNetworkName, [Parameter(Mandatory=$false)] [string]$storageContainerName = "", [Parameter(Mandatory=$false)] [switch]$disableHA, [Parameter(Mandatory=$true)] [string]$computerNamePrefix, [Parameter(Mandatory=$true)] [string]$adminUser, [Parameter(Mandatory=$true)] [string]$adminPass, [Parameter(Mandatory=$false)] [string]$sshPublicKey, [Parameter(Mandatory=$true)] [ValidateSet('CloudInit', 'WindowsAnswerFiles')] [string]$bootstrapType, [Parameter(Mandatory=$false)] [Hashtable]$tags, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--replicas" = $replicaCount; "--image-name" = $galleryImageName; "--os-type" = $osType; "--computer-name-prefix" = $computerNamePrefix; "--admin-username" = $adminUser; "--admin-password" = $adminPass; "--bootstrap-type" = $bootstrapType; "--group" = $group } if (-Not [string]::IsNullOrEmpty($sshPublicKey)) { $argsDict["--ssh-public-key"] = $sshPublicKey } if ($virtualNetworkName) { $argsDict["--vnet-name"] = $virtualNetworkName } if (-not [string]::IsNullOrEmpty($storageContainerName)) { $argsDict["--storage-container-name"] = $storageContainerName } [string[]] $boolFlags = @() if ($disableHA.IsPresent) { $boolFlags += "--disable-high-availability" } if ($tags -and $tags.Count -gt 0) { [string[]] $tagsVal = @() foreach ($key in $tags.Keys) { $val = $tags[$key] $tagsVal += ("{0}={1}" -f $key, $val) } $argsDict["--tags"] = $tagsVal } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " compute vmss create" -argDictionary $argsDict -boolFlags $boolFlags } function Get-MocVMSS { <# .DESCRIPTION Get a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute vmss list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" compute vmss show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Set-MocVMSS { <# .DESCRIPTION Get a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS .PARAMETER replicaCount replicaCount for the VMSS .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [int]$replicaCount, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vmss scale --count $replicaCount --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } function Remove-MocVMSS { <# .DESCRIPTION Removes a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vmss delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocVMSS { <# .DESCRIPTION Remove all moc vmss .PARAMETER group group for the VMSS .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" compute vmss list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocVMSS -name $entityName -group $group -timeout $timeoutSeconds } else { Remove-MocVMSS -name $entityName -group $group } } } } #endregion moc virtualmachine #region moc virtual harddisk function New-MocVirtualHardDisk { <# .DESCRIPTION Adds a virtualharddisk resource to moc . .PARAMETER name Name of the virtualharddisk .PARAMETER sizeBytes size in bytes for the virtualharddisk .PARAMETER group The name of the group in which the virtualharddisk resides .PARAMETER dynamic flag to specify if we want * dynamic expanding disk -> overprovisioning * static disks .PARAMETER containerName name of the storage container to use .PARAMETER timeoutSeconds timeout in seconds .PARAMETER hyperVGeneration hyperv generation of the disk .PARAMETER diskFileFormat hyperv generation of the disk #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int64]$sizeBytes, [Parameter(Mandatory=$false)] [bool]$dynamic = $true, [Parameter(Mandatory=$false)] [string]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [Parameter(Mandatory=$false)] [ValidateSet('HyperVGeneration_V1', 'HyperVGeneration_V2')] [string]$hyperVGeneration, [Parameter(Mandatory=$false)] [ValidateSet('VHD', 'VHDX')] [string]$diskFileFormat ) [string[]] $boolFlags = @() $argsDict = @{ "--name" = $name; "--disk-size-bytes" = $sizeBytes; "--group" = $group } if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $argsDict["--container"] = $containerName } if (-Not [string]::IsNullOrWhiteSpace($hyperVGeneration)) { $argsDict["--hyperv-generation"] = $hyperVGeneration } if (-Not [string]::IsNullOrWhiteSpace($diskFileFormat)) { $argsDict["--disk-file-format"] = $diskFileFormat } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } if ($dynamic) { $boolFlags += "--dynamic" } Invoke-MocCommand " storage vhd create" -argDictionary $argsDict -boolFlags $boolFlags } function Import-MocVirtualHardDisk { <# .DESCRIPTION Impoirts an existing virtualharddisk resource to moc . .PARAMETER name Name of the virtualharddisk .PARAMETER sourcePath sourcePath of the virtualharddisk to import .PARAMETER group The name of the group in which the virtualharddisk resides .PARAMETER containerName name of the storage container to use .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$sourcePath, [Parameter(Mandatory=$false)] [string]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $yaml = @" name: $name virtualharddiskproperties: source: $sourcePath "@ $yamlFile = Set-MocYaml -yamlData $yaml $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand $(" storage vhd create --config ""$yamlFile"" --group $group $cmd" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } function Get-MocVirtualHardDisk { <# .DESCRIPTION Get a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk .PARAMETER containerName name of the storage container to use .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [String]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" storage vhd list --group "+$group + $cmd + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" storage vhd show --name ""$name"" --group "+$group + $cmd + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Resize-MocVirtualHardDisk { <# .DESCRIPTION Resize a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk .PARAMETER newSizeBytes newSizeBytes for the VirtualHardDisk .PARAMETER containerName name of the storage container to use .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int64]$newSizeBytes, [Parameter(Mandatory=$true)] [string]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $cmdDict = @{ "--name" = $name; "--size-bytes" = $newSizeBytes; "--group" = $group } if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmdDict["--container"] = $containerName } if($timeoutSeconds) { $cmdDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand $( "storage vhd resize") -argDictionary $cmdDict } function Remove-MocVirtualHardDisk { <# .DESCRIPTION Removes a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" storage vhd delete --name ""$name"" --group $group --container $containerName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocVirtualHardDisk { <# .DESCRIPTION Remove all moc virtual harddisks. .PARAMETER group group for the VirtualHardDisk .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$containerName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand $(" storage vhd list --output tsv --query ""[*].name"" --group $group $cmd") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocVirtualHardDisk -name $entityName -group $group -containerName $containerName -timeout $timeoutSeconds } else { Remove-MocVirtualHardDisk -name $entityName -group $group -containerName $containerName } } } } #endregion moc virtual harddisk #region moc certificate function Update-MocCertificate { <# .DESCRIPTION Updates a certificate resource to moc . .PARAMETER name Name of the certificate. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) [string[]] $boolFlags = @() $argsDict = @{ "--name" = $name;} if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } return Invoke-MocCommand " security certificate update" -argDictionary $argsDict } function Get-MocCertificate { <# .DESCRIPTION Updates a certificate resource to moc . .PARAMETER name Name of the certificate. .PARAMETER fqdn Hostname or IP of the cloud agent. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [Parameter(Mandatory=$false)] [String]$format = "json", [Parameter(Mandatory=$false)] [int32]$expiryDays = 90 ) [string[]] $boolFlags = @() $argsDict = @{ "--name" = $name; "--output" = $format; "--expirydays" = $expiryDays } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } return Invoke-MocCommand " security certificate list " -argDictionary $argsDict } #endregion moc certificate #region moc identity function New-MocIdentity { <# .DESCRIPTION Adds a identity resource to moc . .PARAMETER name Name of the identity identity. .PARAMETER validityDays Time before expiry of token for identity in days. .PARAMETER validitySeconds Time before expiry of token for identity in seconds .PARAMETER location Location for the identity. .PARAMETER fqdn Hostname or IP of the cloud agent. .PARAMETER clienttype Type of client for which identity is created. .PARAMETER port Port that cloud agent is listening on. .PARAMETER authport Authorizer Port that cloud agent is listening on. .PARAMETER encode output to be base64 encoded. .PARAMETER outFile write the identity to the given file name. .PARAMETER timeoutSeconds timeout in seconds .PARAMETER enableTokenAutoRotate An indicate whether enable cloud agent token auto rotate feature. Default is disabled .PARAMETER loginFilepath The login yaml filepath for nodeToCloudAgent.yaml #> param ( [Parameter(Mandatory=$true)] [String]$name, [int64]$validityDays, [String]$fqdn = "localhost", [Parameter(Mandatory=$true)] [String]$location, [string]$clienttype = "Node", [int]$port = $global:defaultCloudAgentPort, [int]$authport = $global:defaultCloudAuthorizerPort, [switch]$encode, [string]$outFile = "", [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [int64]$validitySeconds, [switch]$enableTokenAutoRotate, [string] $loginFilePath ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_identity, $name)) [string[]] $boolFlags = @() $argsDict = @{ "--name" = $name; "--fqdn" = $fqdn; "--client-type" = $clienttype; "--port" = $port; "--auth-port" = $authport; "--location" = $location; "--outfile" = $outFile} if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } if ($validitySeconds) { $argsDict["--validity-seconds"] = $validitySeconds } if ($validityDays) { $argsDict["--validity-days"] = $validityDays } if ($loginFilePath) { $argsDict["--login-path"] = $loginFilePath } $boolFlags += "--encode=$encode" if ($enableTokenAutoRotate.IsPresent) { $boolFlags += "--enable-autorotate=$enableTokenAutoRotate" } return Invoke-MocCommand " security identity create" -argDictionary $argsDict -boolFlags $boolFlags } function Update-MocIdentity { <# .DESCRIPTION Updates a identity resource to moc . .PARAMETER name Name of the identity identity. .PARAMETER validityDays Time before expiry of token for identity in days. .PARAMETER validitySeconds Time before expiry of token for identity in seconds .PARAMETER location Location for the identity. .PARAMETER fqdn Hostname or IP of the cloud agent. .PARAMETER clienttype Type of client for which identity is created. .PARAMETER port Port that cloud agent is listening on. .PARAMETER authport Authorizer Port that cloud agent is listening on. .PARAMETER encode output to be base64 encoded. .PARAMETER outFile write the identity to the given file name. .PARAMETER timeoutSeconds timeout in seconds .PARAMETER enableTokenAutoRotate An indicate whether enable cloud agent token auto rotate feature. Default is disabled .PARAMETER loginFilepath The login yaml filepath for nodeToCloudAgent.yaml #> param ( [Parameter(Mandatory=$true)] [String]$name, [int64]$validityDays, [String]$fqdn = "localhost", [Parameter(Mandatory=$true)] [String]$location, [string]$clienttype = "Node", [int]$port = $global:defaultCloudAgentPort, [int]$authport = $global:defaultCloudAuthorizerPort, [switch]$encode, [string]$outFile = "", [Parameter(Mandatory=$false)] [int32]$timeoutSeconds, [int64]$validitySeconds, [switch]$enableTokenAutoRotate, [string] $loginFilePath ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_identity, $name)) [string[]] $boolFlags = @() $argsDict = @{ "--name" = $name; "--fqdn" = $fqdn; "--client-type" = $clienttype; "--port" = $port; "--auth-port" = $authport; "--location" = $location; "--outfile" = $outFile} if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } if ($validitySeconds) { $argsDict["--validitySeconds"] = $validitySeconds } if ($validityDays) { $argsDict["--validity-days"] = $validityDays } if ($loginFilePath) { $argsDict["--login-path"] = $loginFilePath } $boolFlags += "--encode=$encode" if ($enableTokenAutoRotate.IsPresent) { $boolFlags += "--enable-autorotate=$enableTokenAutoRotate" } return Invoke-MocCommand " security identity update" -argDictionary $argsDict -boolFlags $boolFlags } function Remove-MocIdentity { <# .DESCRIPTION Deletes a identity resource from moc . .PARAMETER name Name of the identity identity. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Write-Status -moduleName $moduleName $($MocLocMessage.moc_deleting_identity) return Invoke-MocCommand $(" security identity delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Get-MocIdentity { <# .DESCRIPTION Get a identity resource from moc . .PARAMETER name Name of the identity identity. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [String]$format = "json", [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--output" = $format } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } return Invoke-MocCommand " security identity list " -argDictionary $argsDict } function Invoke-MocIdentityRotate { <# .DESCRIPTION Adds a identity resource to moc . .PARAMETER name Name of the identity identity. .PARAMETER encode output to be base64 encoded. #> param ( [Parameter(Mandatory=$true)] [String]$name, [switch]$encode ) Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_rotating_identity_tokens_for, $name)) return Invoke-MocCommand " security identity rotate --name $name --encode=$encode" } function New-MocAdminIdentity { <# .DESCRIPTION Creates a new Admin identity resource in moc . .PARAMETER name Name of the identity identity. .PARAMETER validityDays Time before expiry of token for identity in days. #> param ( [String]$name = "Appliance", [int64]$validityDays = 90 ) $mocConfig = Get-MocConfig $adminIdentity = New-MocIdentity -name $name -validityDays $validityDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "LocationContributor" | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "IdentityContributor" | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "RoleContributor" | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "CertificateReader" | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VipPoolReader" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "GroupContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "KeyVaultContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VirtualNetworkContributor" $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "LBContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "NetworkInterfaceContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VMContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "SecretContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "GalleryImageContributor" -location $mocConfig.cloudLocation | Out-Null New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "StorageContainerContributor" -location $mocConfig.cloudLocation | Out-Null return $adminIdentity } #endregion moc identity #region moc role assignment function New-MocRoleAssignment { <# .DESCRIPTION Assigns a moc role to a moc identity .PARAMETER identityName Name of the identity. .PARAMETER roleName Name of the role to assign. .PARAMETER location Location to which the role is scoped to. .PARAMETER group Group to which the role is scoped to. .PARAMETER providerType ProviderType to which the role is scoped to. .PARAMETER resourceName Name of resource to which the role is scoped to. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Write-Status -moduleName $moduleName $($MocLocMessage.moc_creating_role_assignment) $argsDict = @{ "--identity" = $identityName; "--role" = $roleName } if (-not [string]::IsNullOrWhiteSpace($location)) { $argsDict["--location"] = $location } if (-not [string]::IsNullOrWhiteSpace($group)) { $argsDict["--group"] = $group } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $argsDict["--provider-type"] = $providerType } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $argsDict["--resource"] = $resourceName } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } return Invoke-MocCommand " security roleassignment create" -argDictionary $argsDict } function New-MocRoleAssignmentWhenAvailable { <# .DESCRIPTION Waits for a moc role to be available, then assigns it to a moc identity .PARAMETER identityName Name of the identity. .PARAMETER roleName Name of the role to assign. .PARAMETER location Location to which the role is scoped to. .PARAMETER group Group to which the role is scoped to. .PARAMETER providerType ProviderType to which the role is scoped to. .PARAMETER resourceName Name of resource to which the role is scoped to. #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName ) Write-Status -moduleName $moduleName $($MocLocMessage.moc_creating_role_assignment) $isRoleAvailable = Wait-ForMocRole -roleName $roleName if (-not $isRoleAvailable) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_role_unavailable, $roleName)), ([ErrorTypes]::IsErrorFlag)) } return New-MocRoleAssignment -identityName $identityName -roleName $roleName -location $location -group $group -providerType $providerType -resourceName $resourceName } function Get-MocRoleAssignment { <# .DESCRIPTION Get a RoleAssignment resource from moc. .PARAMETER name Name of the RoleAssignment. .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [String]$identityName, [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { $cmd = "" if (-not [string]::IsNullOrWhiteSpace($identityName)) { $cmd += " --identity $identityName" } if (-not [string]::IsNullOrWhiteSpace($roleName)) { $cmd += " --role $roleName" } if (-not [string]::IsNullOrWhiteSpace($location)) { $cmd += " --location $location" } if (-not [string]::IsNullOrWhiteSpace($group)) { $cmd += " --group $group" } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $cmd += " --provider-type $providerType" } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $cmd += " --resource $resourceName" } Invoke-MocListCommand $(" security roleassignment list $cmd" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" security roleassignment show --name ""$name""" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocRoleAssignmentByName { <# .DESCRIPTION Removes a role assignment to an identity from moc using a generated role assignment name. .PARAMETER name Name of the role assignment. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) return Invoke-MocCommand $(" security roleassignment delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Remove-MocRoleAssignment { <# .DESCRIPTION Removes a role assignment to an identity from moc. .PARAMETER name Name of the role assignment. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $cmdDict = @{ "--identity" = $identityName; "--role" = $roleName } if (-not [string]::IsNullOrWhiteSpace($location)) { $cmdDict[" --location"] = $location } if (-not [string]::IsNullOrWhiteSpace($group)) { $cmdDict["--group"] = $group } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $cmdDict["--provider-type"] = $providerType } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $cmdDict["--resource"] = $resourceName } if($timeoutSeconds) { $cmdDict['--timeout'] = $timeoutSeconds } return Invoke-MocCommand " security roleassignment delete" -argDictionary $cmdDict } #endregion moc role assignment #region moc earlyaccess preview function Enable-MocPreview { <# .SYNOPSIS Enable AKSHCI catalog and ring configuration to expose early access preview builds. .DESCRIPTION Enable AKSHCI catalog and ring configuration to expose early access preview builds. .PARAMETER activity Activity name to use when updating progress .PARAMETER catalog Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter. .PARAMETER ring Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter. #> [CmdletBinding()] param ( [parameter(DontShow)] [String] $activity = $MyInvocation.MyCommand.Name, [parameter()] [String] $catalog, [parameter()] [String] $ring ) #Set MocConfig for early access preview Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_enabling_preview, $moduleName)) Set-MocConfigValue -name "catalog" -value $catalog Set-MocConfigValue -name "ring" -value $ring Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuration_for_module_updated, $moduleName)) Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } function Disable-MocPreview { <# .SYNOPSIS Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build. .DESCRIPTION Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build. .PARAMETER activity Activity name to use when updating progress. .PARAMETER catalog Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter. .PARAMETER ring Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter. #> [CmdletBinding()] param ( [parameter(DontShow)] [String] $activity = $MyInvocation.MyCommand.Name, [parameter()] [String] $catalog, [parameter()] [String] $ring ) #Revert MocConfig from early access preview to stable build Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_disabling_preview, $moduleName)) Set-MocConfigValue -name "catalog" -value $catalog Set-MocConfigValue -name "ring" -value $ring Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuration_for_module_updated, $moduleName)) Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done) } #end region earlyaccess preview #region moc role function New-MocRole { <# .DESCRIPTION Add new role resource to moc. .PARAMETER name Name of the role. .PARAMETER actionOperations List of actions to be allowed in role. .PARAMETER actionProviders List of providers to which the corresponding actions are applied to. .PARAMETER notActionOperations List of actions to be excluded in role. .PARAMETER notActionProviders List of providers to which the corresponding excluded actions are applied to. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string[]]$actionOperations, [Parameter(Mandatory=$true)] [string[]]$actionProviders, [Parameter(Mandatory=$false)] [string[]]$notActionOperations, [Parameter(Mandatory=$false)] [string[]]$notActionProviders, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Write-Status -moduleName $moduleName $($MocLocMessage.moc_creating_role) if ($actionOperations.Count -eq 0) { throw [CustomException]::new($($MocLocMessage.moc_no_action_operation), ([ErrorTypes]::IsErrorFlag)) } if ($actionOperations.Length -ne $actionProviders.Length) { throw [CustomException]::new($($MocLocMessage.moc_unequal_action_operation), ([ErrorTypes]::IsErrorFlag)) } if ($notActionOperations -and $notActionOperations.Length -ne $notActionProviders.Length) { throw [CustomException]::new($($MocLocMessage.moc_unequal_not_action_operations), ([ErrorTypes]::IsErrorFlag)) } $argsDict = @{ "--name" = $name } if ($actionOperations -and $actionOperations.Count -gt 0) { $argsDict["--actions"] = $actionOperations $argsDict["--action-providers"] = $actionProviders } if ($notActionOperations -and $notActionOperations.Count -gt 0) { $argsDict["--not-actions"] = $notActionOperations $argsDict["--not-action-providers"] = $notActionProviders } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " security role create" -argDictionary $argsDict } function Get-MocRole { <# .DESCRIPTION Get a Role resource from moc. .PARAMETER name Name of the Role. .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security role list" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" security role show --name ""$name""" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocRole { <# .DESCRIPTION Removes a role resource from moc. .PARAMETER name Name of the role. .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) return Invoke-MocCommand $(" security role delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } #endregion moc role #region moc login function Invoke-NodeLogin { <# .DESCRIPTION Provisions the Script to have access to node ctl .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName, [Parameter(Mandatory=$true)] [String]$loginYaml ) Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeloginYAMLLocation = $args[0] $nodectlPath = $args[1] Invoke-Expression "& '$nodectlPath' security login --loginpath ""$nodeloginYAMLLocation"" --identity" } -ArgumentList @($loginYaml, $global:nodeCtlFullPath) } #endregion moc login #region moc key vault function New-MocKeyVault { <# .DESCRIPTION Adds a keyvault resource to moc . .PARAMETER name Name of the keyvault .PARAMETER group The name of the group in which the keyvault resides .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--group" = $group } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " security keyvault create" -argDictionary $argsDict } function Get-MocKeyVault { <# .DESCRIPTION Get a KeyVault resource from moc. .PARAMETER name Name of the KeyVault .PARAMETER group group for the KeyVault .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault list --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" security keyvault show --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocKeyVault { <# .DESCRIPTION Removes a KeyVault resource from moc. .PARAMETER name Name of the KeyVault .PARAMETER group group for the KeyVault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" security keyvault delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocKeyVault { <# .DESCRIPTION Remove all moc keyvault. .PARAMETER group group for the NetworkInterface .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" security keyvault list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocKeyVault -name $entityName -group $group -timeout $timeoutSeconds } else { Remove-MocKeyVault -name $entityName -group $group } } } } #endregion moc key vault #region moc secret function New-MocSecret { <# .DESCRIPTION Adds a secret resource to moc . .PARAMETER name Name of the secret .PARAMETER group The name of the group in which the secret resides .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$value, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--value" = $value; "--vault-name" = $keyvaultName; "--group" = $group} if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " security keyvault secret set" -argDictionary $argsDict } function Get-MocSecret { <# .DESCRIPTION Get a Secret resource from moc. .PARAMETER name Name of the Secret .PARAMETER group group for the Secret .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault secret list --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" security keyvault secret show --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Remove-MocSecret { <# .DESCRIPTION Removes a Secret resource from moc. .PARAMETER name Name of the Secret .PARAMETER group group for the Secret .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" security keyvault secret delete --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Reset-MocSecret { <# .DESCRIPTION Remove all moc secret. .PARAMETER group group for the NetworkInterface .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" security keyvault secret list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if($timeoutSeconds) { Remove-MocSecret -name $entityName -group $group -keyvaultName $keyvaultName -timeout $timeoutSeconds } else { Remove-MocSecret -name $entityName -group $group -keyvaultName $keyvaultName } } } } #endregion moc secret #region moc key function New-MocKey { <# .DESCRIPTION Adds a key resource to moc . .PARAMETER name Name of the key .PARAMETER group The name of the group in which the key resides .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [ValidateSet('RSA', 'AES')] [string]$type, [Parameter(Mandatory=$true)] [ValidateSet(256, 2048)] [string]$size, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--key-size" = $size; "--key-type" = $type; "--group" = $group } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " security keyvault key create" -argDictionary $argsDict -boolFlags $boolFlags } function Get-MocKey { <# .DESCRIPTION Get a Key resource from moc. .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault key list --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } else { Invoke-MocShowCommand $(" security keyvault key show --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } } function Import-MocKey { <# .DESCRIPTION Imports a key resource to moc . .PARAMETER name Name of the key .PARAMETER group The name of the group in which the key resides .PARAMETER importKeyFile The path of the import key file previously exported from moc .PARAMETER type The type of key. Can be either RSA or AES .PARAMETER size The size of key. Can be either 256 for AES or 2048 for RSA .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$importKeyFile, [Parameter(Mandatory=$true)] [ValidateSet('RSA', 'AES')] [string]$type, [Parameter(Mandatory=$true)] [ValidateSet(256, 2048)] [string]$size, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--key-size" = $size; "--key-type" = $type; "--group" = $group; "--key-file-path" = $importKeyFile } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand " security keyvault key import" -argDictionary $argsDict -boolFlags $boolFlags } function Export-MocKey { <# .DESCRIPTION Exports a key resource from moc . .PARAMETER name Name of the key .PARAMETER group The name of the group in which the key resides .PARAMETER wrappingKeyName The name of the key that will be used to wrap the key .PARAMETER wrappingKeyFile The key file path of the key that will be used to wrap the key .PARAMETER outFile The file path where to store the export key .PARAMETER algorithm The wrapping key algorithm .PARAMETER size The size of key. Can be either 256 for AES or 2048 for RSA .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$false)] [string]$wrappingKeyName, [Parameter(Mandatory=$false)] [string]$wrappingPubKeyFile, [Parameter(Mandatory=$false)] [ValidateSet('CKM_RSA_AES_KEY_WRAP')] [string]$algorithm = 'CKM_RSA_AES_KEY_WRAP', [Parameter(Mandatory=$true)] [ValidateSet(256, 2048)] [string]$size, [Parameter(Mandatory=$false)] [string]$outFile, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--algorithm" = $algorithm; "--group" = $group; "--out-file" = $outFile; "--key-size" = $size; } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } if (-not ([string]::IsNullOrEmpty($wrappingKeyName))) { $argsDict["--wrapping-key-name"] = $wrappingKeyName } if (-not ([string]::IsNullOrEmpty($wrappingPubKeyFile))) { $argsDict["--wrapping-pub-key-file"] = $wrappingPubKeyFile } Invoke-MocCommand " security keyvault key export" -argDictionary $argsDict -boolFlags $boolFlags } function Invoke-MocKeyEncrypt { <# .DESCRIPTION Encrypt input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64', [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsdict = @{ "--name" = $name; "--group" = $group; "--vault-name" = $keyvaultName; "--file" = $inputDataFile; "--out-file" = $outputDataFile; "--data-type" = $inputType } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand "security keyvault key encrypt" -argDictionary $argsDict } function Invoke-MocKeyDecrypt { <# .DESCRIPTION Decrypt input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64', [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsdict = @{ "--name" = $name; "--group" = $group; "--vault-name" = $keyvaultName; "--file" = $inputDataFile; "--out-file" = $outputDataFile; "--data-type" = $inputType } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand "security keyvault key decrypt" -argDictionary $argsDict } function Invoke-MocKeyWrap { <# .DESCRIPTION Wrap input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64', [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsdict = @{ "--name" = $name; "--group" = $group; "--vault-name" = $keyvaultName; "--file" = $inputDataFile; "--out-file" = $outputDataFile; "--data-type" = $inputType } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand "security keyvault key wrap" -argDictionary $argsDict } function Invoke-MocKeyUnwrap { <# .DESCRIPTION Unwrap input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64', [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) $argsdict = @{ "--name" = $name; "--group" = $group; "--vault-name" = $keyvaultName; "--file" = $inputDataFile; "--out-file" = $outputDataFile; "--data-type" = $inputType } if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand "security keyvault key unwrap" -argDictionary $argsDict } function Get-MocKeyPublicKey { <# .DESCRIPTION Downloads Public Key if keytype is RSA .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$outputFile, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $("security keyvault key download --name $name --group $group --vault-name $keyvaultName --file-path ""$outputFile"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}})) } function Remove-MocKey { <# .DESCRIPTION Get a Key resource from moc. .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) if($timeoutSeconds) { $argsDict["--timeout"] = $timeoutSeconds } Invoke-MocCommand $(" security keyvault key delete --name ""$name"" --group $group --vault-name $keyvaultName") } function Reset-MocKey { <# .DESCRIPTION Remove all moc key .PARAMETER group group for the key .PARAMETER keyvaultName name of the keyvault .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" security keyvault key list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { if ($timeoutSeconds) { Remove-MocKey -name $entityName -group $group -keyvaultName $keyvaultName -timeout $timeoutSeconds } else { Remove-MocKey -name $entityName -group $group -keyvaultName $keyvaultName } } } } #endregion moc key #region moc backup function Invoke-MocBackup { <# .DESCRIPTION Removes a group resource from moc. .PARAMETER path Path to backup to .PARAMETER timeoutSeconds timeout in seconds #> param ( [Parameter(Mandatory=$true)] [String]$path, [Parameter(Mandatory=$false)] [int32]$timeoutSeconds ) Invoke-MocCommand $(" admin recovery backup --path ""$path"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) ) } #endregion moc backup #end region function Get-NodeAgentVersion { <# .DESCRIPTION Executes a nodeagent command. #> $cmdArgs = " version" $tmp = Invoke-CommandLine -Command $global:nodeAgentBinary -Arguments $cmdArgs -moduleName $moduleName return $tmp[2] } function Get-MocAgentVersion { <# .DESCRIPTION Executes a nodeagent command. #> $cmdArgs = " version" $tmp = Invoke-CommandLine -Command $global:cloudAgentBinary -Arguments $cmdArgs -moduleName $moduleName return $tmp[2] } function Set-DeploymentType { <# .DESCRIPTION Determine what type of deployment is being performed (single node or multi-node). #> Write-Status -moduleName $moduleName $($MocLocMessage.moc_determining_deployment_type) # Important to save the cluster information globally for subsequent function calls to use $failoverCluster = Get-FailoverCluster if ($failoverCluster) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_multinode_deployment_using_failover_cluster, $failoverCluster.Name)) # Set additional cluster related globals for later use Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::MultiNode) Set-MocConfigValue -name "nodeCount" -value (Get-ClusterNode).Count Set-CloudFQDN } else { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_singlenode_deployment) $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname # If this single node hostname is a short name, we use the host ip to avoid DNS resolution issues if ($hostname -notlike '*.*') { $hostname = Get-HostIp -nodeName localhost } Set-MocConfigValue -name "cloudFqdn" -value $hostname Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::SingleNode) } } function Set-CloudFQDN { <# .DESCRIPTION This function sets the cloud fqdn for cloud agent. If using default cluster role name, then cloudfqdn = failovercluster.Name + "." + failoverCluster.Domain. If customer is providing their own cluster role name then cloudfqdn = clusterRoleName + "." + failoverCluster.Domain #> $clusterRoleName = (Get-MocConfig)["clusterRoleName"] $failoverCluster = Get-FailoverCluster $useUpdateFailoverClusterCreationFlow = (Get-MocConfig)["useUpdatedFailoverClusterCreationLogic"] if ($useUpdateFailoverClusterCreationFlow) { Set-MocConfigValue -name "cloudFqdn" -value ($failoverCluster.Name + "." + $failoverCluster.Domain) } else { Set-MocConfigValue -name "cloudFqdn" -value ($clusterRoleName + "." + $failoverCluster.Domain) } } function Update-CloudFqdn { <# .DESCRIPTION This function updates the cloud fqdn for cloud agent. To handle existing cxs and ensure backwards compability, this method will check existing cloud fqdn. If 1) clusterRolename is default and cloudfqdn does not start with ca-, then we will cloudfqdn = $failoverCluster.Name + "." + $failoverCluster.Domain. If 2) cloud fqdn will remain unchanged for all other cases to ensure backwards compability. #> $clusterRoleName = (Get-MocConfig)["clusterRoleName"] $cloudFqdn = Get-CloudFqdn $failoverCluster = Get-FailoverCluster $useUpdateFailoverClusterCreationFlow = (Get-MocConfig)["useUpdatedFailoverClusterCreationLogic"] if (($useUpdateFailoverClusterCreationFlow) -and !($cloudFqdn -like "ca-*")) { $updatedCloudFqdn = ($failoverCluster.Name + "." + $failoverCluster.Domain) Set-MocConfigValue -name "cloudFqdn" -value ($failoverCluster.Name + "." + $failoverCluster.Domain) } else { $updatedCloudFqdn = ($clusterRoleName + "." + $failoverCluster.Domain) Set-MocConfigValue -name "cloudFqdn" -value ($clusterRoleName + "." + $failoverCluster.Domain) } return $updatedCloudFqdn } function Test-Remoting { <# .DESCRIPTION Validate that powershell remoting to a node is working. .PARAMETER nodeName The node to remote to. #> param ( [String]$nodeName ) try { Invoke-Command -ComputerName $nodeName -ScriptBlock { return $null } -ErrorAction Stop return $true } catch {} return $false } function Test-CloudLimits { <# .DESCRIPTION Verify the resource limits for all nodes in the cloud. .PARAMETER path Path to WSSD Image directory. .PARAMETER minRequiredDisk Minimimum required disk space (GB). .PARAMETER minRequiredMemory Minimimum required memory (GB). .PARAMETER minRequiredLp Minimimum required logical processor. #> param ( [string]$path, [int]$minRequiredDisk, [int]$minRequiredMemory, [int]$minRequiredLp ) Write-Status $($MocLocMessage.moc_verifying_cloud_limits) -moduleName $moduleName if (Test-MultiNodeDeployment) { if($path.ToLower().IndexOf("$env:SystemDrive\clusterstorage".ToLower()) -eq 0) { Write-SubStatus $($MocLocMessage.moc_checking_available_space) -moduleName $moduleName $space = Get-ClusterSharedVolume -ErrorAction Stop | Where-Object {$path.ToLower().IndexOf($_.SharedVolumeInfo.FriendlyVolumeName.ToLower()) -eq 0} if ($null -ne $space) { $freespaceGb = [Math]::Round($space.SharedVolumeInfo.Partition.FreeSpace / 1GB) } else { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_missing_drive, $path)) } Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_has_free_space, $freespaceGb)) -moduleName $moduleName Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_disk_space_required, $minRequiredDisk)) -moduleName $moduleName if ($freespaceGb -lt $minRequiredDisk) { Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_has_free_space, $freespaceGb)) Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_disk_space_required, $minRequiredDisk)) throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_insufficient_shared_drive_space, $drive)), ([ErrorTypes]::IsInfraErrorFlag)) } } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Test-HostLimits -nodeName $_.Name -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp } } else { $drive = Split-Path -Path $path -Qualifier $disk = Get-WmiObject Win32_LogicalDisk -Filter $("DeviceID='$drive'") | Select-Object Size,FreeSpace $freespaceGb = [Math]::Round($Disk.Freespace / 1GB) Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_drive_free_space, $drive, $freespaceGb)) -moduleName $moduleName Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_minimum_disk_space_required_on_drive, $minRequiredDisk, $drive)) -moduleName $moduleName if ($freespaceGb -lt $minRequiredDisk) { Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_drive_free_space, $drive, $freespaceGb)) Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_minimum_disk_space_required_on_drive, $minRequiredDisk, $drive)) throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_insufficient_drive_space, $drive)), ([ErrorTypes]::IsInfraErrorFlag)) } Test-HostLimits -nodeName ($env:computername) -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp } } function Test-HostLimits { <# .DESCRIPTION Verify the resource limits for a host node. .PARAMETER nodeName The node to execute on. .PARAMETER minRequiredMemory Minimimum required memory (GB). .PARAMETER minRequiredLp Minimimum required LogicalProcessor. #> param ( [string]$nodeName, [int]$minRequiredMemory, [int]$minRequiredLp ) Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_verifying_host_limits, $nodeName)) -moduleName $moduleName try { Invoke-Command -ComputerName $nodeName -ScriptBlock { $minRequiredMemory = $args[0] $minRequiredLp = $args[1] $MocLocMessage = $args[2] $GenericLocMessage = $args[3] $freemem = Get-WmiObject -Class Win32_OperatingSystem $freemem = [Math]::Round($freemem.FreePhysicalMemory / 1MB) write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_free_memory_left, $freemem)) write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_minimum_required_memory, $minRequiredMemory)) if ($freemem -lt $minRequiredMemory) { throw $($MocLocMessage.moc_insufficient_memory) } $lpCount = (Get-ComputerInfo -Property CsNumberOfLogicalProcessors).CsNumberOfLogicalProcessors write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_logical_processors_count, $lpCount)) write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_minimum_required_logical_processors, $minRequiredLp)) if ($lpCount -lt $minRequiredLp) { throw $($MocLocMessage.moc_insufficient_logical_procesors) } } -ArgumentList $minRequiredMemory, $minRequiredLp, $MocLocMessage, $GenericLocMessage } catch { throw [CustomException]::new($_, ([ErrorTypes]::IsInfraErrorFlag)) } } function Confirm-Remoting { <# .DESCRIPTION Confirm that powershell remoting is enabled and working for the cloud. .PARAMETER localhostOnly Limit the validation to only the local node. #> param ( [Switch]$localhostOnly ) if ((-not $localhostOnly.IsPresent) -and (Test-MultiNodeDeployment)) { # Multi-node Get-ClusterNode -ErrorAction Stop | ForEach-Object { $hostname = $_.Name Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_powershell, $hostname)) -moduleName $moduleName if (-not (Test-Remoting -nodeName $hostname)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_disabled_powershell, $hostname)), ([ErrorTypes]::IsInfraErrorFlag)) } Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_remoting_successful, $hostname)) -moduleName $moduleName } } else { # Single node or localhostOnly Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_powershell, $env:computername)) -moduleName $moduleName if (-not (Test-Remoting -nodeName $env:computername)) { Write-SubStatus $($MocLocMessage.moc_remoting_not_enabled) -moduleName $moduleName Enable-Remoting if (-not (Test-Remoting -nodeName $env:computername)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ps_remoting_unsuccessful, $env:computername)), ([ErrorTypes]::IsErrorFlag)) } } Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_remoting_successful, $env:computername)) -moduleName $moduleName } } function Confirm-NetworkControllerConfiguration { <# .DESCRIPTION Does basic validation of NetworkController Data #> param( [string] $networkControllerFqdnOrIpAddress, [string] $networkControllerClientCertificateName, [string] $networkControllerLbSubnetRef, [string] $networkControllerLnetRef, [VipPoolSettings] $vipPool ) if ([string]::IsNullOrWhiteSpace($networkControllerFqdnOrIpAddress)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerFqdnOrIpAddress")) } if ([string]::IsNullOrWhiteSpace($networkControllerLbSubnetRef)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerLbSubnetRef")) } if ([string]::IsNullOrWhiteSpace($networkControllerLnetRef)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerLnetRef")) } if (-not (Test-NetConnection -ComputerName $networkControllerFqdnOrIpAddress -Port 443 -InformationLevel Quiet)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_conn_test_failed, $networkControllerFqdnOrIpAddress)) } # TODO We need to add validation for various NetworkController parameters. Skipping it for now until its confirmed NC REST endpoint is accessible at this point. } function Confirm-Configuration { <# .DESCRIPTION Validate if the configuration can be used for the deployment #> param ( [String] $workingDir, [String] $cloudConfigLocation, [String] $imageDir, [Switch] $skipHostLimitChecks, [Switch] $skipRemotingChecks, [Switch] $useStagingShare, [String] $stagingShare, [VirtualNetwork] $vnet, [string] $cloudServiceCidr, [Switch] $useNetWorkController, [string] $networkControllerFqdnOrIpAddress, [string] $networkControllerClientCertificateName, [string] $networkControllerLbSubnetRef, [string] $networkControllerLnetRef, [VipPoolSettings] $vipPool, [string] $clusterRoleName ) if (!$skipRemotingChecks.IsPresent) { Confirm-Remoting } if ($useNetworkController.IsPresent) { Confirm-NetworkControllerConfiguration -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress ` -networkControllerLbSubnetRef $networkControllerLbSubnetRef ` -networkControllerLnetRef $networkControllerLnetRef ` -networkControllerClientCertificateName $networkControllerClientCertificateName ` -vipPool $vipPool } if ([string]::IsNullOrWhiteSpace($workingDir)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir")) } if ([string]::IsNullOrWhiteSpace($cloudConfigLocation)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "cloudConfigLocation")) } # cloudConfigLocation and imageDir cannot be a parent path for workingDir # for example, workingDir = c:\test\123 and cloudConfigLocation = c:\test is invalid # During install of Moc, the system is reset including cloudConfigLocation/imageDir, that would wipe of workingDir, that # contains critical PS configuration if ($workingDir.StartsWith($cloudConfigLocation.TrimEnd('\\'), $true, [System.Globalization.CultureInfo]::InvariantCulture)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_invalid_working_dir_cloudconfig, $workingDir, $cloudConfigLocation) ) } if ($workingDir.StartsWith($imageDir.TrimEnd('\\'), $true, [System.Globalization.CultureInfo]::InvariantCulture)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_invalid_working_dir_imagedir, $workingDir, $imageDir) ) } $multiNode = Test-MultiNodeDeployment if ($multiNode) { #Check if the working directory corresponds to CSV root. $volumeList = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo foreach ($volume in $volumeList) { if (!$volume.SharedVolumeInfo.FriendlyVolumeName) { # If volume is not online, we will not find get the friendly volume name continue } $rootPath = $volume.SharedVolumeInfo.FriendlyVolumeName.ToLower() if (([System.IO.Path]::GetFullPath($WorkingDir)).ToLower() -eq $rootPath) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root)) } } } else { #Static check to ensure working directory is not System Drive or Root if (($WorkingDir -eq $env:SystemDrive) -or ($WorkingDir.TrimEnd('\\') -eq $env:SystemDrive) -or ($WorkingDir -eq $env:SystemRoot) -or ($WorkingDir.TrimEnd('\\') -eq $env:SystemRoot) -or ($WorkingDir -eq [System.IO.Path]::GetFullPath($env:SystemDrive))) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root)) } } #Check HCI node registration status if ($multiNode) { Test-ClusterHealth Test-HCIRegistration # Sanity check. if ((Test-LocalFilePath -path $workingDir) ) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-workingDir")) } if ((Test-LocalFilePath -path $cloudConfigLocation)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-cloudConfigLocation")) } if ((Test-LocalFilePath -path $imageDir)) { throw $($MocLocMessage.moc_no_moc_config) } } if (!$skipHostLimitChecks.IsPresent) { $tmpDir = $workingDir # Skip SMB path if (!$tmpDir.StartsWith("\\")) { if ($multiNode) { Test-CloudLimits -path $tmpDir -minRequiredDisk 40 -minRequiredMemory 10 -minRequiredLp 4 } else { Test-CloudLimits -path $tmpDir -minRequiredDisk 50 -minRequiredMemory 20 -minRequiredLp 4 } } } if ($useStagingShare.IsPresent -and [string]::IsNullOrWhiteSpace($stagingShare)) { throw $($MocLocMessage.generic_staging_share_unspecified) } #Netowrk Validations : #1. whether vSwitch provided in the vnet configuration exists. #2. CloudCIDR parameter is provided in case of static networks. if ($multiNode) { Get-ClusterNode -ErrorAction Stop | ForEach-Object { if ($vnet) { Test-HostNetworking -nodeName $_.Name -vswitchName $vnet.VswitchName } } $testClusterRoleName = (Get-MocConfig)["testClusterRoleName"] Test-ClusterNetworkProperties -cloudServiceCidr $cloudServiceCidr -vnet $vnet -clusterRoleName $testClusterRoleName } else { if ($vnet) { Test-HostNetworking -nodeName ($env:computername) -vswitchName $vnet.VswitchName } # There is no cloud service cidr for standalone # We use the host ip for cloud service ip if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr)) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ignore_csip , $cloudServiceCidr)) } } } function Get-SshNatRuleMap { <# .DESCRIPTION Fetches the ssh nat lb rules for the group and caches it. Returns a map of all ssh nat rules for the given group .PARAMETER groupName Group name #> param ( [Parameter()] [String] $groupName ) if ($script:natRuleCache -ne $null) { $ruleMap = $script:natRuleCache[$groupName] if ($ruleMap -ne $null) { return $ruleMap } } else { $script:natRuleCache = @{} } $ruleMap = @{} $lbs = Invoke-MocCommand "network loadbalancer list --group $groupName -o json" if ($lbs -eq $null) { $script:natRuleCache[$groupName] = $ruleMap return $ruleMap } $lbJson = $lbs | Convertfrom-json $inboundLb = $lbJson | where-object { $_.properties.inboundNatRules -ne $null } foreach($lb in $inboundLb) { $sshNatRules = $lb.properties.inboundNatRules | ` Where-Object { $_.Properties.backendPort -eq 22 } if($sshNatRules -ne $null) { $feIP = $lb.properties.frontendIPConfigurations[0].properties.ipAddress foreach($natRule in $sshNatRules) { $port = $natRule.properties.frontendPort $sshData = @{} $sshData["ip"] = $feIP $sshData["port"] = $port $ruleMap[$natRule.Name] = $sshData } } } $natRuleCache[$groupName] = $ruleMap return $ruleMap } function Get-SshNatEndpoint { <# .DESCRIPTION Fetches the ssh frontend nat ip and port for the given nic. When SDN integration is configured, ssh access to the node is via the inbound nat rule .PARAMETER nicName Networkinterface name .PARAMETER groupName Group name #> param ( [Parameter()] [String] $nicName, [Parameter()] [String] $groupName ) $natRuleName = Invoke-MocCommand "network vnic show --name ""$nicName"" -o yaml --query properties.ipConfigurations[0].properties.loadBalancerInboundNatRules[0].name --group ""$groupName""" if ($natRuleName -eq $null -or $natRuleName[0] -eq $null) { return $null } $ruleMap = Get-SshNatRuleMap -groupName $groupName return $ruleMap[$natRuleName] } function Get-GuestVirtualMachineLogs { <# .DESCRIPTION Collects logs from Virtual Machines via scp .PARAMETER logDirectoryName Output directory for the logs .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $logDirectoryName, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_discovering_cloud_groups) $groups = Invoke-MocCommand ("cloud group list --output tsv --query ""[*].name"" --location " + $global:config[$modulename]["cloudLocation"]) foreach ($grpName in $groups) { if ($grpName -ieq "No Group Resources") { continue } # Make Resource Group directory $logGroupDir = [io.Path]::Combine($logDirectoryName, $grpName) New-Item -ItemType Directory -Force -Path $logGroupDir | Out-Null try { $virtualMachines = Invoke-MocCommand "compute vm list -o tsv --query ""[*].name"" --group ""$grpName""" foreach ($vmName in $virtualMachines) { if ($vmName -ieq "No VirtualMachine Resources") { continue } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_gather_logs_for_vm, $vmName)) try { $nicName = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.networkprofile.networkinterfaces[0] --group ""$grpName""" $cleanNicName = $nicName.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join '' $ipAddress = Invoke-MocCommand "network vnic show --name ""$cleanNicName"" -o tsv --query properties.ipConfigurations[0].properties.privateIPAddress --group ""$grpName""" $port = 22 $keyGenAddress = $ipAddress if (Test-MocSdnEnabled) { # Use SDN NAT IP & port for ssh if configured $sshData = Get-SshNatEndpoint -nicName $cleanNicName -groupName $grpName if ($sshData -ne $null) { $ipAddress = $sshData.ip $port = $sshData.port $keyGenAddress = "[" + $ipAddress+ "]" + ":" + $port } } if ([string]::IsNullOrWhiteSpace($ipAddress) -or $ipAddress -ieq "no virtual network interface resources") { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_log_collection_failed, $vmName)), ([ErrorTypes]::IsErrorFlag)) } # Remove cached keys belonging to this ip from the known_hosts file. # This will avoid scp/ssh warnings being shown in redeployment scenarios where stale data may exist for a host/ip in the known_hosts file. (& ssh-keygen -R ${keyGenAddress}) 2>&1>$null $userSshPrivateKey = Get-UserSSHPrivateKey # Make VM directory $logVMDir = [io.Path]::Combine($logDirectoryName, $grpName) New-Item -ItemType Directory -Force -Path $logVMDir | Out-Null Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_cloudinit_logs_from_vm, $vmName)) $osType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o yaml --query virtualmachineproperties.osprofile.osType --group ""$grpName""" if ($osType -like 'Windows') { $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_logs.zip") $randomFolder=$(New-Guid) (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "Administrator@${ipAddress}" "mkdir C:\tmp\$randomFolder && pushd C:\tmp\$randomFolder && powershell.exe C:\Packages\collect-windows-logs.ps1") 2>&1>$null if ($?) { (& scp -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -P $port "Administrator@${ipAddress}:/tmp/$randomFolder/*.zip" $logDirFile) 2>$null (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "Administrator@${ipAddress}" "powershell.exe Remove-Item -Recurse -Force C:\tmp\$randomFolder") 2>$null } else { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_failed_to_get_logs_from_vm, $vmName)) } # Windows VM log collection is over continue } $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_cloudinit.log") (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no -P $port "clouduser@${ipAddress}:/var/log/cloud-init-output.log" $logDirFile) 2>$null Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_systemd_logs, $vmName)) $systemdLogFile = [io.Path]::Combine($logVMDir, "${vmName}_systemd.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl --output=short-precise -n $global:defaultLogLineCount) > $systemdLogFile # Check if LB VM Type $vmType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.vmType --group ""$grpName""" $cleanVmType = $vmType.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join '' if ($cleanVmType -ieq "LoadBalancer") { if ($ipAddress -ine "no virtual network interface resources") { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_haproxy_configuration, $vmName)) $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.cfg") (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no -P $port "clouduser@${ipAddress}:/etc/haproxy/haproxy.cfg" $lbLogDirFile) 2>$null Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_keepalived_configuration, $vmName)) $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.conf") (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no -P $port "clouduser@${ipAddress}:/etc/keepalived/keepalived.conf" $lbLogDirFile) 2>$null Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_haproxy_logs, $vmName)) $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl -u haproxy -n $global:defaultLogLineCount) > $lbLogDirFile Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_keepalived_logs, $vmName)) $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl -u keepalived -n $global:defaultLogLineCount) > $lbLogDirFile Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_lbagent_logs, $vmName)) $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_lbagent.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl -u lbagent -n $global:defaultLogLineCount) > $lbLogDirFile } } else { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_docker_logs, $vmName)) $dockerLogFile = [io.Path]::Combine($logVMDir, "${vmName}_docker.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl --no-pager -u containerd -n $global:defaultLogLineCount) > $dockerLogFile Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_kubelet_logs, $vmName)) $kubeletLogFile = [io.Path]::Combine($logVMDir, "${vmName}_kubelet.log") (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no -p $port "clouduser@${ipAddress}" sudo journalctl --no-pager -u kubelet -n $global:defaultLogLineCount) > $kubeletLogFile } } catch [Exception] { Write-Status -msg $($GenericLocMessage.generic_exception) -moduleName $moduleName Write-SubStatus -msg $_.Exception.Message.ToString() -moduleName $moduleName Write-SubStatus -msg $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_could_not_get_cloudinit_logs, $vmName)) -moduleName $moduleName } } } catch [Exception] { Write-Status -msg $($GenericLocMessage.generic_exception) -moduleName $moduleName Write-SubStatus -msg $_.Exception.Message.ToString() -moduleName $moduleName } } } function Wait-ForActiveNodes { <# .DESCRIPTION Waits for all nodes in the cloud to be in Active state. .PARAMETER sleepDuration Duration to sleep for between attempts .PARAMETER timeout Duration until timeout of waiting .PARAMETER location Location for the nodes. .PARAMETER activity Activity name to use when updating progress #> param ( [int]$sleepDuration=20, [int]$timeout=1800, #seconds in 30min [string]$location = $global:defaultCloudLocation, [String]$activity = $MyInvocation.MyCommand.Name ) trap [System.Exception] { throw [CustomException]::new($($MocLocMessage.moc_nodes_not_active, $_.Exception.Message), ([ErrorTypes]::IsErrorFlag)) } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_waiting_for_cloud_nodes_to_be_active) ## Start the timer $timer = [Diagnostics.Stopwatch]::StartNew() $nodeCount = 1 if (Test-MultiNodeDeployment) { $nodeCount = (Get-ClusterNode -ErrorAction Ignore).Count Wait-ForConnectionFromNodes -timeout $timeout } while(($timer.Elapsed.TotalSeconds -lt $timeout)) { $activeNodes = $null try { $activeNodes = Invoke-MocCommand "cloud node list -o json --query ""[?properties.statuses.RunningState=='Active']"" --location $location" | ConvertFrom-Json } catch { # When no nodes are Active, ConvertFrom-Json will throw an exception if (-not ($_.Exception.Message -like "*Invalid JSON primitive: No.*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } } if ($null -ne $activeNodes) { $numActive = ($activeNodes).Count if ($nodeCount -eq $numActive) { Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_all_nodes_active) return $true } } Start-Sleep $sleepDuration } return $false } #endregion #region Wait for resource functions function Test-MocApiServer { <# .DESCRIPTION Waits for the cloudagent generic service VIP/FQDN to be functional (i.e. wait for DNS to propogate). .PARAMETER cloudFqdn Fqdn to attemp to resolve and connect .PARAMETER ports List of ports to test .PARAMETER nodeName Name of the node to run the test from #> param ( [Parameter(Mandatory=$true)] [String] $cloudFqdn, [Parameter(Mandatory=$true)] [AllowNull()][String[]] $ports, [Parameter(Mandatory=$true)] [string] $nodeName ) try { Invoke-Command -ComputerName $nodeName -ErrorAction Stop -ScriptBlock { $cloudFqdn = $args[0] $ports = $args[1] $MocLocMessage = $args[2] Clear-DnsClientCache Resolve-DnsName $cloudFqdn | Out-Null foreach ($port in $ports) { if (-not (Test-NetConnection -ComputerName $cloudFqdn -Port $port -InformationLevel Quiet)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testnetconnection_failed_to_contact_ca, $cloudFqdn, $port)) } } } -ArgumentList @($cloudFqdn, $ports, $MocLocMessage) } catch { throw [CustomException]::new($_, ([ErrorTypes]::IsInfraErrorFlag)) } } function Wait-ForCloudAgentEndpoint { <# .DESCRIPTION Waits for the cloudagent generic service VIP/FQDN to be functional (i.e. wait for DNS to propogate). On failure, error is thrown back with appropriate message conveying why this failed. .PARAMETER sleepDuration Duration to sleep for between attempts .PARAMETER timeout Duration until timeout of waiting .PARAMETER activity Activity name to use when updating progress #> param ( [int]$sleepDuration=20, [int]$timeout=3600, #seconds in a hour [String]$activity = $MyInvocation.MyCommand.Name ) trap [System.Exception] { throw $($MocLocMessage.moc_cloudagent_unreachable, $_.Exception.Message) } Test-MocService -activity $activity Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_waiting_for_cloudagent_api_endpoint) -moduleName $moduleName Write-SubStatus $($MocLocMessage.moc_dns_propogation_warning) -moduleName $moduleName $endpoint = Get-CloudFqdn $cloudPorts = ($global:config[$modulename]["cloudAgentPort"], $global:config[$modulename]["cloudAgentAuthorizerPort"]) $lastException = $null ## Start the timer $timer = [Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_cloudagent_endpoint, $endpoint)) -moduleName $moduleName $location = $null try { # Test using existing APIs # This could fail if DNS resolution fails # Test-MocApiServer -cloudFqdn $endpoint -ports $cloudPorts -nodeName $(hostname) Invoke-MocLogin -loginYaml $($global:config[$moduleName]["mocLoginYAML"]) | Out-Null $location = Get-MocLocation if ($null -ne $location) { Write-SubStatus $($MocLocMessage.moc_cloudagent_vip_is_working) -moduleName $moduleName # MOC is completely functional return } } catch { $lastException = $_ Write-Verbose -Message $_ } Sleep $sleepDuration } throw $lastException } function Test-MocAgents { param ( [String]$activity = $MyInvocation.MyCommand.Name ) <# .DESCRIPTION Test if cloud agent service is running. Test if node agent service is running. If Service exists and is running, would return If Service exists and is not running, would throw exception If Service doesnt exist, an exception woud be thrown .PARAMETER activity Activity name to use when updating progress #> trap [System.Exception] { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_agents_not_healthy, $_.Exception.Message)) } Test-MocService -activity $activity if (Test-MultiNodeDeployment) { Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name $tmpService = Get-Service wssdagent -ComputerName $nodeName if ($tmpService.Status -ne "Running") { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_not_running, $nodeName)) } $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_running, $nodeName)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName } } else { $nodeName = $(hostname) $tmpService = Get-Service wssdagent if ($tmpService.Status -ne "Running") { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_not_running, $nodeName)) } $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_running, $nodeName)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName } } function Test-MocService { param ( [String]$activity = $MyInvocation.MyCommand.Name ) <# .DESCRIPTION Test if cloud agent service is running. If Service exists and is running, would return $true If Service exists and is not running, would return $false If Service doesnt exist, an exception woud be thrown .PARAMETER activity Activity name to use when updating progress #> if (Test-MultiNodeDeployment) { $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_for_cloudagent_service, (Get-Cluster).Name)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName $tmpGroup = Get-ClusterGroup -Name ($global:config[$modulename]["clusterRoleName"].ToString()) -ErrorAction Ignore if ($tmpGroup.State -ne "Online") { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_not_running, (Get-Cluster).Name)) } $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_running, (Get-Cluster).Name)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName } else { $nodeName = $(hostname) $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_for_cloudagent_service, $nodeName)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName $tmpService = Get-Service wssdcloudagent -ErrorAction Ignore if ($tmpService.Status -ne "Running") { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_not_running, $nodeName)) } $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_running, $nodeName)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName } } function Test-Moc { param ( [String]$activity = $MyInvocation.MyCommand.Name ) <# .DESCRIPTION Test if MOC installation is healthy Test if MOC Service is healthy Test if MOC endpoints are helathy Test if MOC Dns resolutions are healthy from nodes Throws if any tests fails .PARAMETER activity Activity name to use when updating progress #> trap { Uninitialize-MocEnvironment -activity $activity throw $_ } Initialize-MocEnvironment -activity $activity Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_test_health) -moduleName $moduleName # Test-MocInstallation $currentState = Get-InstallState -module $moduleName switch ($currentState) { ([InstallState]::NotInstalled) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_not_installed)) } ([InstallState]::InstallFailed) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_not_installed)) } Default { $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installed)) Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName } } Test-MocAgents Wait-ForCloudAgentEndpoint Test-CloudDnsFromNodes Uninitialize-MocEnvironment -activity $activity Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_healthy) -moduleName $moduleName } function Wait-ForMocRole { <# .DESCRIPTION Waits for MOC role to be available. .PARAMETER roleName Name of role to wait on .PARAMETER sleepDuration Duration to sleep for between attempts .PARAMETER timeout Duration until timeout of waiting .PARAMETER activity Activity name to use when updating progress #> param ( [Parameter(Mandatory=$true)] [string]$roleName = $global:defaultCloudLocation, [int]$sleepDuration=5, [int]$timeout=120, #seconds [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_waiting_for_role, $roleName)) ## Start the timer $timer = [Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { try { Get-MocRole -name $roleName | Out-Null } catch { # When role is not found, exception is thrown if (-not ($_.Exception.Message -like "*Not Found*")) { Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message)) } Start-Sleep $sleepDuration continue } return $true } return $false } #endregion #region function Test-MocConfiguration { <# .SYNOPSIS Runs validation tests against Moc and host configuration to make sure host is ready to install Moc successfully. .DESCRIPTION Runs validation tests against Moc and host configuration to make sure host is ready to install Moc successfully. .PARAMETER Ignore Specifies which tests or category of tests to ignore during the validation test run. All others tests or category of tests will run. .PARAMETER Include Specifies which tests or category of tests to include during the validation test run. Only the tests or category of tests specified here will run. .PARAMETER List Causes the cmdlet to list the tests and test categories. This command will not run any test. .PARAMETER Skip Causes the cmdlet not to generate report file. #> [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess, ConfirmImpact = 'Low')] param ( [Parameter()] [String[]] $Ignore, [Parameter()] [String[]] $Include, [Parameter()] [Switch] $List, [Parameter()] [Switch] $Skip ) $mocTests = Get-MocValidationTests if ($List) { return $mocTests | select-object -Property Category, TestName, Description } $mocTestsToRun = @() if ($Include.length -gt 0) { $includedTests = $mocTests | Where-Object {$_.Category -in $Include -or $_.TestName -in $Include} $mocTestsToRun += $includedTests } else { $mocTestsToRun = $mocTests } if ($Ignore.length -gt 0) { $mocTestsToRun = $mocTestsToRun | Where-Object {$_.Category -notin $Ignore -and $_.TestName -notin $Ignore} } if (-not(PSasAdmin)){ Write-Host '' Write-Host $($MocLocMessage.moc_validation_not_admin) -ForegroundColor Red Write-Host '' Write-Host $($MocLocMessage.moc_validation_not_admin_recommendation) -ForegroundColor Yellow Write-Host '' exit } $dateTime = get-date -Format "dd.MM.yyyy HH:mm:ss" Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_start, $dateTime)) $testResults = @() $overallResult = $true $totalTests = ($mocTestsToRun | measure).Count $current = 1 foreach($mocTest in $mocTestsToRun) { $startTime = Get-Date Write-Host '===============================================================================' -ForegroundColor DarkYellow $outputHeader = "Test ({0} of {1}): `"{2}`". Category: {3}" -f $current,$totalTests,$mocTest.TestName,$mocTest.Category Write-Host '' Write-Host $outputHeader -ForegroundColor DarkYellow $testFunction = $mocTest.TestFunction try { $testReport = &"$testFunction" } catch { $testReport = [pscustomobject]@{ 'TestName' = $mocTest.TestName; 'Category' = $mocTest.Category; 'TestResult' = "Failed"; 'Details' = $_.Exception.Message } } $testResults += $testReport if ($testReport.TestResult -eq "Failed") { $overallResult = $false Write-Host "Test Failed" -ForegroundColor Red Write-Host "Details:"$testReport.Details -ForegroundColor Red Write-Host "Recommendation:"$testReport.Recommendation -ForegroundColor Red } else { Write-Host "Test Succeeded" -ForegroundColor Green Write-Host "Details:"$testReport.Details -ForegroundColor Green Write-Host "Recommendation:"$testReport.Recommendation -ForegroundColor Green } $current++ $testExecutionTime = ((Get-Date) - $startTime).TotalMilliseconds Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_test_time, $testExecutionTime)) -ForegroundColor DarkYellow Write-Host '' } Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_end, $dateTime)) if ($overallResult) { Write-Host '' Write-Host '=====================================================' Write-Host ' '$mocLocMessage.moc_validation_test_success_summary -ForegroundColor Green Write-Host '=====================================================' Write-Host '' } else { Write-Host '' Write-Host '=====================================================' Write-Host ' '$mocLocMessage.moc_validation_test_failure_summary -ForegroundColor Red Write-Host '=====================================================' Write-Host '' } if (-not($Skip)) { $testResults | ConvertTo-Html -Title $mocLocMessage.moc_validation_report_title | Out-File -FilePath moc_validation_report.html $reportFileName = "moc_validation_report.html" Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_report_check, $reportFileName)) -ForegroundColor Yellow } return $testResults } function Get-MocValidationTests() { $mocTests = @() # Test category "Moc Host" # $mocHostNetworkConnectivityTest = [ordered]@{ # 'TestName' = "Validate MOC Host Internet Connetivity"; # 'Category' = "MOC Host"; # 'Description' = "Validates MOC Host Internet Connetivity"; # 'TestFunction' = "Test-MocHostInternetConnectivity" # } # $mocTests += New-Object -TypeName PsObject -Property $mocHostNetworkConnectivityTest $mocHostLimitTest = [ordered]@{ 'TestName' = "Validate MOC Host Limits"; 'Category' = "MOC Host"; 'Description' = "Validates MOC Host Limits"; 'TestFunction' = "Test-MocHostLimits" } $mocTests += New-Object -TypeName PsObject -Property $mocHostLimitTest $mocHostRemotingTest = [ordered]@{ 'TestName' = "Validate MOC Host Remoting"; 'Category' = "MOC Host"; 'Description' = "Validates MOC Host Remoting"; 'TestFunction' = "Test-MocHostRemoting" } $mocTests += New-Object -TypeName PsObject -Property $mocHostRemotingTest # Test category "Moc Configuration" $mocNetworkConfigurationTest = [ordered]@{ 'TestName' = "Validate MOC Network Configuraiton"; 'Category' = "MOC Configuration"; 'Description' = "Validates MOC network configuration"; 'TestFunction' = "Test-MocNetworkConfiguration" } $mocTests += New-Object -TypeName PsObject -Property $mocNetworkConfigurationTest $mocSDNConfigurationTest = [ordered]@{ 'TestName' = "Validate MOC SDN Configuraiton"; 'Category' = "MOC Configuration"; 'Description' = "Validates MOC SDN configuration"; 'TestFunction' = "Test-MocSDNConfiguration" } $mocTests += New-Object -TypeName PsObject -Property $mocSDNConfigurationTest $mocValidDirectoryTest = [ordered]@{ 'TestName' = "Validate MOC directories"; 'Category' = "MOC Configuration"; 'Description' = "Validates MOC directories"; 'TestFunction' = "Test-MocDirectories" } $mocTests += New-Object -TypeName PsObject -Property $mocValidDirectoryTest # Test category "Moc Failover Cluster" $mocFailOverClusterHealthTest = [ordered]@{ 'TestName' = "Validate Failover Cluster Health"; 'Category' = "MOC Failover Cluster"; 'Description' = "Validates Failover Cluster Health"; 'TestFunction' = "Test-FailOverClusterHealthForMoc" } $mocTests += New-Object -TypeName PsObject -Property $mocFailOverClusterHealthTest # Test category "Moc Failover Cluster" $mocFailOverClusteHCIRegistrationTest = [ordered]@{ 'TestName' = "Validate Failover Cluster HCI Registration"; 'Category' = "MOC Failover Cluster"; 'Description' = "Validates Failover Cluster HCI Registration"; 'TestFunction' = "Test-FailOverClusterHCIRegistrationForMoc" } $mocTests += New-Object -TypeName PsObject -Property $mocFailOverClusteHCIRegistrationTest # Test category "Moc HyperV" $mocHyperVMCreationTest = [ordered]@{ 'TestName' = "Validate VM Creation in Hyper-V"; 'Category' = "MOC HyperV"; 'Description' = "Validates VM Creation in Hyper-V"; 'TestFunction' = "Test-VMCreationInHyperV" } $mocTests += New-Object -TypeName PsObject -Property $mocHyperVMCreationTest $mocHyperVSwitchTest = [ordered]@{ 'TestName' = "Validate switch in Hyper-V"; 'Category' = "MOC HyperV"; 'Description' = "Validates switch in Hyper-V"; 'TestFunction' = "Test-SwitchInHyperV" } $mocTests += New-Object -TypeName PsObject -Property $mocHyperVSwitchTest return $mocTests } Function PSasAdmin{ $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } Function RunPScript([String] $PSScript){ $GUID=[guid]::NewGuid().Guid $Job = Register-ScheduledJob -Name $GUID -ScheduledJobOption (New-ScheduledJobOption -RunElevated) -ScriptBlock ([ScriptBlock]::Create($PSScript)) -ArgumentList ($PSScript) -ErrorAction Stop $Task = Register-ScheduledTask -TaskName $GUID -Action (New-ScheduledTaskAction -Execute $Job.PSExecutionPath -Argument $Job.PSExecutionArgs) -Principal (New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest) -ErrorAction Stop $Task | Start-ScheduledTask -AsJob -ErrorAction Stop | Wait-Job | Remove-Job -Force -Confirm:$False While (($Task | Get-ScheduledTaskInfo).LastTaskResult -eq 267009) {Start-Sleep -Milliseconds 150} $Job1 = Get-Job -Name $GUID -ErrorAction SilentlyContinue | Wait-Job $Job1 | Receive-Job -Wait -AutoRemoveJob Unregister-ScheduledJob -Id $Job.Id -Force -Confirm:$False Unregister-ScheduledTask -TaskName $GUID -Confirm:$false } function Test-MocHostInternetConnectivity() { $ErrorActionPreference= 'silentlycontinue' $TestFailed=$false Write-Host Write-Host $mocLocMessage.moc_validation_internet_testing -ForegroundColor Yellow $PSScript = "(Invoke-WebRequest -uri 'www.microsoft.com' -UseBasicParsing).StatusCode" $TestResult = RunPScript -PSScript $PSScript if ($TestResult -eq 200){ Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.microsoft.com", $mocLocMessage.moc_validataion_succeeded)) -ForegroundColor Green }else{ $TestFailed=$true Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.microsoft.com", $mocLocMessage.moc_validataion_failed)) -ForegroundColor Red } if ($TestFailed){ Write-Host '' Write-Host '' Write-Host $mocLocMessage.moc_validation_internet_failure -ForegroundColor red Write-Host '' Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_recommend_actions_header , $mocLocMessage.moc_validation_internet_recommend_actions)) -ForegroundColor Yellow Write-Host $recommendation return [pscustomobject]@{ 'TestName' = "Validate MOC host network connectivity"; 'Category' = "MOC Host"; 'TestResult' = "Failed"; 'Details' = $mocLocMessage.moc_validation_internet_failure; 'Recommendation' = $recommendation } }else{ Write-Host '' Write-Host $mocLocMessage.moc_validation_internet_success -ForegroundColor Green Write-Host '' return [pscustomobject]@{ 'TestName' = "Validate MOC host network connectivity"; 'Category' = "MOC Host"; 'TestResult' = "Success"; 'Details' = $mocLocMessage.moc_validation_internet_success; 'Recommendation' = '' } } } function Test-MocHostLimits() { Write-Host '' $skipHostLimitChecks = (Get-MocConfig)["skipHostLimitChecks"] $workingDir = Get-ConfigurationValue -name "workingDir" -module $moduleName $result = [pscustomobject]@{ 'TestName' = "Validate MOC Host Limits"; 'Category' = "MOC Host"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = '' } if (-not $skipHostLimitChecks) { $tmpDir = $workingDir # Skip SMB path if (!$tmpDir.StartsWith("\\")) { $multiNode = Test-MultiNodeDeployment try{ if ($multiNode) { Test-CloudLimits -path $tmpDir -minRequiredDisk 40 -minRequiredMemory 10 -minRequiredLp 4 } else { Test-CloudLimits -path $tmpDir -minRequiredDisk 50 -minRequiredMemory 20 -minRequiredLp 4 } } catch { $result.TestResult = "Failed" $result.Details = $_.Exception.Message return $result } } } $result.Details = $mocLocMessage.moc_validation_host_limit_success return $result } function Test-MocHostRemoting() { Write-Host '' $skipRemotingChecks = (Get-MocConfig)["skipRemotingChecks"] $result = [pscustomobject]@{ 'TestName' = "Validate MOC Host Remoting"; 'Category' = "MOC Host"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = '' } if (-not $skipRemotingChecks) { try { Confirm-Remoting } catch { $result.TestResult = "Failed" $result.Details = $_.Exception.Message return $result } } $result.Details = $mocLocMessage.moc_validation_host_remoting_success return $result } function Test-MocNetworkConfiguration() { Write-Host '' $result = [pscustomobject]@{ 'TestName' = "Validate MOC Network Configuraiton"; 'Category' = "MOC Configuration"; 'TestResult' = "Failed"; 'Details' = ""; 'Recommendation' = '' } $multiNode = Test-MultiNodeDeployment $vnet = Get-VNetConfiguration -module $moduleName $cloudServiceCidr = (Get-MocConfig)["cloudServiceCidr"] #Netowrk Validations : #1. whether vSwitch provided in the vnet configuration exists. #2. CloudCIDR parameter is provided in case of static networks. if ($multiNode) { Get-ClusterNode -ErrorAction Stop | ForEach-Object { if ($vnet) { try { Test-HostNetworking -nodeName $_.Name -vswitchName $vnet.VswitchName } catch { $result.Details = $_.Exception.Message return $result } } } try { $testClusterRoleName = (Get-MocConfig)["testClusterRoleName"] Test-ClusterNetworkProperties -cloudServiceCidr $cloudServiceCidr -vnet $vnet -clusterRoleName $testClusterRoleName } catch { $result.Details = $_.Exception.Message return $result } } else { if ($vnet) { try { Test-HostNetworking -nodeName ($env:computername) -vswitchName $vnet.VswitchName } catch { $result.Details = $_.Exception.Message return $result } } # There is no cloud service cidr for standalone # We use the host ip for cloud service ip if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr)) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ignore_csip , $cloudServiceCidr)) } } $result.TestResult = "Success" $result.Details = "MOC network has been verified successfully" return $result } function Test-MocSDNConfiguration() { Write-Host '' $isSDNConfigured = (Get-MocConfig)["useNetworkController"] $result = [pscustomobject]@{ 'TestName' = "Validate MOC SDN Configuraiton"; 'Category' = "MOC Configuration"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = ""; } if ($isSDNConfigured) { $networkControllerFqdnOrIpAddress = (Get-MocConfig)["networkControllerFqdnOrIpAddress"] $networkControllerLbSubnetRef = (Get-MocConfig)["networkControllerLbSubnetRef"] $networkControllerLnetRef = (Get-MocConfig)["networkControllerLnetRef"] $networkControllerClientCertificateName = (Get-MocConfig)["networkControllerClientCertificateName"] $vipPoolStart = (Get-MocConfig)["vipPoolStart"] $vipPoolEnd = (Get-MocConfig)["vipPoolEnd"] $vipPool = New-VipPoolSettings -name "testPool" -vipPoolStart $vipPoolStart -vipPoolEnd $vipPoolEnd try { Confirm-NetworkControllerConfiguration -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress ` -networkControllerLbSubnetRef $networkControllerLbSubnetRef ` -networkControllerLnetRef $networkControllerLnetRef ` -networkControllerClientCertificateName $networkControllerClientCertificateName ` -vipPool $vipPool $result.Details = $mocLocMessage.moc_validation_sdn_configuration_success } catch { $result.TestResult = "Failed" $result.Details = $_.Exception.Message } } else { $result.TestResult = "Skipped" $result.Details = "SDN is not enabled" } return $result } function Test-MocDirectories() { Write-Host '' $result = [pscustomobject]@{ 'TestName' = "Validate MOC directories"; 'Category' = "MOC Configuration"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = "" } $workingDir = Get-ConfigurationValue -name "workingDir" -module $moduleName if ([string]::IsNullOrWhiteSpace($workingDir)) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir")) return $result } $cloudConfigLocation = Get-ConfigurationValue -name "cloudConfigLocation" -module $moduleName if ([string]::IsNullOrWhiteSpace($cloudConfigLocation)) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "cloudConfigLocation")) return $result } # cloudConfigLocation cannot be a parent path for workingDir # for example, workingDir = c:\test\123 and cloudConfigLocation = c:\test is invalid # During install of Moc, the system is reset including cloudConfigLocation, that would wipe of workingDir, that # contains critical PS configuration if ($workingDir.StartsWith($cloudConfigLocation.TrimEnd('\\'), $true, [System.Globalization.CultureInfo]::InvariantCulture)) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_invalid_working_dir_cloudconfig, $workingDir, $cloudConfigLocation)) return $result } $multiNode = Test-MultiNodeDeployment if ($multiNode) { #Check if the working directory corresponds to CSV root. $volumeList = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo foreach ($volume in $volumeList) { if (!$volume.SharedVolumeInfo.FriendlyVolumeName) { # If volume is not online, we will not find get the friendly volume name continue } $rootPath = $volume.SharedVolumeInfo.FriendlyVolumeName.ToLower() if (([System.IO.Path]::GetFullPath($workingDir)).ToLower() -eq $rootPath) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root)) return $result } } if ((Test-LocalFilePath -path $workingDir) ) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-workingDir")) return $result } if ((Test-LocalFilePath -path $cloudConfigLocation)) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-cloudConfigLocation")) return $result } $imageDir = Get-ConfigurationValue -name "imageDir" -module $moduleName if ((Test-LocalFilePath -path $imageDir)) { $result.TestResult = "Failed" $result.Details = $($MocLocMessage.moc_no_moc_config) return $result } } else { #Static check to ensure working directory is not System Drive or Root if (($workingDir -eq $env:SystemDrive) -or ($workingDir.TrimEnd('\\') -eq $env:SystemDrive) -or ($workingDir -eq $env:SystemRoot) -or ($workingDir.TrimEnd('\\') -eq $env:SystemRoot) -or ($workingDir -eq [System.IO.Path]::GetFullPath($env:SystemDrive))) { $result.TestResult = "Failed" $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root)) return $result } } $useStagingShare = Get-ConfigurationValue -name "useStagingShare" -type "Boolean" -module $moduleName $stagingShare = Get-ConfigurationValue -name "stagingShare" -module $moduleName if ($useStagingShare -and [string]::IsNullOrWhiteSpace($stagingShare)) { $result.TestResult = "Failed" $result.Details = $($MocLocMessage.generic_staging_share_unspecified) return $result } $result.Details = $mocLocMessage.moc_validation_directories_success_details return $result } function Test-FailOverClusterHealthForMoc() { Write-Host '' $result =[pscustomobject]@{ 'TestName' = "Validate Failover Cluster Health"; 'Category' = "MOC Failover Cluster"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = '' } $failoverCluster = Get-FailoverCluster if ($null -ne $failoverCluster) { try { Test-ClusterHealth $result.Details = $mocLocMessage.moc_validation_failover_cluster_healthy } catch { $result.TestResult = "Failed" $result.Details = $_.Exception.Message } } else { $result.TestResult = "Skipped" $result.Details = $mocLocMessage.moc_validation_failover_cluster_skip } return $result } function Test-FailOverClusterHCIRegistrationForMoc() { Write-Host '' $result = [pscustomobject]@{ 'TestName' = "Validate Failover Cluster HCI Registration"; 'Category' = "MOC Failover Cluster"; 'TestResult' = "Success"; 'Details' = ""; 'Recommendation' = '' } $failoverCluster = Get-FailoverCluster if ($null -ne $failoverCluster) { try { Test-HCIRegistration $result.Details = $mocLocMessage.moc_validation_hci_registration_success } catch { $result.TestResult = "Failed" $result.Details = $_.Exception.Message } } else { $result.TestResult = "Skipped" $result.Details = $mocLocMessage.moc_validation_failover_cluster_skip } return $result } function Test-VMCreationInHyperV() { Write-Host '' return [pscustomobject]@{ 'TestName' = "Validate VM Creation in Hyper-V"; 'Category' = "MOC HyperV"; 'TestResult' = "Not Implemented"; 'Details' = ""; 'Recommendation' = '' } } function Test-SwitchInHyperV() { Write-Host '' return [pscustomobject]@{ 'TestName' = "Validate switch in Hyper-V"; 'Category' = "MOC HyperV"; 'TestResult' = "Not Implemented"; 'Details' = ""; 'Recommendation' = '' } } function Test-DHCPEnabledInterfaceGreaterThanZero { <# .DESCRIPTION Returns true if number of DHCP enabled network interface card for ClusterAndClient network is greater than 0 Else return false #> $networkList = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { $_.Role -eq "ClusterAndClient" } $dhcpInterfaceCount = 0 foreach($network in $networkList) { if ((Get-ClusterNetworkInterface -Network $($network.Name) | Select-Object -expandProperty "DhcpEnabled") -eq 1) { $dhcpInterfaceCount += 1 } } return ($dhcpInterfaceCount -gt 0) } function Test-ClusterNetworkProperties { <# .DESCRIPTION This function validates cloud service CIDR for both V1 and V2 failover cluster creation workflow. We assume that this function is called only for multinode setups: Validations specific to Multinode setups (Common to both V1, V2): 1. CIDR should be part of a cluster network. 2. The prefix length should match that of the cluster network. 3. Cloud service CIDR should not overlap with VIP pool, or k8snodepool IP addresses provided during VNET configuration. Validations Specific to V1 creation workflow: 1. CIDR must be provided if the machine is not DHCP not enabled. 2. Can create a failover cluster resource if the given cluster role name is not empty Validations specific to V2 creation workflow: 1. Cluster Group's Network name and DNS name must be online and available. .PARAMETER CloudServiceCIDR The static IP/network prefix to be assigned to the MOC CloudAgent service. .PARAMETER vnet A VirtualNetwork object created using the New-AksHciNetworkSetting cmdlet. .PARAMETER clusterRoleName Name of the cluster group (Example: ca-2f87825b-a4af-473f-8a33-8e3bdd5f9b61) #> param ( [string] $cloudServiceCidr, [virtualNetwork] $vnet, [string] $clusterRoleName ) $isSDNConfigured = (Get-MocConfig)["useNetworkController"] if ($isSDNConfigured) { $vipPoolStart = (Get-MocConfig)["vipPoolStart"] $vipPoolEnd = (Get-MocConfig)["vipPoolEnd"] } elseif ($vnet) { $vipPoolStart = $vnet.VipPoolStart $vipPoolEnd = $vnet.VipPoolEnd } #Checks against cloudServiceIP if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr)) { $cloudServiceCidrArray = $cloudServiceCidr.Split("/") $cloudServiceIP = $cloudServiceCidrArray[0] if ($cloudServiceCidrArray.Length -gt 1) { $prefixLength = $cloudServiceCidrArray[1] } #Check if Cloud Service IP is in valid format. if (-Not [System.Net.IPAddress]::TryParse($cloudServiceIP, [ref]$null)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, ` $MocLocMessage.moc_invalid_csip, $cloudServiceIP)), ([ErrorTypes]::IsUserErrorFlag)) } #Check if cloud service IP is already in use if (Test-NetConnection $cloudServiceIP -InformationLevel "Quiet" -ErrorAction Ignore -WarningAction SilentlyContinue) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, ` $MocLocMessage.moc_csip_in_use, $cloudServiceIP)), ([ErrorTypes]::IsUserErrorFlag)) } } $dhcpEnabledCluster = $True $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) ` -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths $clusterNetworkList = "" $useUpdateFailoverClusterCreationFlow = (Get-MocConfig)["useUpdatedFailoverClusterCreationLogic"] # We only run this test if customer does not have any DHCP enabled network interface if (-not (Test-DHCPEnabledInterfaceGreaterThanZero)) { foreach ($clusterNetwork in $clusterNetworks) { for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { #Multiple interfaces can be linked to a cluster network. if ((Get-ClusterNetworkInterface -Network $($clusterNetwork.Name) | Select-Object -expandProperty "DhcpEnabled") -ne 1) { $dhcpEnabledCluster = $False $clusterNetworkList += "$($clusterNetwork.IPv4Addresses[$i])/$($clusterNetwork.Ipv4PrefixLengths[$i]) " if (!($useUpdateFailoverClusterCreationFlow)) { #Static Network + Cloud Service CIDR not provided -> Throw error #If ClusterRoleName is empty, it means that customer provided their own clusterRoleName if ([string]::IsNullOrWhiteSpace($cloudServiceCidr) -and [string]::IsNullOrEmpty($clusterRoleName)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, ` $MocLocMessage.moc_ca_cidr_required, $clusterNetwork.IPv4Addresses[$i])), ([ErrorTypes]::IsUserErrorFlag)) } } } } } } #Cloud Service CIDR is provided -> cluster network and prefix check. if(-not [string]::IsNullOrWhiteSpace($cloudServiceCidr)) { $foundInClusterNetwork = $False #Check if Cloud service CIDR is part of Cluster network foreach ($clusterNetwork in $clusterNetworks) { for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) { [System.Net.IPAddress]$ipv4 = $null $clusIpv4 = $clusterNetwork.Ipv4Addresses[$i] if (-Not [System.Net.IPAddress]::TryParse($clusIpv4, [ref] $ipv4)) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_ignore_failover_ip , $clusIpv4)) continue } $lastIp = [AKSHCI.IPUtilities]::GetLastIpInCidr($ipv4, $clusterNetwork.Ipv4PrefixLengths[$i]) if([AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $ipv4) -ge 0 -AND [AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $lastIp) -le 0) { $foundInClusterNetwork = $True #The cloud service CIDR is in the range of the cluster network IP! Compare prefix lengths now! $prefixLength = $clusterNetwork.Ipv4PrefixLengths[$i] break } } } #DHCP Network + Cloud Service CIDR provided -> Give warning and ignore if($dhcpEnabledCluster -eq $True) { Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dhcp_network , $cloudServiceCidr)) } #Not DHCP cluster, and not found in network. elseif ($foundInClusterNetwork -ne $True) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, ` $MocLocMessage.moc_csip_not_in_network, $cloudServiceCidr, $clusterNetworkList)), ([ErrorTypes]::IsUserErrorFlag)) } #Not DHCP cluster, found in network, but not same prefix length. elseif ($cloudServiceCidrArray.Length -gt 1) { if ($cloudServiceCidrArray[1] -ne $prefixLength) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_invalid_prefix_len, $cloudServiceCidr, $cloudServiceCidrArray[1], $clusIpv4, $prefixLength)), ([ErrorTypes]::IsUserErrorFlag)) } } } #CloudService CIDR should not overlap with VIP pool or k8s node pool if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr) -and ($vnet)) { if([AKSHCI.IPUtilities]::CheckIPInIPPool($vipPoolStart, $vipPoolEnd, $cloudServiceIP)) { throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_in_pool, $cloudServiceIP, $vipPoolStart, $vipPoolEnd)), ([ErrorTypes]::IsUserErrorFlag)) } if(-not [string]::IsNullOrWhiteSpace($vnet.K8snodeIPPoolStart)) { if([AKSHCI.IPUtilities]::CheckIPInIPPool($vnet.K8snodeIPPoolStart, $vnet.K8snodeIPPoolEnd, $cloudServiceIP)){ throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_in_pool, $cloudServiceIP, $vnet.K8snodeIPPoolStart, $vnet.K8snodeIPPoolEnd)), ([ErrorTypes]::IsUserErrorFlag)) } } } # Failover cluster resource tests if ($useUpdateFailoverClusterCreationFlow) { Test-FailoverClusterDefaultGroup } if (!($useUpdateFailoverClusterCreationFlow) -and !([string]::IsNullOrEmpty($clusterRoleName))) { Test-FailoverClusterResourceCreate -cloudServiceCIDR $cloudServiceCidr -prefix $prefixLength ` -dhcpEnabled $dhcpEnabledCluster -clusterGroupName $clusterRoleName } } function Test-FailoverClusterDefaultGroup { <# .DESCRIPTION This test is only being run when we are not creating failover cluster network resource and it checks two things: 1) The "Cluster Group's" DNS Network name is online and reachable 2) All of cluster group's IP Address must be online Pre-requisites for calling this function 1. Multinode setup #> $clusterGroupUUID = Get-ClusterGroupUUID $clusterNameResourceUUID = Get-ClusterNameResourceUUID $clusterGroup = Get-ClusterGroup -Name "$clusterGroupUUID" -ErrorAction SilentlyContinue if ($clusterGroup -eq $null) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_failover_cluster_group_not_found)) } # We need to ensure the cluster group's DNS Network Name is reachable $dnsName = (Get-ClusterGroup -name "$clusterGroupUUID" -ErrorAction Stop | Get-ClusterResource -Name "$clusterNameResourceUUID" -ErrorAction Stop | Get-ClusterParameter -Name "DnsName" -ErrorAction Stop).Value $dnsSuffix = (Get-ClusterGroup -name "$clusterGroupUUID" -ErrorAction Stop | Get-ClusterResource -Name "$clusterNameResourceUUID" -ErrorAction Stop| Get-ClusterParameter -Name "DnsSuffix" -ErrorAction Stop).Value $dnsNetworkName = ($dnsName + "." + $dnsSuffix) if (!(Test-NetConnection -ComputerName $dnsNetworkName -InformationLevel Quiet)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cluster_group_network_name_unreachable, $dnsNetworkName, $clusterGroupUUID)) } # we need to ensure all cluster IP address is reachable $IpAddressList = Get-ClusterGroup -name "$clusterGroupUUID" -ErrorAction Stop | Get-ClusterResource -ErrorAction Stop | Where-Object {$_.ResourceType -eq "IP Address"} if ($IpAddressList.length -eq 0) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_available_cluster_ip_address)) } foreach ($ipAddressResource in $IpAddressList) { $ipAddress = ($ipAddressResource | Get-ClusterParameter -name "address" -ErrorAction Stop).value if (!(Test-NetConnection -ComputerName $ipAddress -InformationLevel Quiet)) { throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ip_address_unreachable, $ipAddress, $ipAddress)) } } } function Test-FailoverClusterResourceCreate { <# .DESCRIPTION This checks if we are able to create and start Failover Cluster resource. Pre-requisites for calling this function : 1. Multi node setup 2. Cloud service CIDR is validated, and sent with prefix 3. Cluster Network check has already been done to determine if DHCP is enabled on all interfaces. .PARAMETER cloudServiceCIDR The static IP/network prefix to be assigned to the MOC CloudAgent service. .PARAMETER prefix The prefix length of the Cloud .PARAMETER dhcpEnabled This parameter denotes if DHCP is enabled on all the interfaces of the cluster network .PARAMETER clusterGroupName Name of the cluster group (Example: ca-2f87825b-a4af-473f-8a33-8e3bdd5f9b61) #> param ( [string] $cloudServiceCIDR, [string] $prefix, [bool] $dhcpEnabled, [string] $clusterGroupName ) try { Add-ClusterGroup -Name $clusterGroupName -GroupType GenericService -ErrorAction Stop | Out-Null $dnsName = Add-ClusterResource -Name "$clusterGroupName" -ResourceType "Network Name" -Group $clusterGroupName -ErrorAction Stop $dnsName | Set-ClusterParameter -Multiple @{"Name"="$clusterGroupName";"DnsName"="$clusterGroupName"} -ErrorAction Stop # 1. Create and Start the resources in order - IP resource, Service and then start the Resource Group if ([string]::IsNullOrWhiteSpace($cloudServiceCIDR)) { # 1.a DHCP Case $networkList = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { $_.Role -eq "ClusterAndClient" } foreach ($network in $networkList) { $IPResourceName = "IPv4 Address on $($network.Address)" $IPAddress = Add-ClusterResource -Name $IPResourceName -ResourceType "IP Address" -Group $clusterGroupName -ErrorAction Stop $IPAddress | Set-ClusterParameter -Multiple @{"Network"=$network;"EnableDhcp"=1} -ErrorAction Stop Add-ClusterResourceDependency -Resource "$clusterGroupName" -Provider $IPResourceName -ErrorAction Stop | Out-Null try { Start-FailoverClusterResource -resourceName $IPResourceName | Out-Null } catch { $errorMessage = Write-ModuleEventException -message "Start-FailoverClusterResource failed on resource $IPResourceName" -exception $_ -moduleName $global:MocModule Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_failover_cluster_networks_error, $errorMessage), $_.Exception)) } } } else { # We ignore Cloud service CIDR - no need to add the IP param if($dhcpEnabled -eq $False) { $cloudServiceCidrArray = $cloudServiceCIDR.Split("/") $subnetMask = Get-Ipv4MaskFromPrefix -PrefixLength $prefixLength $IPResourceName = "IPv4 Address $cloudServiceCidrArray[0]" $IPAddress = Add-ClusterResource -Name $IPResourceName -ResourceType "IP Address" -Group $clusterGroupName -ErrorAction Stop $IPAddress | Set-ClusterParameter -Multiple @{"Address"=$cloudServiceCidrArray[0];"SubnetMask"=$subnetMask;"EnableDhcp"=0} -ErrorAction Stop Add-ClusterResourceDependency -Resource "$clusterGroupName" -Provider $IPResourceName -ErrorAction Stop | Out-Null try { Start-FailoverClusterResource -resourceName $IPResourceName | Out-Null } catch { $errorMessage = Write-ModuleEventException -message "Start-FailoverClusterResource failed." -exception $_ -moduleName $global:MocModule Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_invalid_ip_address, $($cloudServiceCidrArray[0]), $errorMessage), $_.Exception)) } } } #Start the failover cluster group try { Start-FailoverClusterGroup -clusterGroupName $clusterGroupName | Out-Null } catch [Exception] { Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_cluster_perm, $clusterGroupName, $($_.Exception.Message.ToString()))) } } finally { <#Do this after the try block regardless of whether an exception occurred or not#> Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore } } #endregion # SIG # Begin signature block # MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCHcfCmn0IJVapb # 4i3xQ8s4a7fFz6IvmvqH+5fqVURJB6CCDYUwggYDMIID66ADAgECAhMzAAADTU6R # phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+ # Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd # j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k # bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x # zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH # g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD # YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT # 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq # 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY # 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN # fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv # XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE # usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH # Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z # ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab # rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGAX # uw5F/Vd4c5tG4y4iGxhbEcUwfk9c4NzHFpDpMrlaMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAENTVjMfEwiFMnctoTiJlXXruOv03MGKkKsg6 # 8+WxPUarRkqbbx/kFNgHUZ7Fy+CDUevgsCVLR2EhU2wWMfKH8BWyVtbrJCDsI6XE # fo3lnt+6jWFexkGcRiPXclJJe48JiKY3Qz7fGmc6Qz9/w/fd9Isq2VNJIKg9v+wY # 1/+lhU85qrXasjBGs/R2Pp3sO7koXXe4Ff1MD9cZ0dfMa/tAj7mDFAnk+JYiYtRN # 1L23/8SLr/MhUNNyUP0Jp6LBDfLD144lCQJmNbrq/iCKci7fORzkg3Xs/rzbuEI0 # 6ll7Sqr86CBawAtx+h1tOyFj47gO8Iymigf9aYqVkgh3m+ckq6GCFykwghclBgor # BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCCAhTr+whn8s6mMdX0FsQVqKILoZGoEU4QY # QRhW9sx28wIGZD/Te/HSGBMyMDIzMDUxNTIwNDYxMS42MTNaMASAAgH0oIHYpIHV # MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT # HVRoYWxlcyBUU1MgRVNOOjg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3Nv # ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG3ISca # B6IqhkYAAQAAAbcwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTAwHhcNMjIwOTIwMjAyMjE0WhcNMjMxMjE0MjAyMjE0WjCB0jELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z # b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMf9 # z1dQNBNkTBq3HJclypjQcJIlDAgpvsw4vHJe06n532RKGkcn0V7p65OeA1wOoO+8 # NsopnjPpVZ8+4s/RhdMCMNPQJXoWdkWOp/3puIEs1fzPBgTJrdmzdyUYzrAloICY # x722gmdpbNf3P0y5Z2gRO48sWIYyYeNJYch+ZfJzXqqvuvq7G8Nm8IMQi8Zayvx+ # 5dSGBM5VYHBxCEjXF9EN6Qw7A60SaXjKjojSpUmpaM4FmVec985PNdSh8hOeP2tL # 781SBan92DT19tfNHv9H0FAmE2HGRwizHkJ//mAZdS0s6bi/UwPMksAia5bpnIDB # OoaYdWkV0lVG5rN0+ltRz9zjlaH9uhdGTJ+WiNKOr7mRnlzYQA53ftSSJBqsEpTz # Cv7c673fdvltx3y48Per6vc6UR5e4kSZsH141IhxhmRR2SmEabuYKOTdO7Q/vlvA # fQxuEnJ93NL4LYV1IWw8O+xNO6gljrBpCOfOOTQgWJF+M6/IPyuYrcv79Lu7lc67 # S+U9MEu2dog0MuJIoYCMiuVaXS5+FmOJiyfiCZm0VJsJ570y9k/tEQe6aQR9MxDW # 1p2F3HWebolXj9su7zrrElNlHAEvpFhcgoMniylNTiTZzLwUj7TH83gnugw1FCEV # Vh5U9lwNMPL1IGuz/3U+RT9wZCBJYIrFJPd6k8UtAgMBAAGjggFJMIIBRTAdBgNV # HQ4EFgQUs/I5Pgw0JAVhDdYB2yPII8l4tOwwHwYDVR0jBBgwFoAUn6cVXQBeYl2D # 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l # LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD # ggIBAA2dZMybhVxSXTbJzFgvNiMCV5/Ayn5UuzJU495YDtcefold0ehR9QBGBhHm # AMt10WYCHz2WQUyM3mQD4IsHfEL1JEwgG9tGq71ucn9dknLBHD30JvbQRhIKcvFS # nvRCCpVpilM8F/YaWXC9VibSef/PU2GWA+1zs64VFxJqHeuy8KqrQyfF20SCnd8z # RZl4YYBcjh9G0GjhJHUPAYEx0r8jSWjyi2o2WAHD6CppBtkwnZSf7A68DL4OwwBp # mFB3+vubjgNwaICS+fkGVvRnP2ZgmlfnaAas8Mx7igJqciqq0Q6An+0rHj1kxisN # dIiTzFlu5Gw2ehXpLrl59kvsmONVAJHhndpx3n/0r76TH+3WNS9UT9jbxQkE+t2t # hif6MK5krFMnkBICCR/DVcV1qw9sg6sMEo0wWSXlQYXvcQWA65eVzSkosylhIlIZ # ZLL3GHZD1LQtAjp2A5F7C3Iw4Nt7C7aDCfpFxom3ZulRnFJollPHb3unj9hA9xvR # iKnWMAMpS4MZAoiV4O29zWKZdUzygp7gD4WjKK115KCJ0ovEcf92AnwMAXMnNs1o # 0LCszg+uDmiQZs5eR7jzdKzVfF1z7bfDYNPAJvm5pSQdby3wIOsN/stYjM+EkaPt # Uzr8OyMwrG+jpFMbsB4cfN6tvIeGtrtklMJFtnF68CcZZ5IAMIIHcTCCBVmgAwIB # AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0 # IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1 # WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O # 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn # hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t # 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq # D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP # frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW # rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv # 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb # r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten # IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc # xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a # j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB # MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU # n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw # QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E # b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/ # MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ # oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p # Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB # BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v # Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h # LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x # 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p # y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A # oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC # HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB # 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt # yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3 # rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV # v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24 # 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw # Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB # 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk # TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAyGdBGMObODlsGBZm # SUX2oWgfqcaggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN # BgkqhkiG9w0BAQUFAAIFAOgMl7IwIhgPMjAyMzA1MTUxOTM2MThaGA8yMDIzMDUx # NjE5MzYxOFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA6AyXsgIBADAHAgEAAgIT # PjAHAgEAAgIR2zAKAgUA6A3pMgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE # AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB # AAQ1FZrGPTaQZxdiGu3OYgldSZ8U0VMifMyDaIGK/bpa8DryeHo+DoUw1fQsrrmF # 3ZbvAof4BeeAy/v5pGNxVORmNC3F9NKxIHmKOhHJg7kuiqThfeN04CT565nXZgqC # bQsfDWkpoKEB4UQLqtTCCidy1n5hJ55H/e8ZTmzWNObdMYIEDTCCBAkCAQEwgZMw # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG3IScaB6IqhkYAAQAA # AbcwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB # BDAvBgkqhkiG9w0BCQQxIgQguKwKssWr8nJ8ZLkIxfMVlvLk42k6xVuTxYBmGG28 # 22EwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBsJ3jTsh7aL8hNeiYGL5/8 # IBn8zUfr7/Q7rkM8ic1wQTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAABtyEnGgeiKoZGAAEAAAG3MCIEIK4DbGq83bRAt0L1NcANy/PF # aVN6HHh/OvIYB93C/8DJMA0GCSqGSIb3DQEBCwUABIICAKgVAYyDitdZ1DQwPIdx # z0jfAur9f3FzZuGlrHwF+et6VSebzvuYiLSP2QqpAe7LaOsSdCumXHLpak9Eup66 # M7hwDfX8d3o4yFb9iIgl2RxoHrdjhc9qdk8vNGeKglupNcC5Ub5Q1MU5grfZ0Rxn # g0mSsAIGjRt61KUM2qBMCrgZiRzQKst6rfKeStkuS1MErWhwLEtJViBTEGzV10gx # Ya/3XXYRWJUYojkW5wPaHTlkWL3zGo/qqhLLHqwPAq+lj4QQQ1yYo27ynAEwI0NG # 4JTUNRahFVq0u2pNpABRrprh88HidG5BuqdRzJPpExiywsErGebeSpbNNnAfnfwB # /9J5Snk1uFMfOcaiouVeM7FDv4FS7zQ5n6cYQp6huPeRGV8MTMXACju5Xx4SX0Dv # SXf/uk39ucvWUq5pJeSWZN06wRaxZTqz33lVCKP543twHJt2113M82Urp4xX99Db # lYonLmSPL27XBfIPnmD32VaOVomUQBIBDHyUHVcM3jJpl4Bpkee83Wp+YnypMCQB # 06UnHuzQzS2pDmwbTKRj0SX1C/7NC18HRbFNwf44Ph94MGIH74/TU0rXoDclhq6a # IVh3VDYdOgXrQ7iU8QyiVYJrKR5P8zJVRD7CLIln7/SdNw62dEz2iiUmu/Op6pJ1 # s36JI6rCeDKXNBbUK9TnvH20 # SIG # End signature block |