Obs/bin/ObsDep/content/Powershell/Roles/Common/RoleHelpers.psm1
<###################################################
# # # Copyright (c) Microsoft. All rights reserved. # # # ##################################################> Import-Module $PSScriptRoot\JustEnoughAdministrationHelpers.psm1 -DisableNameChecking -Verbose:$false | Out-Null Import-Module NetworkControllerRESTWrappers -DisableNameChecking -Verbose:$false | Out-Null Import-Module $PSScriptRoot\NetworkControllerWorkloadHelpers.psm1 -DisableNameChecking -Verbose:$false | Out-Null # Below 4 modules will ues -ErrorAction SilentlyContinue as the import might happen at the dependency repo code and those might not package the CloudCommon modules. # In such case, the functionnality is not impacted as CloudCommon is always copied into Windows PowerShell module at the beginning of action plan exectuion Import-Module $PSScriptRoot\..\..\Common\Helpers.psm1 -DisableNameChecking -Verbose:$false -ErrorAction SilentlyContinue | Out-Null Import-Module $PSScriptRoot\..\..\Common\NetworkHelpers.psm1 -DisableNameChecking -Verbose:$false -ErrorAction SilentlyContinue | Out-Null Import-Module $PSScriptRoot\..\..\Common\StorageHelpers.psm1 -DisableNameChecking -Verbose:$false -ErrorAction SilentlyContinue | Out-Null Import-Module $PSScriptRoot\..\..\Common\ClusterHelpers.psm1 -DisableNameChecking -Verbose:$false -ErrorAction SilentlyContinue | Out-Null Import-LocalizedData LocalizedStrings -FileName Roles.Strings.psd1 -ErrorAction SilentlyContinue Import-LocalizedData LocalizedNetworkData -FileName Network.Strings.psd1 -ErrorAction SilentlyContinue function Test-PSSession { Param( [Parameter(Mandatory=$true)] $RemoteSession, [Parameter(Mandatory=$true)] $SessionCredential, [Parameter(Mandatory=$true)] $SessionComputer ) if (($RemoteSession -eq $null) -or ($RemoteSession.State -ne "Opened")) { Trace-Execution "Session not ready, state: $($RemoteSession.State)" Trace-Execution "Removing the session" $RemoteSession | Remove-PSSession Trace-Execution "Regenerating the session" $RemoteSession = New-PSSession -ComputerName $SessionComputer -Credential $SessionCredential Initialize-ECESession $RemoteSession Trace-Execution "State after regenerate = $($RemoteSession.State)" } else { Trace-Execution "Session Opened" } return $RemoteSession } # Make sure the MAC addresses and IPv4Address are loaded into Parameters object from the manifest. function Set-MacAndIPAddressSingleNode { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory=$true)] [PSObject] $Node, [ValidateSet('PXE','Management')] [string] $IPv4AddressSource = 'PXE' ) if (-not $node.MacAddress) { Trace-Execution "Failed to find MAC address for node '$($node.Name)' in the manifest. Skip this" } else { $macAddress = $node.MacAddress Trace-Execution "Normalizing MAC address '$macAddress'." # Normalize MAC address to .NET canonical form, e.g., E4-1D-2D-1D-25-30. $node.MacAddress = $node.MacAddress.ToUpper().Replace(':', '-') } $allHostNodes = Get-NetworkMgmtIPv4FromECEForAllHosts -Parameters $Parameters $ipv4Address = $allHostNodes[$node.Name] Trace-Execution "Setting IPv4 address $ipv4Address for node $($node.Name) ." if(-not $node.IPv4Address) { $node | Add-Member -MemberType NoteProperty -Name "IPv4Address" -value $Ipv4Address } else { $node.IPv4Address = $ipv4Address } } function Set-MacAndIPAddress { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory = $true)] [System.Xml.XmlElement] $PhysicalMachinesRole, [ValidateSet('PXE','Management')] [string] $IPv4AddressSource = 'PXE', [Parameter(Mandatory=$false)] [System.Xml.XmlElement] $Node = $null ) Trace-ECEScript "Setting IP addresses." { if ($Node) { Set-MacAndIPAddressSingleNode -Parameters $Parameters -Node $node -IPv4AddressSource $IPv4AddressSource } else { foreach ($node in $PhysicalMachinesRole.Nodes.Node) { Set-MacAndIPAddressSingleNode -Parameters $Parameters -Node $node -IPv4AddressSource $IPv4AddressSource } } } } function Get-BareMetalCredential { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $cloudRole = $Parameters.Roles['Cloud'].PublicConfiguration # Account info $securityInfo = $cloudRole.PublicInfo.SecurityInfo $bareMetalUser = $securityInfo.HardwareUsers.User | Where-Object -Property Role -EQ 'BareMetalAdmin' $bareMetalCredential = $Parameters.GetCredential($bareMetalUser.Credential) return $bareMetalCredential } # Gets the current active Domain Controller computer name. function Get-AvailableADComputerName { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String[]] $AllADComputerName, [Parameter(Mandatory = $false)] [PSCredential] $RemoteServiceCredentials = $null ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Trace-ECEScript "Getting the current active Domain Controller computer name." { # Proactively look for AD running on local machine (in case of multi-node, this will return the DVM). $service = Get-Service -Name "NTDS" -ErrorAction SilentlyContinue if($service -and $service.Status -eq "Running") { $activeADComputer = $env:COMPUTERNAME } else { # In case of one Node or post deployment, this will return DC01. foreach($adComputer in $AllADComputerName) { if ($RemoteServiceCredentials) { try { $psSession = New-PsSession -Credential $RemoteServiceCredentials -ComputerName $adComputer -ErrorAction SilentlyContinue if($psSession) { $service = Invoke-Command -Session $psSession -ScriptBlock { Get-Service -Name "NTDS" -ErrorAction SilentlyContinue } } } finally { if ($psSession) { Remove-PsSession $psSession } } } else { $service = Invoke-Command -ComputerName $adComputer -ScriptBlock { Get-Service -Name "NTDS" -ErrorAction SilentlyContinue } -ErrorAction SilentlyContinue } if ($service -and $service.Status -eq "Running") { $activeADComputer = $adComputer break } } } } if($activeADComputer) { Trace-Execution "Found AD running on $activeADComputer" return $activeADComputer } else { Trace-Error "No Available AD computer found." } } # Virtual Network Common function Test-NetworkMap { param ( [Parameter(Mandatory=$true)] $NetworkMap, [Parameter(Mandatory=$false)] [UInt32] $RetryCount = 0, [Parameter(Mandatory=$false)] [UInt32] $RetryIntervalInSeconds = 60, [Parameter(Mandatory=$false)] [string[]] $NodeNames ) Trace-ECEScript $LocalizedNetworkData.CheckNICConnectivity { $nodesToCheck = $NetworkMap.Keys if ($NodeNames) { $nodesToCheck = $nodesToCheck | Where-Object {$_ -in $NodeNames} } $retry = 0 do { try { # Test nic connectivity for each machine foreach ($nodeName in ($nodesToCheck | Sort-Object)) { $node = $NetworkMap.$nodeName $hasPingableConnections = $false foreach ($nicId in ($node.Keys | Sort-Object)) { $nic = $NetworkMap.$nodeName.$nicId try { # This cast will fail if the IP address isn't well-formed $ipAddress = [ipaddress]$nic.IPv4Address.Split('/')[0] $canPing = Test-IPConnection $ipAddress.IPAddressToString } catch { $canPing = $false } if ($canPing) { $hasPingableConnections = $true } $nic | Add-Member -MemberType NoteProperty -Name CanPing -Value $canPing Trace-Execution "$(if ($canPing) {'+'} else {'-'}) $nodeName | $($nic.Name)" } if (-not $hasPingableConnections) { throw ($LocalizedNetworkData.NoPingableConnections -f $nodeName) } } break } catch { if($retry -eq $RetryCount) { throw } Trace-Execution "Test-NetworkMap failed. Will retry in $RetryIntervalInSeconds seconds. Retry attempted: $retry." Start-Sleep -Seconds $RetryIntervalInSeconds } $retry++ } while ($retry -le $RetryCount) } } function Get-IsVirtualNetworkAlreadyConfigured { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] $NetworkMap, [Parameter(Mandatory=$true)] $IsOneNode ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Check that all non-Deployment vNICs can be pinged. $notPingableAdapters = $NetworkMap.Values.Values | ? CanPing -ne $true if (-not $notPingableAdapters) { Trace-Execution $LocalizedNetworkData.NetworkingAlreadyConfigured return $true } return $false } # BUGBUG: This function may not needed any more in ASZ. Leave it here to not break existing code. Need refactoring. function Get-NetworkMap { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, $PhysicalMachinesRole ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $nodeNames = $PhysicalMachinesRole.Nodes.Node | % Name Trace-ECESCript "Get Network Map for nodes $nodeNames" { # If multiple nodes are being added at the same time, only test those that are being deployed by the current action plan instance $executionRoleName = $Parameters.Context.ExecutionContext.Roles.Role.RoleName $hostsList = $Parameters.Context.ExecutionContext.Roles.Role.Nodes.Node.Name if ($executionRoleName -ieq "Cluster") { $hostsList = $null $hostsList = [array]($Parameters.Context.ExecutionContext.Roles.Role.Nodes.Node.PhysicalNodes.PhysicalNode.Name) } $networkmap = @{} $allHostNodes = Get-NetworkMgmtIPv4FromECEForAllHosts -Parameters $Parameters foreach ($node in $PhysicalMachinesRole.Nodes.Node) { if (-not($node.OperationStatus -eq "RequiresRemediation") -and (-not($node.OperationStatus -eq "Adding") -or ($hostsList -contains $node.Name))) { # there would be only 1 IP on each host that may need checking. $nodeNics = @{} $nicIPv4 = $allHostNodes[$node.Name] $nicName = $node.Name if (-not (Test-NetworkIPv4Address -IPv4Address $nicIPv4)) { Trace-Execution "[Get-NetworkMap]: IPv4 Address for node $($node.Name) $nicIPv4 is not with expected format, skip." continue } $nicData = New-Object PSObject -Property @{ Name = $nicName IPv4Address = $nicIPv4 } $nodeNics.$nicName = $nicData $networkMap.($node.Name) = $nodeNics } } } return $networkMap } function Wait-VirtualNetwork { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, $PhysicalMachinesRole, [Boolean] $IsIdempotentRun = $true, [Parameter(Mandatory=$false)] [string] $UpdateNodeName ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Trace-ECEScript "Waiting for Virtual Network to be configured on the host." { $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $securityInfo = $cloudRole.PublicInfo.SecurityInfo $localAdminUser = $securityInfo.LocalUsers.User | ? Role -EQ $Parameters.Configuration.Role.PrivateInfo.Accounts.BuiltInAdminAccountID $credential = $Parameters.GetCredential($localAdminUser.Credential) # in the case of Bare Metal the hosts, it is possible that the DSC hasn't completed the configuration for the hosts at this point, # so add some retry logic to test the network connectivity $networkMap = Get-NetworkMap -Parameters $Parameters -PhysicalMachinesRole $PhysicalMachinesRole if ($UpdateNodeName) { Test-NetworkMap -NetworkMap $networkMap -RetryCount 10 -NodeNames @($UpdateNodeName) } else { Test-NetworkMap -NetworkMap $networkMap -RetryCount 10 } $nodes = $PhysicalMachinesRole.Nodes.Node $nodeNames = $nodes | ForEach-Object Name $jobs = @() $isOneNode = $false if ($UpdateNodeName) { # For update, only one host is service at a time and this should not get confused with One Node MAS environment $nodes = $nodes | Where-Object Name -eq $UpdateNodeName } elseif ($nodeNames.Count -eq 1 ) { $isOneNode = $true # MASD One Node will always be run locally $nodeNames = 'LocalHost' } if ($IsIdempotentRun) { if (Get-IsVirtualNetworkAlreadyConfigured -NetworkMap $networkMap -IsOneNode $isOneNode) { return } } $allHostNodes = Get-NetworkMgmtIPv4FromECEForAllHosts -Parameters $Parameters if ($env:OSImageType -ne "ISO") { Trace-ECEScript "Start PowerShell jobs to retrieve and set the DSC state on all nodes $nodeNames." { foreach ($node in $nodes) { # Collect information about the state of the recently deployed physical host. $managementIpAddress = $allHostNodes[$node.Name] $nodeName = $node.Name Trace-Execution "Sending DSC query to $managementIpAddress for $nodeName" $jobs += Start-Job -ScriptBlock { $retVal = $false $logText = "DSC report for node $(($using:Node).Name)`r`n" # Wait as much as ten minutes for the exiting DSC configuration to complete. This is just a best effort. for ($i = 0; $i -lt 40; $i++) { $cimSession = $null $result = $null try { # This call may fail if it can't connect to the remote host when remote host is configuring vmswitch and host vnic. $cimSession = New-CimSession -ComputerName $Using:nodeName -Credential $using:credential -ErrorAction SilentlyContinue if ($null -eq $cimSession) { $cimSession = New-CimSession -ComputerName $using:managementIpAddress -Credential $using:credential -ErrorAction Stop } # This call will throw exception if there is currently a DSC config running on the host. # we use it as a way as an indication. $logText += "Calling Get-DscConfigurationStatus at attempt #$i. `r`n" $result = Get-DscConfigurationStatus -CimSession $cimSession -ErrorAction Stop break } catch { Start-Sleep -Seconds 15 $logText += "Get-DscConfigurationStatus failed with exception ($($_.Exception.Message)) at attempt #$i `r`n" } finally { if ($cimSession) { Remove-CimSession $cimSession } } } $logText += "Trying to make sure host DSC configuration is completed. `r`n" for ($i = 0; $i -lt 5; $i++) { $cimSession = $null try { $cimSession = New-CimSession -ComputerName $Using:nodeName -Credential $using:credential -ErrorAction SilentlyContinue if ($null -eq $cimSession) { $cimSession = New-CimSession -ComputerName $using:managementIpAddress -Credential $using:credential -ErrorAction Stop } $logText += "Calling Start-DscConfiguration at attempt #$i `r`n" Start-DscConfiguration -UseExisting -Wait -CimSession $cimSession -Verbose -Force -ErrorAction Stop $logText += "Calling Get-DscConfigurationStatus at attempt #$i. `r`n" $result = Get-DscConfigurationStatus -CimSession $cimSession -ErrorAction Stop $logText += "Calling Get-DscConfiguration at attempt #$i. `r`n" $logText += (Get-DscConfiguration -CimSession $cimSession -Verbose -ErrorAction Stop | out-string) } catch { # While DSC configuration on the host is running, it may cause the network connectivity to be disrupted. # If that disruption last too long, say more than 3 or 4 minutes, the above calls may fail with exception. # So catch the exception here but do nothing. $logText += "Attempt #$i : failed with exception ($($_.Exception.Message)). `r`n" } finally { if ($result.status -eq 'Success') { $retVal = $true } if ($cimSession) { Remove-CimSession $cimSession } } if ($retVal) { break } } $retObj = @{RetVal = $retVal; LogText = $logText; HostIp = $using:managementIpAddress} $retObj } } } # Wait for all the nodes to either converge or fail to converge. If they failed to # converge, or if we failed to send the WSManAction to them, then use the more stateful # networking methods to figure out why, later. Trace-ECEScript "Waiting for DSC query to complete" { $results = Receive-Job -Wait -Job $jobs $results | ForEach-Object { Trace-Execution $_.LogText if ($_.RetVal -ne $true) { Trace-Error "DSC failed to converge on a physical host $($_.HostIp)." } } } $jobs | Remove-Job -ErrorAction SilentlyContinue } else { Trace-Execution "Skipping wait for DSC config as this is an ISO-based deployment." } } } function Get-DomainIPMapping { param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $domainIPMapping = @{} $deploymentMachineRoleNode = $Parameters.Roles["DeploymentMachine"].PublicConfiguration.Nodes.Node if($deploymentMachineRoleNode -ne $null) { $domainIPMapping.Add($deploymentMachineRoleNode.Name, $deploymentMachineRoleNode.IPv4Address) } return $domainIPMapping } # Get the node names from the ExecutionContext in the $Parameters variable # if available; otherwise, return $null function Get-ExecutionContextNodeName { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory = $false)] [switch] $EnsureSingle ) $nodeName = $null $executionRoleName = $Parameters.Context.ExecutionContext.Roles.Role.RoleName $nodeName = $Parameters.Context.ExecutionContext.Roles.Role.Nodes.Node.Name if ($null -ne $executionRoleName -and $null -eq $nodeName) { # If node names are not provided, get all nodes associated to the $executionRoleName $nodeName = ($Parameters.Roles[$executionRoleName].PublicConfiguration.Nodes.Node.Name) | Select-Object -Unique } Trace-Execution "Received ExecutionContext with role name $executionRoleName and node(s): $($nodeName -join ', ')" if ($EnsureSingle -and (!$nodeName -or ($nodeName.Count -ne 1))) { throw "Expected single node in ExecutionContext but found node(s): $($nodeName -join ', ')" } return $nodeName } # Get the cluster name from the ExecutionContext in the $Parameters variable # if available; otherwise, return $null function Get-ExecutionContextClusterName { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $executionRoleName = $Parameters.Context.ExecutionContext.Roles.Role.RoleName $clusterName = $Parameters.Context.ExecutionContext.Roles.Role.Nodes.Node.ClusterName Trace-Execution "Received ExecutionContext with role name $executionRoleName and clusterName : $clusterName" return $clusterName } # Gets the names of the roles specified in the execution context. function Get-ExecutionContextRoleName { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) # ExecutionContext can specify RolePath and/or RoleName attributes. Since we want just the names # we truncate the role paths if they are the only attribute provided, and return a list of the unique role names. $executionRoleName = @( $Parameters.Context.ExecutionContext.Roles.Role.RoleName ) $executionRolePath = @( $Parameters.Context.ExecutionContext.Roles.Role.RolePath ) $executionRoleName += $executionRolePath | Split-Path -Leaf return ($executionRoleName | Select-Object -Unique) } # Determines whether the environment is one-node from the interface parameters. function IsOneNode { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) return @($Parameters.Roles["Storage"].PublicConfiguration.Nodes.Node).Count -eq 1 } # Determines whether the environment is virtual from the interface parameters. function IsVirtualAzureStack { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $oemModel = $Parameters.Roles["OEM"].PublicConfiguration.PublicInfo.UpdatePackageManifest.UpdateInfo.Model $supportedModelList = $Parameters.Roles["OEM"].PublicConfiguration.PublicInfo.UpdatePackageManifest.UpdateInfo.SupportedModels.SupportedModel if ($null -eq $supportedModelList) { # The model string reported by BIOS and recorded by ExtractOEMContent.ps1 will still have spaces and special characters return (@("Virtual Machine", "Hyper-V") -contains $oemModel) } else { # The supported model list will have had spaces and special characters removed return ($supportedModelList -contains "VirtualMachine") } } # This function checks whether the deployment is running on Virtual Machine by system prams function IsOnVirtualMachine { try { # is on VM $computerSystem = Get-WmiObject win32_computersystem $isOnVM = (('Microsoft Corporation' -eq $computerSystem.Manufacturer) -and ('Virtual Machine' -eq $computerSystem.Model)) } catch { Trace-Error "Error while checking system info for Virtual Machine deployment" return $false } return $isOnVM } # Checks the RunInformation for UpdateVersion. Returns the value if found else returns null function Get-InProgressUpdateVersion { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $version = $null Trace-Execution "Getting in progress Update Version." # In case of update, run time information will be populated if($Parameters.RunInformation -ne $null) { $runtimeParameters = $Parameters.RunInformation['RuntimeParameter'] if( $runtimeParameters -ne $null -and $runtimeParameters.ContainsKey('UpdateVersion') ) { $version = $runtimeParameters["UpdateVersion"] Trace-Execution "Version of the update being applied : $version" return $version } } # If it is a one node or bootstrap VM return null as an update will not run on it. $buildLocally = $Parameters.Context.ExecutionContext.BuildVhdLocally -and [bool]::Parse($Parameters.Context.ExecutionContext.BuildVhdLocally) $RoleName = $Parameters.Configuration.Role.Id $isOneNode = @($Parameters.Roles["BareMetal"].PublicConfiguration.Nodes.Node).Count -eq 1 if(($isOneNode -eq $true) -or ($buildLocally -and ($RoleName -eq "Domain"))) { return $version } # If the version is set to null, check if share has update version info file. # This file only exists while a host is being updated. if($version -eq $null) { $bareMetalRole = $Parameters.Roles["BareMetal"].PublicConfiguration $storageClusterName = Get-ManagementClusterName $Parameters if($bareMetalRole.PublicInfo.UpdateInformation -ne $null) { $updateInfoFolderPath = Get-SharePath $Parameters $bareMetalRole.PublicInfo.UpdateInformation.UpdateVersionFile.FolderName $storageClusterName $updateInfoFileName = $bareMetalRole.PublicInfo.UpdateInformation.UpdateVersionFile.FileName $filePath = Join-Path -Path $updateInfoFolderPath -ChildPath $updateInfoFileName Trace-Execution "Check if version file $filePath exists" if(Test-Path -Path $filePath) { Trace-Execution "The version file is present. Extracting version number and override status from it" $versionFileContent = [xml] ( Get-Content -Path $filePath ) if($versionFileContent) { $versionValue = $versionFileContent.InProgressUpdateVersion.Version $useThisVersion = $versionFileContent.InProgressUpdateVersion.UseThisVersion if($useThisVersion -eq "True") { $version = $versionValue } else { Trace-Error "The version file exists but is not being used as UseThisVersion flag is set to False. This flag should be set to True if FRU is being used to recover from an update failure" } } else { Trace-Error "Version file found at $filePath but has invalid content" } } } } return $version } # Gets the file share directory containing expanded contents of a currently in-progress update package. function Get-InProgressUpdatePackagePath { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $runtimeParameters = $Parameters.RunInformation['RuntimeParameter'] $packagePath = $runtimeParameters["UpdatePackagePath"] if (-not $packagePath) { Trace-Execution "The UpdatePackagePath runtime parameter is not present." return $null } Trace-Execution "Upgrade package @: [$packagePath]" # The package path is expected to be a full path to an update package file (e.g. a self- # extracting .exe, or a metadata.xml file). The path where the contents are extracted is # the directory containing this file. return (Split-Path $packagePath) } # Gets the location of the folder containing nugets in the currently in-progress update package. function Get-InProgressUpdateNugetStore { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $updatePackagePath = Get-InProgressUpdatePackagePath -Parameters $Parameters if ($updatePackagePath) { $urpRole = $Parameters.Roles["URP"].PublicConfiguration $nugetSourceFolderName = ($urpRole.PublicInfo.UpdatePackagePaths.ContentMappings.ContentMapping | Where-Object { $_.Name -eq "Nugets" }).SourceFolder $nugetStorePath = Join-Path $updatePackagePath $nugetSourceFolderName if (Test-Path $nugetStorePath) { Trace-Execution "Found update nuget store at $nugetStorePath." return $nugetStorePath } # Fallback: check for legacy nuget store path. $nugetSourceFolderName = ($urpRole.PublicInfo.UpdatePackagePaths.ContentMappings.ContentMapping | Where-Object { $_.Name -eq "NugetsLegacy" }).SourceFolder $nugetStorePath = Join-Path $updatePackagePath $nugetSourceFolderName if (Test-Path $nugetStorePath) { Trace-Execution "Found legacy update nuget store at $nugetStorePath." return $nugetStorePath } Trace-Execution "No nuget store was found at update package path: $updatePackagePath." } else { Trace-Execution "No update is currently in progress." } return $null } <# .SYNOPSIS Expands the deployment artifacts to the destination location. .DESCRIPTION Expands all deployment artifacts into the destination root path. .EXAMPLE Expand-DeploymentArtifacts -DeploymentContentNode $Parameters.Configuration.Role.PrivateInfo.DeploymentContent -DestinationRootPath "C:" .PARAMETER DeploymentContentNode The xml node containing the deployment content to deliver to the destination. .PARAMETER DestinationRootPath The root path under which to deliver the nuget content. #> function Expand-DeploymentArtifacts { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [System.Xml.XmlElement] $DeploymentContentNode, [Parameter(Mandatory=$true)] [string] $DestinationRootPath, [Parameter(Mandatory=$false)] [string] $NugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore" ) $nugetDeploymentContentItems = $DeploymentContentNode.NugetArtifact $installRolePackages = $false $rolePackageDestination = Join-Path $DestinationRootPath 'NugetStore' foreach( $deployContent in $nugetDeploymentContentItems ) { Write-Verbose -Verbose "Copying content from nuget $($deployContent.Name) into $DestinationRootPath" $destination = Join-Path $DestinationRootPath $deployContent.DestinationPath $sourceIsFile = [bool]$deployContent.SourceIsFile $isNugetInstall = [bool]$deployContent.NugetInstall if($isNugetInstall) { $installRolePackages = $true } Write-Verbose -Verbose "$($deployContent.SourcePath) will be copied to $destination" Write-Verbose -Verbose "IsNugetInstall : $isNugetInstall" Expand-NugetContent -NugetName $deployContent.Name -SourcePath $deployContent.SourcePath -DestinationPath $destination -SourceIsFile:$sourceIsFile -Verbose:$VerbosePreference -NugetStorePath $NugetStorePath -IsNugetInstall:$isNugetInstall Write-Verbose -Verbose "Nuget $($deployContent.Name) with source [$($deployContent.SourcePath)] copied to $destination" } if($installRolePackages) { Write-Verbose -Verbose "Install Role Nuget packages to NugetStore" Install-RoleNugetPackages -NugetStorePath $NugetStorePath -Destination $rolePackageDestination } } function Expand-UpdateContent { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory=$false)] [string[]] $ExecutionRoleName, [Parameter(Mandatory=$false)] [switch] $DSCOnly, [Parameter(Mandatory=$false)] [switch] $EngineUpdateOnly, [Parameter(Mandatory=$false)] [string[]] $NodeName ) $currentContext = $Parameters.Context.ExecutionContext if(-not $ExecutionRoleName) { $ExecutionRoleName = $currentContext.Roles.Role.RoleName } $vmRole = $Parameters.Roles["VirtualMachines"].PublicConfiguration $localExecutionHost = $env:COMPUTERNAME $clusterName = Get-ManagementClusterName $Parameters $allManagementHosts = Get-ManagementClusterNodes $Parameters $uncLibraryShareNugetStorePath = Get-SharePath $Parameters $vmRole.PublicInfo.LibraryShareNugetStoreFolder.Path $clusterName # During AsZ deployment expand nugets on all the nodes part of cluster # Cluster path at this time doesn't exist so use the absolute path if (!(Test-Path -Path $uncLibraryShareNugetStorePath)) { Trace-Execution "AsZ deployment scenario so use absolute path on SeedNode" $uncLibraryShareNugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore" } $localLibraryShareNugetStorePath = $null # Bug 15111454: Waiting for fix it should not block deployment # if(Test-LocalHostHasClusterStoragePath -Parameters $Parameters) # { # Trace-Execution "Getting local CSV path for $uncLibraryShareNugetStorePath" # $localLibraryShareNugetStorePath = Get-LocalCsvPathFromSharePath -Parameters $Parameters -UNCSharePath $uncLibraryShareNugetStorePath # } Trace-Execution "uncLibraryShareNugetStorePath: $uncLibraryShareNugetStorePath" Trace-Execution "localLibraryShareNugetStorePath: $localLibraryShareNugetStorePath" $systemDrive = $env:SystemDrive -Replace (':','$') $healthyNodes = $Parameters.Roles["VirtualMachines"].PublicConfiguration.Nodes.Node | where { $_.ProvisioningStatus -eq "Complete" } | % Name $healthyNodes += $Parameters.Roles["BareMetal"].PublicConfiguration.Nodes.Node | where { $_.ProvisioningStatus -eq "Complete" } | % Name foreach ($roleName in $ExecutionRoleName) { Trace-Execution "Expand-UpdateContent adding update contents for role: $roleName" $roleConfiguration = $Parameters.Roles[$roleName].PublicConfiguration if ($NodeName) { $nodes = $NodeName } else { $nodes = $roleConfiguration.Nodes.Node.Name |where { $healthyNodes.Contains($_)} } Trace-Execution "Expand-UpdateContent for Nodes : $($nodes)" # Skipping nodes which are provided in runtime parameters $nodes = Skip-NodesProvidedInRuntimeParameter -Parameters $Parameters -Nodes $nodes Trace-Execution "Expand-UpdateContent for Nodes (after filtering) : $($nodes)" # Get update content for the role $nugetUpdateContentItems = $roleConfiguration.PublicInfo.DeploymentContent.NugetArtifact | where {$_.Update -eq "True" -or $_.EngineUpdate -eq "True"} $allNodesNameIpMappingInfo = Get-NetworkMgmtIPv4FromECEForAllHosts -Parameters $Parameters # Copy the update content if ($nugetUpdateContentItems) { foreach( $updateContent in $nugetUpdateContentItems ) { if($DSCOnly -and ($updateContent.DSCContent -ne "True")) { Trace-Execution "$($updateContent.Name) is not marked as nuget with DSC content. Skipping update on the target VM(s) " continue } elseif ($EngineUpdateOnly -and ($updateContent.EngineUpdate -ne "True")) { Trace-Execution "$($updateContent.Name) is not marked as nuget with engine content. Skipping update on the target VM(s)." continue } foreach ($node in $nodes) { $libraryShareNugetStorePath = $null if (($node -in $allManagementHosts) -and ($null -ne $localLibraryShareNugetStorePath)) { Trace-Execution "For node $node - member the management cluster - will use local nuget store path $localLibraryShareNugetStorePath" $libraryShareNugetStorePath = $localLibraryShareNugetStorePath } else { Trace-Execution "For node $node will use UNC nuget store path $uncLibraryShareNugetStorePath" $libraryShareNugetStorePath = $uncLibraryShareNugetStorePath } Trace-Execution "Start process to expand nuget content:" Trace-Execution "Local execution host : $localExecutionHost" Trace-Execution "Nuget store Path : $libraryShareNugetStorePath" Trace-Execution "Processing node : $node" $nodeIp = $allNodesNameIpMappingInfo[$node] $DestinationRootPath = "\\$nodeIp\$systemDrive\" if ($updateContent.UpdatePath) { $destination = Join-Path $DestinationRootPath $updateContent.UpdatePath } else { $destination = Join-Path $DestinationRootPath $updateContent.DestinationPath } $isNugetInstall = $null [bool]::TryParse($updateContent.NugetInstall, [ref]$isNugetInstall) Expand-NugetContent -NugetName $updateContent.Name -SourcePath $updateContent.SourcePath -DestinationPath $destination -IsUnc -NugetStorePath $libraryShareNugetStorePath -IsNugetInstall:$isNugetInstall Write-Verbose -Verbose "$($updateContent.SourcePath) copied to $destination" } } } } } #execute to a script block over a VMGroup using PSDirect instead of WinRM function Invoke-PSDirectOnVM { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory = $true)] [String[]]$VMNames, [Parameter(Mandatory = $true)] [Object]$VMCredential, [Parameter(Mandatory = $true)] [ScriptBlock]$ScriptBlock, [Object[]]$ArgumentList = $null ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop #retrieve the environment info $allPhysicalMachines = Get-ActiveClusterNodes $Parameters # Domain Info $domainAdminCredential = Get-DomainCredential -Parameters $Parameters # for each physical host, iterate over all the infra VMs that are part of it $hostTrace = @() foreach ($phyHost in $allPhysicalMachines) { Trace-Execution "For Host $($phyHost)" # test if the target host is reachable $output = $false $output = Test-WSMan -ComputerName $phyHost -ErrorAction:Ignore if (-not $output) { Write-Host "Host $($phyHost) not responding..." continue } $localHostTrace = Invoke-Command -ComputerName $phyHost -Credential $domainAdminCredential -ScriptBlock { $vmTrace = @() $thrownExceptions = @() # get the infra VMs in this physical node $retries = 5 for($retry = 0; $retry -lt $retries; ++$retry) { try { # Unfiltered calls to Get-VM may fail if a VM is being deleted from that host $localVMs = (Get-VM | ? Name -In $using:VMNames).Name break } catch { if ($retry -eq $retries - 1) { throw } } } if($localVMs) { Trace-Execution "Executing on local VMs $($localVMs -join ',')" try { # change PS language mode to FULL to allow script block invocation if (!$using:ArgumentList) { $vmTrace += Invoke-Command -VMName $localVMs -Credential $using:VMCredential -ScriptBlock ([ScriptBlock]::Create($("Import-Module OpenUpSession; Set-FullLanguage; `r`n" + $using:ScriptBlock))) } # check if the scriptblock declares Param because special handling is needed elseif ($using:ScriptBlock -match "param[ ]*\(|param[ ]*\r\n") { $sb = {param($ScriptBlock, [Object[]]$ArgumentList) Import-Module OpenUpSession; Set-FullLanguage; [scriptBlock]::Create($ScriptBlock).invoke($ArgumentList)} $vmTrace += Invoke-Command -VMName $localVMs -Credential $using:VMCredential -ScriptBlock $sb -ArgumentList $using:ScriptBlock, $using:ArgumentList } else { $vmTrace += Invoke-Command -VMName $localVMs -Credential $using:VMCredential -ArgumentList $using:ArgumentList -ScriptBlock ([ScriptBlock]::Create($("Import-Module OpenUpSession; Set-FullLanguage; `r`n" + $using:ScriptBlock))) } } catch { Write-Host "Local exception: $($_)" $thrownExceptions += $_ } } if ($vmTrace) { Write-Host "VM(s) returned: $vmTrace" } if ($thrownExceptions.Count -gt 0) { throw $thrownExceptions } return $vmTrace } if ($localHostTrace) { Trace-Execution "Local Host Output: $($localHostTrace)" } $hostTrace += $localHostTrace } return $hostTrace } function Invoke-ScriptBlockWithRetries { [CmdletBinding(DefaultParameterSetName="All")] param( [ScriptBlock] [parameter(Mandatory = $true)] $ScriptBlock, [Int32] [parameter(Mandatory = $false)] $RetryTimes = 10, [Int32] [parameter(Mandatory = $false)] $RetrySleepTimeInSeconds = 10, [Switch] [parameter(Mandatory = $false)] $InDisconnectedSession=$false, [string] [parameter(Mandatory = $false, ParameterSetName="ByComputerName")] $ComputerName = $null, [PSCredential] [parameter(Mandatory = $false)] $Credential = $null, [parameter(Mandatory = $false, ParameterSetName="BySession")] $Session = $null, [parameter(Mandatory = $false)] $ArgumentList = $null ) $ErrorActionPreference = "Stop" for($retry = 0; $retry -lt $RetryTimes; ++$retry) { try { Write-Verbose "Invoke-ScriptBlockWithRetries: retry #$retry of $RetryTimes, retry sleep time is $RetrySleepTimeInSeconds seconds." -Verbose $invokeParams = @{ } $noNewScope = $true if ($ComputerName) { $invokeParams.ComputerName = $ComputerName $noNewScope = $false } elseif ($Session) { $invokeParams.Session = $Session $noNewScope = $false } if ($Credential) { $invokeParams.Credential = $Credential $noNewScope = $false } if ($noNewScope) { $invokeParams.NoNewScope = $true } if ($InDisconnectedSession) { $invokeParams.InDisconnectedSession = $true } if ($ArgumentList) { $invokeParams.ArgumentList = $ArgumentList } $invokeParams.ScriptBlock = $ScriptBlock return Invoke-Command @invokeParams } catch { Write-Verbose "Invoke-ScriptBlockWithRetries failed with exception: $_" -Verbose if ($retry -lt ($RetryTimes - 1)) { Start-Sleep -Seconds $RetrySleepTimeInSeconds } else { throw } } } } function Invoke-ScriptBlockInParallel { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string[]] $ComputerNames, [Parameter(Mandatory=$false)] [PSCredential] $Credential, [ScriptBlock] [parameter(Mandatory = $true)] [ValidateScript({$_ -NotLike '*$Using:*'})] $ScriptBlock, [Object[]] [parameter(Mandatory = $false)] $ArgumentList = $null, [Int32] [parameter(Mandatory = $false)] $RetryTimes = 2, [Int32] [parameter(Mandatory = $false)] $RetrySleepTimeInSeconds = 5, [Parameter(Mandatory = $false)] [string] $Authorization = 'Default' ) $ErrorActionPreference = 'Stop' $computerNames = $ComputerNames $jobs = @() $sessions =@() $errorMessages = @() # Just retry for kicking off the ScriptBlock in Parallel for ($retry = 0; $retry -lt $RetryTimes; ++$retry) { $retryComputers = @() $session = $null $job = $null Trace-Execution "Invoke-ScriptBlockInParallel: retry #$retry of $RetryTimes, retry sleep time is $RetrySleepTimeInSeconds seconds, machines are $computerNames." foreach ($computerName in $computerNames) { try { if($Credential) { $session = New-PSSession -ComputerName $computerName -Credential $Credential -Authentication $Authorization } else { $session = New-PSSession -ComputerName $computerName } Initialize-ECESession -Session $session $job = Invoke-Command -ErrorAction Stop -Session $session -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList -AsJob $sessions += $session $jobs += $job } catch { $retryComputers += $computerName $errorMessage = "Machine $computerName failed with error: $($_.Exception.Message)" $errorMessages += $errorMessage if ($session -ne $null) { $session | Remove-PSSession -ErrorAction Ignore } if ($job -ne $null) { $job | Remove-Job -ErrorAction Ignore } } } if (($retryComputers.Count -eq 0) -or ($retry -ge ($RetryTimes -1))) { Trace-Execution "Invoke-ScriptBlockInParallel exits from retries for kicking off jobs in $retry retries" break } $computerNames = $retryComputers Start-Sleep -Second $RetrySleepTimeInSeconds $errorMessages = @() } # Waiting for all jobs to finish try { if ($jobs.Count -eq 0) { $errorMessage = "Invoke-ScriptBlockInParallel failed: Script could not be invoked after $RetryTimes retries" $errorMessages += $errorMessage throw "Invoke-ScriptBlockInParallel failed: $errorMessages" } Trace-Execution "Invoke-ScriptBlockInParallel: Waiting for jobs" $jobs | Wait-Job | Receive-Job Trace-Execution "Invoke-ScriptBlockInParallel: finished jobs" $failedJobs = $jobs | ? { $_.JobStateInfo.State -ne "Completed" } foreach ($failedJob in $failedJobs) { $newErrorMessage = $null try { $newErrorMessage = $failedJob.ChildJobs[0].JobStateInfo.Reason.Message } catch { } $errorMessages += "Machine $($failedJob.Location): Failed with error: $newErrorMessage" } if ($errorMessages.Count -gt 0) { throw "Invoke-ScriptBlockInParallel failed: $errorMessages" } } finally { $sessions | Remove-PSSession -ErrorAction Ignore $jobs | Remove-Job -ErrorAction Ignore } } <# .SYNOPSIS Create a folder on management file share for host update .DESCRIPTION This function creates a folder on management file share for host update .PARAMETER Parameters This object is based on the customer manifest. It contains the private information and public information of cloud role. This Parameters object is passed down by the deployment engine. #> function Get-HostUpdateShare { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Trace-ECEScript "Getting the host update share." { $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $clusterName = Get-ManagementClusterName $Parameters $managementLibraryPath = Get-SharePath $Parameters $physicalMachinesRole.PublicInfo.WindowsUpdateStagingFolder.Path $clusterName $managementLibraryPath = Split-Path -Path $managementLibraryPath $hostUpdateLogShare = Join-Path $managementLibraryPath 'Logs\HostUpdate' if(-not (Test-Path $hostUpdateLogShare)) { Trace-Execution " Create a folder on management file share $hostUpdateLogShare " try { New-Item $hostUpdateLogShare -ItemType Directory -Force | Out-Null } catch { throw "Failed to create folder on management file share $hostUpdateLogShare. Error $_" } } } return $hostUpdateLogShare } # Returns the list of orchestrators that are currently available. function Get-CurrentOrchestrators { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) # Check if the code is running on DVM if($Parameters.Roles["DeploymentMachine"]) { $dvmName = $Parameters.Roles["DeploymentMachine"].PublicConfiguration.Nodes.Node | % Name if($env:ComputerName -eq $dvmName) { $dvmIpAddress = $Parameters.Roles["DeploymentMachine"].PublicConfiguration.Nodes.Node | % IPv4Address $orchestrator = New-Object -TypeName psobject -Property @{ Name = $dvmName; IPAddress = $dvmIpAddress } return @($orchestrator) } } else { # Check if we only have one host. In that case the code should be running on the same host or the DVM (if performing bare metal POC) $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $allPhysicalHosts = @($physicalMachinesRole.Nodes.Node | % Name) if( ($allPhysicalHosts.Count -eq 1) -and ($allPhysicalHosts -eq $env:ComputerName) ) { $orchestrator = New-Object -TypeName psobject -Property @{ Name = $env:ComputerName } return @( $orchestrator) } } # If the orchestrator is not the DVM or host, then return ERCS VM information $orchestrators = @() $virtualMachineNodes = $Parameters.Roles["VirtualMachines"].PublicConfiguration.Nodes $ercsNodes = $virtualMachineNodes.Node | where { $_.Role -eq "SeedRingServices"} foreach($ercsNode in $ercsNodes) { $name = $ercsNode.Name $ercsIpAddress = ($ercsNode.NICs.NIC.IPv4Address -split "/")[0] $orchestrator = New-Object -TypeName psobject -Property @{ Name = $name; IPAddress = $ercsIpAddress} $orchestrators += $orchestrator } return @($orchestrators) } # Note: This is a temporary implementation to unblock Image Based update function Update-JEAEndpointsForUpdate { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) # Update PXE endpoint - Remove this once PXE VM has an updated endpoint. # PXE Role Trace-Execution "Start - Update module needed by PXEEndpoint on PXE VM." $pxeRole = $Parameters.Roles["PXE"].PublicConfiguration $pxeServerName = $pxeRole.Nodes.Node.Name # PhysicalMachine Role $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $allPhysicalHosts = $physicalMachinesRole.Nodes.Node | % Name # Skipping nodes which are provided in runtime parameters $allPhysicalHosts = Skip-NodesProvidedInRuntimeParameter -Parameters $Parameters -Nodes $allPhysicalHosts # VirtualMachine Role $virtualMachinesRole = $Parameters.Roles["VirtualMachines"].PublicConfiguration $clusterName = Get-ManagementClusterName $Parameters $libraryShareNugetStorePath = Get-SharePath $Parameters $virtualMachinesRole.PublicInfo.LibraryShareNugetStoreFolder.Path $clusterName $tempNugetDir = Join-Path $env:temp 'AzureStackPXE.Deployment' Trace-Execution "Expanding Nuget Microsoft.AzureStack.Solution.Deploy.AzureStackPXE.Deployment at $tempNugetDir" Expand-NugetContent -NugetName 'Microsoft.AzureStack.Solution.Deploy.AzureStackPXE.Deployment' ` -NugetStorePath $libraryShareNugetStorePath ` -SourcePath 'content\Scripts' ` -DestinationPath $tempNugetDir $sourcePath = "$tempNugetDir\*" $destinationPath = "\\$pxeServerName\c$\Program Files\WindowsPowerShell\Modules\Microsoft.AzureStack.Solution.Deploy.AzureStackPXE.Deployment\Scripts" Trace-Execution "Copying $sourcePath to $destinationPath" New-Item -Path $destinationPath -ItemType Directory -Force -ErrorAction SilentlyContinue Copy-Item -Path $sourcePath -Destination $destinationPath -Force Remove-Item -Force -Path $tempNugetDir -Recurse Trace-Execution "Completed - Update module needed by PXEEndpoint on PXE VM." Trace-Execution "Start - Update module needed by NewManagementVMEndpoint on all the hosts" $tempNugetDir = Join-Path $env:temp 'NodeUtils_NewManagementVM' Expand-NugetContent -NugetName 'Microsoft.AzureStack.Solution.Deploy.Common.NodeUtils' ` -NugetStorePath $libraryShareNugetStorePath ` -SourcePath 'content\NewManagementVM' ` -DestinationPath $tempNugetDir foreach ($nodeName in $allPhysicalHosts) { $sourcePath = "$tempNugetDir\*" $destinationPath = "\\$nodeName\c$\Program Files\WindowsPowerShell\Modules\NewManagementVM" Trace-Execution "Copying $sourcePath to $destinationPath" New-Item -Path $destinationPath -ItemType Directory -Force -ErrorAction SilentlyContinue Copy-Item -Path $sourcePath -Destination $destinationPath -Force } Remove-Item -Force -Path $tempNugetDir -Recurse Trace-Execution "Completed - Update module needed by NewManagementVMEndpoint on all the hosts." } # Returns credentials needed for various operations during deployment and update. function Get-DomainCredential { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $domainRole = $Parameters.Roles["Domain"].PublicConfiguration $securityInfo = $cloudRole.PublicInfo.SecurityInfo $domainAdminUser = $securityInfo.DomainUsers.User | ? Role -EQ "DomainAdmin" $domainAdminCredential = $Parameters.GetCredential($domainAdminUser.Credential) $domainAdminName = $domainAdminCredential.GetNetworkCredential().UserName $domainAdminPassword = $domainAdminCredential.GetNetworkCredential().Password $domainFqdn = $domainRole.PublicInfo.DomainConfiguration.FQDN $domainCredential = New-Credential "$domainFqdn\$domainAdminName" $domainAdminPassword return $domainCredential } # Returns Temporary Domain Admin credentials. function Get-TemporaryDomainCredential { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $domainAdminCredential = Get-UserAccountCredential -Parameters $Parameters return $domainAdminCredential } function Get-UserAccountCredential { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory=$false)] [string] $UserRole = "DomainAdmin" ) $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $domainRole = $Parameters.Roles["Domain"].PublicConfiguration $securityInfo = $cloudRole.PublicInfo.SecurityInfo $UserAccount = $securityInfo.DomainUsers.User | ? Role -EQ $UserRole $UserAccountCredential = $Parameters.GetCredential($UserAccount.Credential) $domainName = $domainRole.PublicInfo.DomainConfiguration.DomainName if(-not $("$($UserAccountCredential.UserName)" -like "*\*") -and [String]::IsNullOrEmpty($DomainName) -eq $false) { $UserAccountCredential = New-Credential -UserName "$domainName\$($UserAccountCredential.UserName)" -Password $UserAccountCredential.GetNetworkCredential().Password } return $UserAccountCredential } # Update Windows DISM features based on the configuration of Role.xml # Including installing features and removiing unused features function Update-WindowsDismFeature { [CmdletBinding()] Param ( [Parameter(Mandatory = $false)] [object] $WindowsFeature, [Parameter(Mandatory = $false)] [Switch] $Online, [Parameter(Mandatory = $false)] [string] $Path, [Parameter(Mandatory = $false)] [string] $WinSxSPath = "$env:windir\WinSxS" ) Trace-Execution "Update Windows DISM features" if ($null -ne $WindowsFeature) { if ($Online) { $dismFeatures = (Get-WindowsOptionalFeature -Online).FeatureName } else { $dismFeatures = (Get-WindowsOptionalFeature -Path $Path).FeatureName } if ($null -ne $WindowsFeature.Feature.Name) { $featuresToInstall = $dismFeatures | Where-Object { $_ -in $WindowsFeature.Feature.Name } if ($null -ne $featuresToInstall -and $featuresToInstall.Count -gt 0) { Trace-Execution "Install Windows DISM features: $featuresToInstall" if ($Online) { Enable-WindowsOptionalFeature -FeatureName $featuresToInstall -Online -All -Source $WinSxSPath -NoRestart } else { Enable-WindowsOptionalFeature -FeatureName $featuresToInstall -Path $path -All } } } if ($null -ne $WindowsFeature.RemoveFeature.Name) { $featuresToRemove = $dismFeatures | Where-Object { $_ -in $WindowsFeature.RemoveFeature.Name } if ($null -ne $featuresToRemove -and $featuresToRemove.Count -gt 0) { Trace-Execution "Remove unused Windows DISM features: $featuresToRemove" if ($Online) { Disable-WindowsOptionalFeature -FeatureName $featuresToRemove -Online -NoRestart } else { Disable-WindowsOptionalFeature -FeatureName $featuresToRemove -Path $Path } } } } } <# .SYNOPSIS #### Reference from nuget in 1907 Returns a structured object containing information about each of the exceptions in an update summary #> function Read-SummaryXml { Param ( [parameter(Mandatory = $false)][string] $RoutingJsonPath = $RoutingJsonSharePath, [parameter(Mandatory = $false, ParameterSetName = 'Path')][string] $SummaryXMLPath, [parameter(Mandatory = $false, ParameterSetName = 'Xml')][xml] $SummaryXML, [parameter(Mandatory = $false)][switch] $Routing ) if ($SummaryXMLPath) { $SummaryXML = Get-Content "$SummaryXMLPath" -ErrorAction Stop } $Exceptions = $SummaryXML.SelectNodes(".//Exception") $returnInformation = @() foreach ($exception in $Exceptions) { if ($exception.ParentNode.RolePath) { $RolePath = $exception.ParentNode.RolePath | select -first 1 $Interface = $exception.ParentNode.InterfaceType | select -first 1 $ExecutionContextRolePath = $null $ExecutionContextNode = "" $ActionType = $null $ActionRolePath = "" $curAncestor = $exception while ($curAncestor -and (!$ExecutionContextRolePath -or !$ActionType)) { if (!$ExecutionContextRolePath) { $ExecutionContextRolePath = $curAncestor.ExecutionContext.Roles.Role.RolePath | select -first 1 $ExecutionContextNode = $curAncestor.ExecutionContext.Roles.Role.Nodes.Node.Name | select -first 1 } if (!$ActionType) { $ActionType = $curAncestor.ActionType $ActionRolePath = $curAncestor.RolePath } $curAncestor = $curAncestor.ParentNode } $returnInformation += @{ 'Exception' = $exception.Raw 'ExecutionContextNode' = $ExecutionContextNode 'ExecutionContextRolePath' = $ExecutionContextRolePath 'RolePath' = $RolePath 'InterfaceType' = $Interface 'ActionType' = $ActionType 'ActionRolePath' = $ActionRolePath } } } $returnInformation } function Get-FailureListFromMainActionPlan { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $runtimeParameters = $null $failureList = $null if($Parameters.RunInformation -ne $null -and $Parameters.RunInformation.ContainsKey('RuntimeParameter')) { $runtimeParameters = $Parameters.RunInformation['RuntimeParameter'] } if($runtimeParameters -ne $null -and $runtimeParameters.ContainsKey('FailureSummaryXml')) { $failureSummaryXml = $runtimeParameters['FailureSummaryXml'] $failureList = @(Read-SummaryXml -SummaryXML $failureSummaryXml) } return $failureList } <# .SYNOPSIS A generic wrapper function to send telemetry events. #> function Send-TelemetryEvent { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $ComponentName, [Parameter(Mandatory=$true)] [string] $EventName, [Parameter(Mandatory=$true)] [int] $EventVersion, [Parameter(Mandatory=$true)] [hashtable] $EventData ) $ErrorActionPreference = "Stop" Trace-Execution "Telemetry event = Component:[$ComponentName], EventName:[$EventName], EventVersion:[$EventVersion], EventData:$($EventData | Out-String)" try { $telemetryPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Fabric.Health.Telemetry.TelemetryReporter" $telemetryDll = Join-Path $telemetryPath "lib\net46\Microsoft.AzureStack.Fabric.Health.Telemetry.TelemetryReporter.dll" Import-Module -Name $telemetryDll -Verbose:$false -DisableNameChecking } catch { Trace-Warning "Error loading telemetry library: $_" return } try { Trace-Execution "Sending telemetry event." Send-AzureStackTelemetryEvent -Component $ComponentName -Name $EventName -Version $EventVersion -TelemetryEventData $EventData -Verbose Trace-Execution "Finish sending telemetry event." } catch { Trace-Warning "Error sending telemetry event: $_" } } <# .SYNOPSIS A generic wrapper function checks whether the current update is pnu or not by comparing minor build version. #> function Test-IsMonthlyUpdate { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) Trace-Execution "Inside Test-IsMonthlyUpdate method." $isPnu = $false if ((Test-IsUpdate -Parameters $Parameters) -and !(Test-IsHotfixUpdate -Parameters $Parameters)) { $isPnu = $true } Trace-Execution "Is it a pnu update :: $isPnu." return $isPnu } <# .SYNOPSIS A generic wrapper function checks whether the current update is a Hotfix pnu or not by checking the Update Name. #> function Test-IsHotfixUpdate { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) Trace-Execution "Inside Test-IsHotfixUpdate method." $isHotfixPNU = $false # In case of update, run time information will be populated if($Parameters.RunInformation -ne $null) { $runtimeParameters = $Parameters.RunInformation['RuntimeParameter'] if( $runtimeParameters -ne $null -and $runtimeParameters.ContainsKey('UpdateVersion') ) { $updateMetadataXml = [xml]$runtimeParameters['metadata'] Trace-Execution "UpdateName : $($updateMetadataXml.UpdatePackageManifest.UpdateInfo.UpdateName)" if($updateMetadataXml.UpdatePackageManifest.UpdateInfo.UpdateName -match 'AzS Hotfix') { $isHotfixPNU = $true; } } } Trace-Execution "Is it a Hotfix Update :: $isHotfixPNU." return $isHotfixPNU } <# .SYNOPSIS A helper function to check if the current action plan is an Update action plan. #> function Test-IsUpdate { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $updateVersion = Get-InProgressUpdateVersion -Parameters $Parameters if ($updateVersion -ne $null) { return $true } else { return $false } } <# .SYNOPSIS Tests whether the execution context node is a member of the management cluster. #> function Test-ManagementClusterNode { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Default to true if there is no BareMetal execution context $result = $true # If there is a BareMetal execution context, compare management cluster name and RefClusterId of the node if ( ($nodeName = Get-ExecutionContextNodeName -Parameters $Parameters) -and ((Get-ExecutionContextRoleName -Parameters $Parameters) -eq 'BareMetal') ) { $managementClusterName = Get-ManagementClusterName $Parameters Trace-Execution "Management cluster name is '$managementClusterName'" $bareMetalRole = $Parameters.Roles["BareMetal"].PublicConfiguration $nodeClusterName = ($bareMetalRole.Nodes.Node | Where-Object {$_.Name -eq $nodeName}).RefClusterId Trace-Execution "Node '$nodeName' cluster is '$nodeClusterName'" if ($nodeClusterName -eq $managementClusterName) { Trace-Execution "Node '$nodeName' is a member of management cluster '$managementClusterName'" } else { Trace-Execution "Node '$nodeName' is not a member of management cluster '$managementClusterName'" $result = $false } } return $result } <# .SYNOPSIS A helper function to Skip nodes which are provided in runtime parameters. #> function Skip-NodesProvidedInRuntimeParameter { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory = $true)] [string[]] $Nodes ) Trace-Execution "[Skip-NodesProvidedInRuntimeParameter] List of Nodes which are getting filtered $($Nodes -join ',')." $unreachableNodesStr = $Parameters.RunInformation['RuntimeParameter']['UnreachableNodes'] if([string]::IsNullOrEmpty($unreachableNodesStr)) { Trace-Execution "[Skip-NodesProvidedInRuntimeParameter] No nodes are present in the list of unreachable nodes." return $Nodes } Trace-Execution "[Skip-NodesProvidedInRuntimeParameter] List of unreachable nodes: $unreachableNodesStr." $unreachableNodes = $unreachableNodesStr -split ',' $newList = $Nodes | % { $_ -notin $unreachableNodes } Trace-Execution "[Skip-NodesProvidedInRuntimeParameter] List of Nodes after Filtering $($newList -join ',')." return $newList } <# .SYNOPSIS A helper function to get first available node. #> function Get-FirstAvailableNode { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]] $Nodes ) Trace-Execution "[Get-FirstAvailableNode] List of Nodes: $($Nodes -join ',')." foreach($node in $Nodes) { $returnValue = Invoke-Command {"reachable"} -ComputerName $node -ErrorAction Ignore if($returnValue -eq "reachable") { Trace-Execution "[Get-FirstAvailableNode] Node selected with WinRM connectivity: '$node'." return $node } else { Trace-Execution "[Get-FirstAvailableNode] Node is not reachable via WinRM: '$node'." } } Trace-Warning "[Get-FirstAvailableNode] No node is reachable via WinRM, fall back to Test-NetConnection (ping)." foreach($node in $Nodes) { $returnValue = Test-NetConnection -ComputerName $node -ErrorAction Ignore if($returnValue.PingSucceeded) { Trace-Execution "[Get-FirstAvailableNode] Node selected with Test-NetConnection connectivity: '$node'." return $node } else { Trace-Execution "[Get-FirstAvailableNode] Node is not reachable via Test-NetConnection: '$node'." } } # In few situations where this code is running on ECE agent then there may be permission issues, to work that around # returning first node if none of the nodes are reachable. # TODO: Fix the permission issue with ECE agent Trace-Warning "[Get-FirstAvailableNode] Fall back to return first node as No node is reachable from the list: $($Nodes -join ',')." return $Nodes[0] } <# .SYNOPSIS A helper function to check if the cloud admin operation status is update critical. #> function Test-CloudStatusUpdateCritical { $ErrorActionPreference = "Stop" Import-Module -Name ECEClient -Verbose:$false -DisableNameChecking $eceClient = Create-ECEClientWithServiceResolver $eceParamsXml = [XML]($eceClient.GetCloudParameters().getAwaiter().GetResult().CloudDefinitionAsXmlString) $statusNode = $eceParamsXml.SelectNodes("//Parameter[@Name='CloudAdminOperationStatus']") return ($statusNode.Value -eq "UpdateCritical") } function Get-AzureStackHostAllVirtualSwitchNames { Param( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) [System.String[]] $physicalMachines = $Parameters.Roles["BareMetal"].PublicConfiguration.Nodes.Node | Select-Object -ExpandProperty "Name" [PSObject[]] $vmSwitch = Get-VMSwitch -ComputerName $physicalMachines[0] -SwitchType External if ((-not $vmSwitch) -or $vmSwitch.Count -eq 0) { Trace-Error "Cannot get Virtual Switch Name from [ $($physicalMachines[0]) ]" } [System.String[]] $retVal = $vmSwitch.Name return $retVal } function Get-AzureStackHostDefaultVirtualSwitchName { Param( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) [System.String[]] $allSwitches = Get-AzureStackHostAllVirtualSwitchNames -Parameters $Parameters return $allSwitches[0] } function Set-TrustedHosts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $hostIp, [Parameter(Mandatory = $true)] [string] $hosts, [Parameter(Mandatory = $true)] [PSCredential] $Credential ) try { Invoke-Command -ComputerName $hostIp -ScriptBlock { $trustedHosts = $using:hosts $existingTrustedHosts = (Get-Item -Path WSMan:\localhost\Client\TrustedHosts).Value Trace-Execution "Existing Trusted Hosts: $existingTrustedHosts." if (![string]::IsNullOrEmpty($existingTrustedHosts) -and $existingTrustedHosts -ne "*") { $trustedHosts = (($existingTrustedHosts.Split(",") + $trustedHosts.Split(",")) | select -uniq) -join ',' } Trace-Execution "Adding Trusted Hosts: $trustedHosts." Set-Item WSMan:\localhost\Client\TrustedHosts -Value $trustedHosts -Force } -Credential $Credential } catch { Trace-Error "Failed to add Trusted Hosts: $hosts on $hostIp with exception. $_" throw $_ } } Export-ModuleMember -Function Add-IDnsConfiguration Export-ModuleMember -Function Add-IPAddress Export-ModuleMember -Function Add-LoadBalancerToNetworkAdapter Export-ModuleMember -Function Add-NetworkAdapterToNetwork Export-ModuleMember -Function ConnectPSSession Export-ModuleMember -Function ConvertFrom-IPAddress Export-ModuleMember -Function Convert-IPv4IntToString Export-ModuleMember -Function Convert-IPv4StringToInt Export-ModuleMember -Function ConvertTo-IPAddress Export-ModuleMember -Function ConvertTo-MacAddress Export-ModuleMember -Function ConvertTo-PrefixLength Export-ModuleMember -Function ConvertTo-SubnetMask Export-ModuleMember -Function Expand-DeploymentArtifacts Export-ModuleMember -Function Expand-NugetContent Export-ModuleMember -Function Expand-UpdateContent Export-ModuleMember -Function Get-FirstAvailableNode Export-ModuleMember -Function Get-AvailableADComputerName Export-ModuleMember -Function Get-BareMetalCredential Export-ModuleMember -Function Get-BroadcastAddress Export-ModuleMember -Function Get-ClusterShare Export-ModuleMember -Function Get-ClusterShareNames Export-ModuleMember -Function Get-CurrentOrchestrators Export-ModuleMember -Function Get-DomainCredential Export-ModuleMember -Function Get-DomainIPMapping Export-ModuleMember -Function Get-EndpointAndPort Export-ModuleMember -Function Get-ExecutionContextClusterName Export-ModuleMember -Function Get-ExecutionContextNodeName Export-ModuleMember -Function Get-ExecutionContextRoleName Export-ModuleMember -Function Get-GatewayAddress Export-ModuleMember -Function Get-HostUpdateShare Export-ModuleMember -Function Get-InProgressUpdateVersion Export-ModuleMember -Function Get-InProgressUpdatePackagePath Export-ModuleMember -Function Get-InProgressUpdateNugetStore Export-ModuleMember -Function Get-IsVirtualNetworkAlreadyConfigured Export-ModuleMember -Function Get-JeaSession Export-ModuleMember -Function Get-LocalCsvPathFromSharePath Export-ModuleMember -Function Get-MacAddress Export-ModuleMember -Function Get-MacAddressString Export-ModuleMember -Function Get-NCAccessControlList Export-ModuleMember -Function Get-NCCredential Export-ModuleMember -Function Get-NCGateway Export-ModuleMember -Function Get-NCGatewayPool Export-ModuleMember -Function Get-NCIPPool Export-ModuleMember -Function Get-NCLoadBalancer Export-ModuleMember -Function Get-NCLoadbalancerManager Export-ModuleMember -Function Get-NCLoadBalancerMux Export-ModuleMember -Function Get-NCLogicalNetwork Export-ModuleMember -Function Get-NCLogicalNetworkSubnet Export-ModuleMember -Function Get-NCMACPool Export-ModuleMember -Function Get-NCNetworkInterface Export-ModuleMember -Function Get-NCNetworkInterfaceInstanceId Export-ModuleMember -Function Get-NCNetworkInterfaceResourceId Export-ModuleMember -Function Get-NCPublicIPAddress Export-ModuleMember -Function Get-NCServer Export-ModuleMember -Function Get-NCSwitch Export-ModuleMember -Function Get-NCVirtualGateway Export-ModuleMember -Function Get-NCVirtualNetwork Export-ModuleMember -Function Get-NCVirtualServer Export-ModuleMember -Function Get-NCVirtualSubnet Export-ModuleMember -Function Get-NetworkAddress Export-ModuleMember -Function Get-NetworkDefinitionForCluster Export-ModuleMember -Function Get-NetworkDefinitions Export-ModuleMember -Function Get-NetworkMap Export-ModuleMember -Function Get-NetworkNameForCluster Export-ModuleMember -Function Get-PortProfileId Export-ModuleMember -Function Get-RangeEndAddress Export-ModuleMember -Function Get-ScopeRange Export-ModuleMember -Function Get-ServerResourceId Export-ModuleMember -Function Get-SharePath Export-ModuleMember -Function Get-StorageEndpointName Export-ModuleMember -Function Get-TemporaryDomainCredential Export-ModuleMember -Function Get-UserAccountCredential Export-ModuleMember -Function Initialize-ECESession Export-ModuleMember -Function Invoke-ECECommand Export-ModuleMember -Function Invoke-PSDirectOnVM Export-ModuleMember -Function Invoke-ScriptBlockInParallel Export-ModuleMember -Function Invoke-ScriptBlockWithRetries Export-ModuleMember -Function Invoke-WebRequestWithRetries Export-ModuleMember -Function IsIpPoolRangeValid Export-ModuleMember -Function IsIpWithinPoolRange Export-ModuleMember -Function IsOneNode Export-ModuleMember -Function IsOnVirtualMachine Export-ModuleMember -Function IsVirtualAzureStack Export-ModuleMember -Function JSONDelete Export-ModuleMember -Function JSONGet Export-ModuleMember -Function JSONPost Export-ModuleMember -Function Mount-WindowsImageWithRetry Export-ModuleMember -Function New-ACL Export-ModuleMember -Function New-Credential Export-ModuleMember -Function New-ExecutionContextXmlForNode Export-ModuleMember -Function New-LoadBalancerVIP Export-ModuleMember -Function New-NCAccessControlList Export-ModuleMember -Function New-NCAccessControlListRule Export-ModuleMember -Function New-NCBgpPeer Export-ModuleMember -Function New-NCBgpRouter Export-ModuleMember -Function New-NCBgpRoutingPolicy Export-ModuleMember -Function New-NCBgpRoutingPolicyMap Export-ModuleMember -Function New-NCCredential Export-ModuleMember -Function New-NCGateway Export-ModuleMember -Function New-NCGatewayPool Export-ModuleMember -Function New-NCGreTunnel Export-ModuleMember -Function New-NCIPPool Export-ModuleMember -Function New-NCIPSecTunnel Export-ModuleMember -Function New-NCL3Tunnel Export-ModuleMember -Function New-NCLoadBalancer Export-ModuleMember -Function New-NCLoadBalancerBackendAddressPool Export-ModuleMember -Function New-NCLoadBalancerFrontEndIPConfiguration Export-ModuleMember -Function New-NCLoadBalancerLoadBalancingRule Export-ModuleMember -Function New-NCLoadBalancerMux Export-ModuleMember -Function New-NCLoadBalancerMuxPeerRouterConfiguration Export-ModuleMember -Function New-NCLoadBalancerOutboundNatRule Export-ModuleMember -Function New-NCLoadBalancerProbe Export-ModuleMember -Function New-NCLoadBalancerProbeObject Export-ModuleMember -Function New-NCLogicalNetwork Export-ModuleMember -Function New-NCLogicalNetworkSubnet Export-ModuleMember -Function New-NCLogicalSubnet Export-ModuleMember -Function New-NCMACPool Export-ModuleMember -Function New-NCNetworkInterface Export-ModuleMember -Function New-NCPublicIPAddress Export-ModuleMember -Function New-NCServer Export-ModuleMember -Function New-NCServerConnection Export-ModuleMember -Function New-NCServerNetworkInterface Export-ModuleMember -Function New-NCSlbState Export-ModuleMember -Function New-NCSwitch Export-ModuleMember -Function New-NCSwitchPort Export-ModuleMember -Function New-NCVirtualGateway Export-ModuleMember -Function New-NCVirtualNetwork Export-ModuleMember -Function New-NCVirtualServer Export-ModuleMember -Function New-NCVirtualSubnet Export-ModuleMember -Function New-NCVpnClientAddressSpace Export-ModuleMember -Function NormalizeIPv4Subnet Export-ModuleMember -Function PublishAndStartDscConfiguration Export-ModuleMember -Function PublishAndStartDscForJea Export-ModuleMember -Function Remove-LoadBalancerFromNetworkAdapter Export-ModuleMember -Function Remove-NCAccessControlList Export-ModuleMember -Function Remove-NCCredential Export-ModuleMember -Function Remove-NCGateway Export-ModuleMember -Function Remove-NCGatewayPool Export-ModuleMember -Function Remove-NCIPPool Export-ModuleMember -Function Remove-NCLoadBalancer Export-ModuleMember -Function Remove-NCLoadBalancerMux Export-ModuleMember -Function Remove-NCLogicalNetwork Export-ModuleMember -Function Remove-NCMACPool Export-ModuleMember -Function Remove-NCNetworkInterface Export-ModuleMember -Function Remove-NCPublicIPAddress Export-ModuleMember -Function Remove-NCServer Export-ModuleMember -Function Remove-NCSwitch Export-ModuleMember -Function Remove-NCVirtualGateway Export-ModuleMember -Function Remove-NCVirtualNetwork Export-ModuleMember -Function Remove-NCVirtualServer Export-ModuleMember -Function Remove-PortProfileId Export-ModuleMember -Function Set-MacAndIPAddressSingleNode Export-ModuleMember -Function Set-MacAndIPAddress Export-ModuleMember -Function Set-NCConnection Export-ModuleMember -Function Set-NCLoadBalancerManager Export-ModuleMember -Function Set-PortProfileId Export-ModuleMember -Function Set-PortProfileIdHelper Export-ModuleMember -Function Send-TelemetryEvent Export-ModuleMember -Function Skip-NodesProvidedInRuntimeParameter Export-ModuleMember -Function Test-CloudStatusUpdateCritical Export-ModuleMember -Function Test-IPConnection Export-ModuleMember -Function Test-NetworkMap Export-ModuleMember -Function Test-IsMonthlyUpdate Export-ModuleMember -Function Test-IsHotfixUpdate Export-ModuleMember -Function Test-IsUpdate Export-ModuleMember -Function Test-ManagementClusterNode Export-ModuleMember -Function Test-PSSession Export-ModuleMember -Function Test-WSManConnection Export-ModuleMember -Function Trace-Error Export-ModuleMember -Function Trace-Execution Export-ModuleMember -Function Trace-Warning Export-ModuleMember -Function Update-JEAEndpointsForUpdate Export-ModuleMember -Function Update-NCCredential Export-ModuleMember -Function Update-NCServer Export-ModuleMember -Function Update-NCVirtualServer Export-ModuleMember -Function Update-WindowsDismFeature Export-ModuleMember -Function Wait-Result Export-ModuleMember -Function Wait-VirtualNetwork Export-ModuleMember -Function Read-SummaryXml Export-ModuleMember -Function Get-FailureListFromMainActionPlan Export-ModuleMember -Function Get-AzureStackHostAllVirtualSwitchNames Export-ModuleMember -Function Get-AzureStackHostDefaultVirtualSwitchName Export-ModuleMember -Function Set-TrustedHosts # SIG # Begin signature block # MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCq7KjTj0Qhr0Br # fcAa/apM/k33E0XcvQ+h4UD1Pf277qCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEg9N8mPyY/OVP/y7gqiujpl # E+Y8tZCyPAjsqGChMMRLMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAVPgKFc5Yzkq40aYjPQKZGVOmr8vAFBUOVYv9nhtPqaF8t2w73b29rdev # UWMkdrPmgoegR8BpNa3+A28BPYOVPubf9RDc0PRZWZT+Olemb3D4lgLocl6KnqiN # AWPA87stH7j6UVVL3CWO9C4zECLnCqVdrvri/iRhr87Q5hCWDjKxnSo4Qap74Rt7 # +cbA67DW8URXzD0G4S6500MjsmlR3fMXsyq1iT6nxf9Xl5j92nRwED4WuCEL0Mce # NXcxzbmIFxYHorSn8JaINJlLdpwAbdkIjdWmFBDEewBhfUAuZoxBGjKSSSjoCnQp # o/XadWttndtRlHi4bIZvQqDhpMi2uqGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC # FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq # hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCB/2sFWDOemoxDO7Z1uMnbVUPjBjJzMWNEIGnGDo5TjeAIGZpe2/i7o # GBMyMDI0MDcyMzExMDA1OC41ODNaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO # Ojg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJ # KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx # MDEyMTkwNzA5WhcNMjUwMTEwMTkwNzA5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl # cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRC # QkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKhOA5RE6i53nHURH4lnfKLp # +9JvipuTtctairCxMUSrPSy5CWK2DtriQP+T52HXbN2g7AktQ1pQZbTDGFzK6d03 # vYYNrCPuJK+PRsP2FPVDjBXy5mrLRFzIHHLaiAaobE5vFJuoxZ0ZWdKMCs8acjhH # UmfaY+79/CR7uN+B4+xjJqwvdpU/mp0mAq3earyH+AKmv6lkrQN8zgrcbCgHwsqv # vqT6lEFqYpi7uKn7MAYbSeLe0pMdatV5EW6NVnXMYOTRKuGPfyfBKdShualLo88k # G7qa2mbA5l77+X06JAesMkoyYr4/9CgDFjHUpcHSODujlFBKMi168zRdLerdpW0b # BX9EDux2zBMMaEK8NyxawCEuAq7++7ktFAbl3hUKtuzYC1FUZuUl2Bq6U17S4CKs # qR3itLT9qNcb2pAJ4jrIDdll5Tgoqef5gpv+YcvBM834bXFNwytd3ujDD24P9Dd8 # xfVJvumjsBQQkK5T/qy3HrQJ8ud1nHSvtFVi5Sa/ubGuYEpS8gF6GDWN5/KbveFk # dsoTVIPo8pkWhjPs0Q7nA5+uBxQB4zljEjKz5WW7BA4wpmFm24fhBmRjV4Nbp+n7 # 8cgAjvDSfTlA6DYBcv2kx1JH2dIhaRnSeOXePT6hMF0Il598LMu0rw35ViUWcAQk # UNUTxRnqGFxz5w+ZusMDAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUbqL1toyPUdpF # yyHSDKWj0I4lw/EwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD # VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG # CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw # MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD # CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAC5U2bINLgXIHWbM # cqVuf9jkUT/K8zyLBvu5h8JrqYR2z/eaO2yo1Ooc9Shyvxbe9GZDu7kkUzxSyJ1I # ZksZZw6FDq6yZNT3PEjAEnREpRBL8S+mbXg+O4VLS0LSmb8XIZiLsaqZ0fDEcv3H # eA+/y/qKnCQWkXghpaEMwGMQzRkhGwcGdXr1zGpQ7HTxvfu57xFxZX1MkKnWFENJ # 6urd+4teUgXj0ngIOx//l3XMK3Ht8T2+zvGJNAF+5/5qBk7nr079zICbFXvxtidN # N5eoXdW+9rAIkS+UGD19AZdBrtt6dZ+OdAquBiDkYQ5kVfUMKS31yHQOGgmFxuCO # zTpWHalrqpdIllsy8KNsj5U9sONiWAd9PNlyEHHbQZDmi9/BNlOYyTt0YehLbDov # mZUNazk79Od/A917mqCdTqrExwBGUPbMP+/vdYUqaJspupBnUtjOf/76DAhVy8e/ # e6zR98PkplmliO2brL3Q3rD6+ZCVdrGM9Rm6hUDBBkvYh+YjmGdcQ5HB6WT9Rec8 # +qDHmbhLhX4Zdaard5/OXeLbgx2f7L4QQQj3KgqjqDOWInVhNE1gYtTWLHe4882d # /k7Lui0K1g8EZrKD7maOrsJLKPKlegceJ9FCqY1sDUKUhRa0EHUW+ZkKLlohKrS7 # FwjdrINWkPBgbQznCjdE2m47QjTbMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ # mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh # dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1 # WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK # NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg # fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp # rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d # vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9 # 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR # Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu # qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO # ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb # oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6 # bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t # AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW # BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb # UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku # aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA # QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2 # VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu # bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw # LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt # MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q # XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6 # U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt # I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis # 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp # kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0 # sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e # W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ # sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7 # Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0 # dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ # tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh # bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 # NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUANiNHGWXbNaDPxnyiDbEOciSjFhCggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOpJdQowIhgPMjAyNDA3MjMwODE3MTRaGA8yMDI0MDcyNDA4MTcxNFowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA6kl1CgIBADAKAgEAAgIWzAIB/zAHAgEAAgISADAK # AgUA6krGigIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAM/yvW46Tfmf9PgY # PMpdMBmO6eq7h7IsmYW0MRG4rCw1dyYuuqyen4zQgQgzuaLWy73nxqL1U7MsFKjK # TQbfYGLwNR0M/75gV0LSmNg3wF8XUSyzEofEoQ7+XititxRsqQUdG9rUlqDwzcLN # jZOfCEnaHs7b4Z285bVBG7bz0xLhMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQg4luW9ALt4rNq5i8faemTm+IdCUw0wEghDccY4RKSZ50wgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCBh/w4tmmWsT3iZnHtH0Vk37UCN02lRxY+RiON6 # wDFjZjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # 3V1XHZXUraobAAEAAAHdMCIEIEp7n5pGcuIf/ZyfQzoerKhUHPh7qyr2pSWrjtMy # y1bdMA0GCSqGSIb3DQEBCwUABIICAFgyezkEqZz4zWdpDXKwma7Bn2Tb7ObBPmCn # p1EGjrYwum97jY/M7D+1DMRvricawz+IUf3eJZasbbUfXpApfoDIrfhUdUsSv+nu # N2lCHFIZL6WClkQ49ANTdckJ+LAmLs2W3hdCJb4BZ6MHeZwNqxsATsfGx2lmYFxS # /foPWf2yDS5n5RwB5ivOP8yszDFEYVEuoesMyXC3rKTp8ebpkHW9orPr5D2gipJV # yRVNvgbaq2QsZUv6iZAyP2zfKsoYr2aGcZ2Sl2w/awTDFw/gAKKvyfJe+ICOv44W # lZyOkV48XD28f7EPnA0vu1GRxsD47iaEhDOs9ZTL8B+PfLlD+WwSwOgweRMsYDDp # 4uQNHymY8zPB1W7UwU1H1BygskqHLPsdgibBe3swFq2nE9iQ0yB408O6LQr3lGRo # 26QZyuj64+iTSbZxb82IlapfD6gTW255KyK6biJrWvigWXhtu6Bygxt2Ymj9s43H # m+63E5NeIm4AaI2PVXuPYNQXu193vgbeoiZcZC2OGkbWBWs+UQK4fHP0pq3RxjNE # Dumbod4q75QlM+98/GIy8qPd8UXLEXxRaWU3UQ0rDqAF0pum78WX7e/KMY1L8ELw # NzFcbpZsUiLhijV7VrU7sTexNFhS/nmXQ6uO821JmWK1f1YUYMNdaz9hQSPQ//ie # guMewnzv # SIG # End signature block |