Obs/bin/ObsDep/content/Powershell/Common/Helpers.psm1
<###################################################
# # # Copyright (c) Microsoft. All rights reserved. # # # ##################################################> Import-Module $PSScriptRoot\Tracer.psm1 # Parameters passed to Invoke-ECECommand that are not forwarded to New-PSSession $NonPSSessionParameters = @( "session", "AsJob", "InDisconnectedSession", "SessionName", "HideComputerName", "JobName", "ScriptBlock", "NoNewScope", "FilePath", "InputObject", "ArgumentList", "PreLoadNugetName", "PreLoadScript", "RoleId") # Parameters passed to Invoke-ECECommand which are passed to Invoke-Command $InvokeSpecificParameters = @( "ArgumentList", "InputObject", "FilePath", "ScriptBlock", "JobName", "AsJob", "HideComputerName", "SessionName", "InDisconnectedSession") function Mount-WindowsImageWithRetry { <# .SYNOPSIS Call mount-windowsimage with retries .DESCRIPTION Retries the mount-windowsimage cmdlet to work around Windows regression in detecting system volume, tracked by AzsureStack bug 10566165. .EXAMPLE Mount-WindowsImageWithRetry .PARAMETER ImagePath The path to the image. .PARAMETER Path The mount path to use. .PARAMETER Index The index to use. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)] [string] $ImagePath, [Parameter(Mandatory=$true)] [string] $Path, [Parameter(Mandatory=$true)] [UInt32] $Index ) PROCESS { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $isImageMounted = $false $retryAttempt = 0 $maxMountRetry = 4 while (-not($isImageMounted) -and ($retryAttempt -lt $maxMountRetry)) { $retryAttempt++ try { $hName = (hostname) $uName = (whoami) Trace-Execution "Trying to mount windows image at '$ImagePath' to folder path '$Path' with index '$Index' on machine '$hName' using creds of '$uName'. Retry attempt: '$retryAttempt'." $null = Mount-WindowsImage -ImagePath $ImagePath -Path $Path -Index $Index -Verbose:$false $isImageMounted = $true } catch { Trace-Execution "Discarding any partially mounted image. Mount Path: '$Path'" try { Dismount-WindowsImage -Path $Path -Discard -ErrorAction Ignore } catch { # Best effort, Dismount-WindowsImage does not respect ErrorAction # More targeted dismount remediation (e.g. due to failover) will be in global remediation SeedRing:Repair Trace-Execution "Best effort dismount attempt: $_" } if (Test-Path -Path "$ImagePath.DISM.vhdx") { Trace-Execution "Removing '$ImagePath.DISM.vhdx'" Remove-Item -Path "$ImagePath.DISM.vhdx" -Force } if ($retryAttempt -lt $maxMountRetry) { $exceptionMessage = $_.Exception.Message Trace-Execution "Failure during mount attempt: '$exceptionMessage'. Retrying." } else { throw } } } } } function Wait-Result { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ScriptBlock[]] $ValidationScript, # Time out in seconds. [Int32] $TimeOut = 30, # Time between checks. [Int32] $Interval = 1 ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Output all logging streams to a file for debugging $logLocation = "$env:systemdrive\Windows\Temp\WaitResultLogs" if(-not (Test-Path $logLocation)) { $null = New-Item -Path $logLocation -Type Directory } $logFileInstance = $null for ($retryNumber = 0; $retryNumber -lt 10; $retryNumber++) { $logName = "WaitResult-" + (Get-Date).ToString("yyyy-MM-dd-HH-mm-ss-ffff") + ".log" $logfile = Join-Path -Path $logLocation -ChildPath $logName try { $logFileInstance = new-item $logfile -type file break } catch { Write-Verbose "Result log file name $logfile is already in use" Start-Sleep -Milliseconds 50 } } if ($logFileInstance -eq $null) { Trace-Error "Log file could not be created for WaitResult" } $ErrorActionPreference = [System.Management.Automation.ActionPreference]::SilentlyContinue $endTime = [DateTime]::Now.AddSeconds($TimeOut) while ([DateTime]::Now -lt $endTime) { $succeedAll = $true $newValidationScript = @($ValidationScript) foreach ($script in $ValidationScript) { $succeedOne = $false try { Write-Verbose "Validate if the script`n$($script.ToString())`nreturns $true." -Verbose *>>$logfile $errorCountBefore = $global:error.Count $succeedOne = $script.Invoke() Write-Verbose "Script Results:" -Verbose *>>$logfile $succeedOne *>>$logfile $errorCountAfter = $global:error.Count $numberOfNewErrors = $errorCountAfter - $errorCountBefore if ($numberOfNewErrors -gt 0) { $global:error.GetRange(0, $numberOfNewErrors) *>>$logfile $global:error.RemoveRange(0, $numberOfNewErrors) } } catch { $_ *>>$logfile $global:error.RemoveAt(0) } $succeedAll = $succeedAll -and $succeedOne if (-not $succeedOne) { Write-Verbose "-----Validation failed-----" -Verbose *>>$logfile # No need to check all if at least one fails. break } else { Write-Verbose "-----Validation passed-----" *>>$logfile $newValidationScript = $newValidationScript -ne $script } } if ($succeedAll) { Write-Verbose "All validations passed." -Verbose *>>$logfile break } $ValidationScript = $newValidationScript Write-Verbose "Wait for $Interval seconds." -Verbose *>>$logfile Start-Sleep -Seconds $Interval } return $succeedAll } function New-Credential { param ( [Parameter(Mandatory = $true)] [string] $UserName, [string] $Password ) $secureString = ConvertTo-SecureString $Password -AsPlainText -Force New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $secureString } function Initialize-ECESession { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0)] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $RoleId, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $PreLoadNugetName, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $PreLoadScript, [Parameter(Mandatory=$false)] [switch] $SuppressVerbose ) PROCESS { # function purposely made a no-op and will be refactored away. This functionality is in New-AzsSession directly now. } } function Invoke-ECECommand { <# .SYNOPSIS Runs a command against an infra vm with helper functions and pre loaded script functions automatically available to the command. .DESCRIPTION Uses a PSsession and loads all helper functions into it so that infra vm common functions can be readily used. Optionally loads functions from the provided preLoadScript. Finally runs the command supplied in this session where preloaded functions can be readily used by the command. #> [CmdletBinding(DefaultParameterSetName='InProcess', HelpUri='http://go.microsoft.com/fwlink/?LinkID=135225', RemotingCapability='OwnedByCommand')] PARAM ( [Parameter(ParameterSetName='Session', Position=0)] [Parameter(ParameterSetName='FilePathRunspace', Position=0)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter(ParameterSetName='ComputerName', Position=0)] [Parameter(ParameterSetName='FilePathComputerName', Position=0)] [Alias('Cn')] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [Parameter(ParameterSetName='ComputerName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='Uri', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathComputerName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathUri', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='VMId', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='VMName', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathVMId', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathVMName', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [pscredential] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='ComputerName')] [ValidateRange(1, 65535)] [int] $Port, [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='FilePathComputerName')] [switch] $UseSSL, [Parameter(ParameterSetName='FilePathUri', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='Uri', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathComputerName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ComputerName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ContainerId', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='VMName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathContainerId', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathVMId', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathVMName', ValueFromPipelineByPropertyName=$true)] [string] $ConfigurationName, [Parameter(ParameterSetName='ComputerName', ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathComputerName', ValueFromPipelineByPropertyName=$true)] [string] $ApplicationName, [Parameter(ParameterSetName='FilePathUri')] [Parameter(ParameterSetName='Session')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='FilePathRunspace')] [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='VMId')] [Parameter(ParameterSetName='VMName')] [Parameter(ParameterSetName='ContainerId')] [Parameter(ParameterSetName='FilePathVMId')] [Parameter(ParameterSetName='FilePathVMName')] [Parameter(ParameterSetName='FilePathContainerId')] [int] $ThrottleLimit, [Parameter(ParameterSetName='FilePathUri', Position=0)] [Parameter(ParameterSetName='Uri', Position=0)] [Alias('URI','CU')] [ValidateNotNullOrEmpty()] [uri[]] $ConnectionUri, [Parameter(ParameterSetName='FilePathRunspace')] [Parameter(ParameterSetName='Session')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='FilePathUri')] [Parameter(ParameterSetName='VMId')] [Parameter(ParameterSetName='VMName')] [Parameter(ParameterSetName='ContainerId')] [Parameter(ParameterSetName='FilePathVMId')] [Parameter(ParameterSetName='FilePathVMName')] [Parameter(ParameterSetName='FilePathContainerId')] [switch] $AsJob, [Parameter(ParameterSetName='FilePathUri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='ComputerName')] [Alias('Disconnected')] [switch] $InDisconnectedSession, [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='FilePathComputerName')] [ValidateNotNullOrEmpty()] [string[]] $SessionName, [Parameter(ParameterSetName='VMName')] [Parameter(ParameterSetName='Session')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='FilePathRunspace')] [Parameter(ParameterSetName='FilePathUri')] [Parameter(ParameterSetName='VMId')] [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='ContainerId')] [Parameter(ParameterSetName='FilePathVMId')] [Parameter(ParameterSetName='FilePathVMName')] [Parameter(ParameterSetName='FilePathContainerId')] [Alias('HCN')] [switch] $HideComputerName, [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='Session')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='FilePathRunspace')] [Parameter(ParameterSetName='FilePathUri')] [Parameter(ParameterSetName='ContainerId')] [Parameter(ParameterSetName='FilePathContainerId')] [string] $JobName, [Parameter(ParameterSetName='VMId', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='Session', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='Uri', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='InProcess', Mandatory=$true, Position=0)] [Parameter(ParameterSetName='ComputerName', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='VMName', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='ContainerId', Mandatory=$true, Position=1)] [Alias('Command')] [ValidateNotNull()] [scriptblock] $ScriptBlock, [Parameter(ParameterSetName='InProcess')] [switch] $NoNewScope, [Parameter(ParameterSetName='FilePathVMId', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='FilePathRunspace', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='FilePathUri', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='FilePathComputerName', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='FilePathVMName', Mandatory=$true, Position=1)] [Parameter(ParameterSetName='FilePathContainerId', Mandatory=$true, Position=1)] [Alias('PSPath')] [ValidateNotNull()] [string] $FilePath, [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathUri')] [switch] $AllowRedirection, [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='FilePathUri')] [System.Management.Automation.Remoting.PSSessionOption] $SessionOption, [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathUri')] [System.Management.Automation.Runspaces.AuthenticationMechanism] $Authentication, [Parameter(ParameterSetName='FilePathComputerName')] [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='Uri')] [Parameter(ParameterSetName='FilePathUri')] [switch] $EnableNetworkAccess, [Parameter(ParameterSetName='ContainerId')] [Parameter(ParameterSetName='FilePathContainerId')] [switch] $RunAsAdministrator, [Parameter(ValueFromPipeline=$true)] [psobject] $InputObject, [Alias('Args')] [System.Object[]] $ArgumentList, [Parameter(ParameterSetName='FilePathVMId', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='VMId', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [Alias('VMGuid')] [ValidateNotNullOrEmpty()] [guid[]] $VMId, [Parameter(ParameterSetName='VMName', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='FilePathVMName', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string[]] $VMName, [Parameter(ParameterSetName='FilePathContainerId', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Parameter(ParameterSetName='ContainerId', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string[]] $ContainerId, [Parameter(ParameterSetName='ComputerName')] [Parameter(ParameterSetName='Uri')] [string] $CertificateThumbprint, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $PreLoadNugetName, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $PreLoadScript, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $RoleId ) PROCESS { $errorActionPreference = 'stop' $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $sessionParameters = @{} + $PSBoundParameters; foreach ($parameter in $NonPSSessionParameters) { # Remove all parameters which do not get forwarded to New-PSSession. if ($sessionParameters.Contains($parameter)) { $sessionParameters.Remove($parameter) } } $invokeParameters = @{} Trace-Execution "Passing the following parameters:" foreach ($parameter in $InvokeSpecificParameters) { # Add all parameters which should be forwarded to the wrapped Invoke-Command. if ($PSBoundParameters.ContainsKey($parameter)) { $invokeParameters.Add($parameter, $PSBoundParameters[$parameter]) # Trace the Argument List only for now if ($parameter.Equals('ArgumentList')) { Trace-Execution "[$parameter] = [$($PSBoundParameters[$parameter] | ConvertTo-Json -Depth 1)]" } } } if (-not $Session) { Trace-Execution "Creating remote powershell session on $ComputerName" $SessionCreated = $true $Session = & Microsoft.PowerShell.Core\New-PSSession @sessionParameters } $DebuggingCallStack = Get-PSCallStack # Make available Get-ASArtifactPath cmdlet in the session. $ComputerName = $Session.ComputerName Trace-Execution "Initializing remote powershell session on $ComputerName with common functions." $scriptPath = Join-Path $PSScriptRoot "InfraVmHelpers.psm1" Trace-Execution "Loading infra vm helpers ($scriptPath) to session on $ComputerName" Microsoft.PowerShell.Core\Invoke-Command -Session $Session -ScriptBlock { Import-Module CloudCommon } try { $TimeString = Get-Date -Format "yyyyMMdd-HHmmss" $RemoteLOGFILE = "$env:LocalRootFolderPath\MASLogs\$RoleId_$callingCommand_$TimeString.log" if ($PreLoadNugetName -and $PreLoadScript) { Initialize-NugetScript -Session $Session -PreLoadNugetName $PreLoadNugetName -PreLoadScript $PreLoadScript -Verbose:$VerbosePreference } $invokeParameters.Add("Session", $Session); Trace-Execution "Invoking command on remote session..." $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Core\Invoke-Command', [System.Management.Automation.CommandTypes]::Cmdlet) & $wrappedCmd @invokeParameters } finally { # Cleanup ps session on failure. if ($Session -and $SessionCreated) { Remove-PSSession -ErrorAction Ignore -Session $Session } } } <# .ForwardHelpTargetName Microsoft.PowerShell.Core\Invoke-Command .ForwardHelpCategory Cmdlet #> } function Expand-NugetContent { <# .SYNOPSIS Expands the content of a nuget to a destination. .DESCRIPTION Copies files or folders from a nuget to destination. Folders are copied excluding the folder name given, that is content under the source folder and not the folder itself. Empty string can be given for the entire nuget. .EXAMPLE Expand-NugetContent -NugetName "MyNuget" -SourcePath "content" -DestinationPath "C:\temp\" .EXAMPLE Expand-NugetContent -NugetName "MyNuget" -SourcePath "content\Folder\MyFile.txt" -DestinationPath "C:\temp\" -SourceIsFile .PARAMETER NugetName The name of the nuget to source the content from. .PARAMETER SourcePath The relative path inside the nuget to a folder or file. If empty string is passed all nuget contents will be expanded. .PARAMETER DestinationPath A destination folder under which all the nuget content will be copied. .PARAMETER NugetStorePath The source path for the nuget package .PARAMETER SourceIsFile A flag indicating the source path represents a file and not a folder and should be copied as an individual file to the destination. .PARAMETER Version The specific version of the nuget package, null if the latest version is required #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string] $NugetName, [Parameter(Mandatory=$true)] [string] [AllowEmptyString()] $SourcePath, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $DestinationPath, [Parameter()] [ValidateNotNullOrEmpty()] [string] $NugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore", [Parameter()] [switch] $SourceIsFile, [Parameter()] [string] $Version = $null, [Parameter()] [switch] $IsNugetInstall, [Parameter()] [switch] $IsUnc ) PROCESS { $ErrorActionPreference = "stop" Write-Verbose -Verbose "Finding nuget package $NugetName with version [$Version] from store $NugetStorePath" #Handle the unavailability of Nuget store path gracefully. Avoid throwing exception in this case. Any unwanted error record in the error stream will cause ECE to fail. if ($Version) { $nugetPackage = Find-Package -Source $NugetStorePath -Name $NugetName -ProviderName "nuget" -Verbose -Force -ForceBootstrap:$false -ErrorAction Ignore -RequiredVersion $Version } else { $nugetPackage = Find-Package -Source $NugetStorePath -Name $NugetName -ProviderName "nuget" -Verbose -Force -ForceBootstrap:$false -ErrorAction Ignore } if($nugetPackage) { if(!$IsNugetInstall.IsPresent) { [Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null $nugetPackageFileLocation = Join-Path $NugetStorePath $nugetPackage.PackageFileName Write-Verbose -Verbose "Opening nuget package $nugetPackageFileLocation" $nugetZipFile = [IO.Compression.ZipFile]::OpenRead($nugetPackageFileLocation) try { # Check for an exact file match or for a folder match. # If the source path is empty string we take every entry. # If the source path is a file we take the file. # If the source path is a folder we append a "/" and then pull all items under that folder. $entriesToCopy = $nugetZipFile.Entries if (![string]::IsNullOrEmpty($SourcePath)) { $normalizedSourcePath = [System.IO.Path]::GetFullPath($SourcePath) $exactFileMatch = $entriesToCopy | Where-Object { [string]::Equals([System.IO.Path]::GetFullPath($_.FullName), $normalizedSourcePath, [System.StringComparison]::OrdinalIgnoreCase) } if ($exactFileMatch) { $entriesToCopy = $exactFileMatch } else { if (!$normalizedSourcePath.EndsWith([System.IO.Path]::DirectorySeparatorChar, [System.StringComparison]::Ordinal)) { $normalizedSourcePath += [System.IO.Path]::DirectorySeparatorChar } $entriesToCopy = $entriesToCopy | Where-Object { [System.IO.Path]::GetFullPath($_.FullName).StartsWith($normalizedSourcePath, [System.StringComparison]::OrdinalIgnoreCase) } } } Write-Verbose -Verbose "Copying $($entriesToCopy.Count) files from $SourcePath to $DestinationPath" if ($SourceIsFile.IsPresent) { $SourcePath = [System.IO.Path]::GetDirectoryName($SourcePath) } foreach ($entryToCopy in $entriesToCopy) { $finalDestinationPath = $entryToCopy.FullName.SubString($SourcePath.Length) $finalDestinationPath = [uri]::UnescapeDataString($finalDestinationPath) $finalDestinationPath = Join-Path $DestinationPath $finalDestinationPath # create the destination directory structure $finalDestinationPathParent = Split-Path $finalDestinationPath -Parent New-Item -ItemType Directory -Force -Path $finalDestinationPathParent | Out-Null if( -not $IsUnc) { $finalDestinationPath = '\\?\' + $finalDestinationPath } try { [IO.Compression.ZipFileExtensions]::ExtractToFile($entryToCopy, $finalDestinationPath, $true) } catch { Trace-Warning "Failed to extract file to $finalDestinationPath" throw } } } finally { Write-Verbose -Verbose "Closing nuget package" $nugetZipFile.Dispose() } } else { Write-Verbose -Verbose "Installing Nuget package $NugetName with version [$Version] at $DestinationPath" Import-Module $PSScriptRoot\..\Roles\Cloud\Cloud.psm1 -DisableNameChecking Install-Nuget -NugetName $NugetName -NugetSourcePath $NugetStorePath -NugetDestinationPath $DestinationPath -Version $Version | Out-Null } } else { Write-Verbose -Verbose "Could not find package $NugetName with version [$Version] from store $NugetStorePath. Looking for package using Get-ASArtifactPath." $expandedNugetLocation = Get-ASArtifactPath -NugetName $NugetName -Version $Version $fullSourcePath = Join-Path $expandedNugetLocation $SourcePath if((!$SourceIsFile.IsPresent) -and (Test-Path -Path $DestinationPath -PathType Container)) { #Append \* only if the destination is directory and already exists. $fullSourcePath += '\*' } #Enable support for long paths > 260 chars in the source and destination. $fullSourcePath = '\\?\' + $fullSourcePath $DestinationPath = '\\?\' + $DestinationPath Write-Verbose -Verbose "Copy nuget contents from $fullSourcePath to $DestinationPath." Copy-Item -Path $fullSourcePath -Destination $DestinationPath -Recurse -Force -Verbose } } } function ConnectPSSession { <# .SYNOPSIS Start PS session to one of the provided machines. .DESCRIPTION Return the first successful connection. Throw if no PS session can be made. .EXAMPLE $RemoteSession = ConnectPSSession $Machines $Credential .PARAMETER Machines The list of machine names. .PARAMETER Credential The credential used to connect to machines. #> [CmdletBinding()] PARAM ( [Parameter(Position=0, Mandatory=$true)] [string[]] $Machines, [Parameter(Position=1, Mandatory=$true)] [PSCredential] $Credential ) PROCESS { $RemoteSession = $null foreach ($Machine in $Machines) { Trace-Execution "Try to start PS remote session to $Machine with user $($Credential.UserName)" try { $RemoteSession = New-PSSession -ComputerName $Machine -Credential $Credential -Authentication Credssp -ErrorAction Stop if ($RemoteSession) { Trace-Execution "Connection to $Machine was successful." return $RemoteSession } } catch { Trace-Warning "Failed to connect to $Machine with exception $($_ | Out-String)" } } throw "Could not create a PSSession to any of the specified machines." } } # This function works around a bug in PowerShell where cmdlets that # return a CimInstance don't actually stop when $ErrorActionPreference is # set to 'Stop'. You have to specify it on the cmdlet itself. function PublishAndStartDscConfiguration { [CmdletBinding(DefaultParameterSetName='PublishAndStart')] Param( [Parameter(Mandatory=$true, ParameterSetName="PublishOnly")] [Parameter(Mandatory=$true, ParameterSetName="PublishAndStart")] [String]$Path, [Parameter(Mandatory=$true)] [String[]]$ComputerName, [int]$RetryCount = 3, [PSCredential]$Credential = $null, [Parameter(ParameterSetName="PublishOnly")] [switch] $PublishOnly, [Parameter(ParameterSetName="StartOnly")] [switch] $StartOnly, # By default, applied configurations will be validated by running an additional Test-DscConfiguration after # Start-DscConfiguration has completed successfully. However, some configurations use DSC resources with non- # functional Test() implementations (by design). When invoking configs with such resources, callers can pass # this flag to allow all the Set() resources to be executed, but without performing the additional Test check. [switch] $SkipTestDscConfiguration ) Import-Module "$PSScriptRoot\..\Roles\Common\RemoteSessionHelpers.psm1" -DisableNameChecking try { # The DCOM session is necessary because attempting to query DSC state through WSMan while JEA endpoints # are being registered by DSC can result in a deadlock of the WinRM service on the target node. $sessionParams = @{ ComputerName = $ComputerName } if ($Credential) { $sessionParams.Credential = $Credential } $sessionDcom = New-CimSessionWithDcom @sessionParams # Deployment code sometimes compiles DSC configuration mofs for multiple target nodes into the same output directory. # We don't have an easy way to separate these without parsing the MOF, so the wait for LCM state will block until # all nodes have stopped processing existing DSC configs (if any). This can be optimized in the future if we change the # publishing mechanism to target MOFs on a per-node basis. Wait-LCMState -CimSession $sessionDcom -TimeoutMinutes 30 if (-not $StartOnly) { Trace-Execution "Publishing DSC configuration from $Path to the following target nodes: $($ComputerName -join ', ')" $publishParams = @{ Path = $Path ErrorAction = "Stop" Force = $true } if ($Credential) { $publishParams.Credential = $Credential } Publish-DscConfiguration @publishParams } if ($PublishOnly) { Trace-Execution "Publish-only mode specified. Not starting DSC config execution." return } $targetNodesRemaining = $ComputerName for ($i = 1; $i -le $RetryCount; $i++) { Trace-Execution "DSC attempt #$i. Starting DSC configuration on the following target nodes: $($targetNodesRemaining -join ', ')" $jobs = @() $mostRecentExceptions = @{} foreach ($targetNode in $targetNodesRemaining) { # Start-DscConfiguration returns a PS job, which we use to track the progress of the DSC configuration. # We do not pass the -Force parameter, which means that if another DSC configuration has pre-empted this attempt, # the job will be marked as Failed and we will retry on the next pass. $startParams = @{ JobName = $targetNode CimSession = $sessionDcom | where ComputerName -eq $targetNode UseExisting = $true Verbose = $true ErrorAction = "Stop" } $jobs += Start-DscConfiguration @startParams } # Wait for jobs for up to 30 minutes. $timeoutSeconds = 30 * 60 Trace-Execution "Waiting for up to $timeoutSeconds seconds for jobs to complete on $($targetNodesRemaining -join ', ')" $jobs | Wait-Job -Timeout $timeoutSeconds foreach ($job in $jobs) { Trace-Execution "Output from job $($job.Name), which is in state: $($job.State):" $job.ChildJobs[0].Warning | Trace-Warning $job.ChildJobs[0].Verbose | Trace-Execution $job.ChildJobs[0].Error | Trace-Warning if ($job.State -eq "Completed") { $nodeName = $job.Name Trace-Execution "DSC configuration converged successfully on $($nodeName)." if (-not $SkipTestDscConfiguration) { Trace-Execution "Validating DSC configuration on $($nodeName)." $nodeSession = $sessionDcom | where ComputerName -eq $nodeName $result = Test-DscConfiguration -Detailed -CimSession $nodeSession -Verbose if (-not $result -or -not $result.InDesiredState) { $msg = "Even though Start-DscConfiguration completed without errors, Test-DscConfiguration indicates that not all resources have converged on $($nodeName)." $msg += "Resources not in desired state:`r`n$($result.ResourcesNotInDesiredState | Out-String)" Trace-Execution $msg $mostRecentExceptions[$nodeName] = $msg continue } } else { Trace-Execution "Additional validation (Test-DscConfiguration) skipped because -SkipTestDscConfiguration was specified." } Trace-Execution "DSC configuration in desired state on $($nodeName)." $targetNodesRemaining = @( $targetNodesRemaining | where { $_ -ne $nodeName } ) } elseif ($job.State -eq "Running") { Trace-Execution "DSC configuration on $($job.Name) did not complete in $timeoutSeconds seconds. Stopping DSC configuration." $job | Stop-Job } elseif ($job.State -eq "Failed") { # Capturing exception here only. The rest of the output can is already traced above, so piping it to Out-Null. $job | Receive-Job -ErrorVariable "jobError" -ErrorAction SilentlyContinue | Out-Null # Since the session is using DCOM protocol, the detailed error message is not returned. We need to get the message from Windows event logs. # There is no easy way to deterministically map the event with the DSC operation, so a workaround would be getting the latest five error message from the target node. # The events are filtered by Event Id 4097 as this type of event gives us the most complete information of an error. Start-Sleep 5 # give some time for Windows event logs to be processed. $event = Get-WinEvent -LogName "Microsoft-Windows-Dsc/Operational" -ComputerName $job.Name -FilterXPath "*[System[(EventID=4097)]]" $jobError += "`nLatest five DSC errors within $timeoutSeconds seconds in Windows Event logs on $($job.Name):" $jobError += $event | where {$_.TimeCreated.AddSeconds($timeoutSeconds) -ge $job.PSBeginTime} | sort TimeCreated | select TimeCreated, Message -Last 5 | fl | Out-String Trace-Execution "DSC configuration on $($job.Name) failed to converge. Exception from job:`r`n$jobError" $mostRecentExceptions[$job.Name] = $jobError } else { $msg = "Unexpected job state for $($job.Name). Details: $($job | fl * | Out-String)" Trace-Execution $msg $mostRecentExceptions[$job.Name] = $msg } } if (-not $targetNodesRemaining) { Trace-Execution "Completed DSC configuration on all target nodes: $($ComputerName -join ', ')" return } } } catch { $invocationException = $_ } finally { $jobs | Stop-Job -ErrorAction Ignore $jobs | Remove-Job -Force -ErrorAction Ignore $sessionDcom | Remove-CimSession -ErrorAction Ignore } $failureMessage = $null if ($invocationException) { $failureMessage += "$($invocationException | Out-String)" } if ($targetNodesRemaining) { Trace-Execution "The following target nodes have failed to converge DSC configuration: $($targetNodesRemaining -join ',')" $failureMessage += "DSC failures:`r`n" foreach ($targetNode in $targetNodesRemaining) { if ($mostRecentExceptions.$targetNode) { $failureMessage += "$($targetNode): $($mostRecentExceptions.$targetNode)`r`n" } else { $failureMessage += "$($targetNode): DSC did not converge, DSC configuration was cancelled.`r`n" } } } if ($failureMessage) { throw $failureMessage } } <# .SYNOPSIS Wait for LCM at the target session to be in the specified state (Idle by default). If the state is not reached within the specified timeout, this method throws an exception. #> function Wait-LCMState { param ( [Parameter(Mandatory=$true)] [CimSession[]] $CimSession, [int] $TimeoutMinutes = 30 ) function Trace-LCMState ([array] $ConfigManager) { $ConfigManager | % { Trace-Execution "$($_.PSComputerName): $($_.LCMState)" } } Trace-Execution "Waiting up to $TimeoutMinutes minutes for LCM on $($CimSession.ComputerName) to exit the Busy state." $configManager = @( Get-DscLocalConfigurationManager -CimSession $CimSession ) $endTime = (Get-Date).AddMinutes($TimeoutMinutes) while ($configManager.LCMState -contains "Busy" -and ((Get-Date) -lt $endTime)) { Trace-Execution "Waiting for LCM to exit Busy state..." Trace-LCMState $configManager Start-Sleep -Seconds 10 $configManager = Get-DscLocalConfigurationManager -CimSession $CimSession } if ($configManager.LCMState -contains "Busy") { throw "LCM State still busy after $TimeoutMinutes minutes." } Trace-LCMState $configManager } function Test-WSManConnection { <# .SYNOPSIS Tests if WSMan is operational on a machine. .DESCRIPTION Tests if wsmans is operational on a machine using WSMan Identify. This supports retries if there is an expectation that WSMan will come up on a machine after some time. TimeoutMs is the time to wait per attempt for WSMan to be ready. .EXAMPLE Test-WSManConnection -ComputerName $ComputerName -TimeoutMs 1000 -RetryCount 10 .PARAMETER ComputerName The name of the computer(s) on which to test WinRM connectivity .PARAMETER RetryDelaySec The time in seconds to wait between attempts. .PARAMETER RetryCount The number of times to try connecting before giving up. .PARAMETER TimeoutMs The time in milliseconds to wait for WSMan to respond per attempt. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [string[]] $ComputerName, [Parameter(Mandatory=$false)] [long] $RetryDelaySec = 5, [Parameter(Mandatory=$false)] [int] $RetryCount = 0, [Parameter(Mandatory=$false)] [long] $TimeoutMs = 5 ) $wsMan = New-Object -ComObject "WSMan.Automation" $connectionOptions = $wsMan.CreateConnectionOptions() $sessionFlags = $wsMan.SessionFlagUseNoAuthentication() -bor $wsman.SessionFlagUTF8() $remainingComputers = New-Object System.Collections.ArrayList $remainingComputers.AddRange($ComputerName) | Out-Null $currentTry = 0 $totalTimeoutMs = ($RetryDelaySec * 1000 + $TimeoutMs * $ComputerName.LongLength) * $RetryCount $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { while (($currentTry -lt ($RetryCount + 1)) -and ($remainingComputers.Count -gt 0) -and ($stopwatch.ElapsedMilliseconds -le $totalTimeoutMs)) { $testComputers = New-Object System.Collections.ArrayList $testComputers.AddRange($remainingComputers) | Out-Null foreach ($testComputer in $testComputers) { try { $iWSManSession = $wsMan.CreateSession($testComputer, $sessionFlags, $connectionOptions); $iWSManSession.Timeout = $TimeoutMs $iWSManSession.Identify(0) | Out-Null $remainingComputers.Remove($testComputer) } catch [System.Exception] { Trace-Execution "WSMan is not operational on $testComputer. Attempt $($currentTry + 1) of $($RetryCount + 1)." if ( $currentTry -ge $RetryCount ) { Trace-Execution ($_ | Format-List * | Out-String) } } } $currentTry++ if ($remainingComputers.Count -gt 0) { Start-Sleep -Seconds $RetryDelaySec } } return $remainingComputers } finally { if ( $wsMan ) { $null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wsMan) $wsMan = $null } } } <# .Synopsis Stops a service with retry logic. This function first tries to stop a service gracefully and waits for it to exit a pending state with retry logic up to 60 seconds. It also support forcefully stopping it if enabled. #> function Stop-ServiceWithRetries { param ( [parameter(Mandatory = $true)] [ValidateNotNull()] [System.String] $ServiceName, [Switch] $Force ) $retryCount = 0 $ServiceNotStopped = $true while ($retryCount -lt 7 -and $ServiceNotStopped) { try { $service = get-service -Name $ServiceName -ErrorAction Ignore if ($service -eq $null) { Trace-Execution "Service not found." return } Trace-Execution "Found Service in $($service.Status)" switch ($service.Status) { { "Running", "Paused" -contains $_ } { Stop-Service -Name $ServiceName $ServiceNotStopped = $false break } "Stopped" { $ServiceNotStopped = $false break } default { start-sleep -Seconds 10 } } } catch { Trace-Warning "Failed to stop service : $($_.Exception.Message)" } finally { $retryCount++ } } $service = Get-Service -Name $ServiceName if ($service.Status -ne "Stopped" -and $Force) { Write-Log ("Failure stopping service '{0}'" -f $ServiceName) try { Write-Log("Attempting to stop underlying process of service '{0}'" -f $ServiceName) $process = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" | Select-Object if($process -ne $null) { Stop-Process -Id $process.ProcessId -Force $stoppedProcess = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" | Select-Object if($stoppedProcess.State -eq "Stopped") { Write-Log ("Process '{0}' with id '{1}' stopped" -f $process.Name,$process.ProcessId) $retries = 0 while($retries -lt 5) { try { # Tell SCM to stop the service again so that it doesn't restart after we kill the underlying process (if startup type is automatic) Stop-Service $ServiceName -Force break } catch { Write-Log("Failure trying to stop service '{0}' after stopping process '{1}'. Retrying..." -f $ServiceName,$process.Name) Start-Sleep -Seconds 3 $retries++ } } Stop-Service $ServiceName -Force $service = Get-Service -Name $ServiceName if($service.Status -ne "Stopped") { throw "Failed to stop service $ServiceName after process stop. Exiting..." } else { Write-Log("Process '{0}' and service '{1}' stopped successfully." -f $process.Name,$ServiceName) } } } else { Write-Log("No underlying process of service '{0}' found. Failured to stop service.") } } catch { Write-Log ("Failure stopping process '{0}'. Message: '{1}'" -f $process.Name,$_.Exception.Message) throw } } } # Helper function to create execution context XML for node-wise operation. # This would mainly be used in conditional action expansion for custom execution context. # Arbitrary custom XML can still be passed directly in the conditional evaluation function if needed. function New-ExecutionContextXmlForNode { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String[]] $NodeNames, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $RoleName, [Parameter(Mandatory = $false)] [System.String] $RolePath ) Trace-Execution "Constructing execution context XML with NodeName: '$NodeNames', RoleName: '$RoleName', RolePath: '$RolePath'" $nodesExecutionContext = @" <ExecutionContext> <Roles> <Role RoleName="$RoleName"> <Nodes /> </Role> </Roles> </ExecutionContext> "@ $ecXml = [xml]$nodesExecutionContext if ($RolePath) { # RolePath is not strictly required (also not easily constructed from the script) $ecXml.SelectSingleNode("ExecutionContext/Roles/Role").SetAttribute("RolePath", $RolePath) | Out-Null } foreach ($node in $NodeNames) { $nodeElement = $ecXml.CreateElement("Node") $nodeElement.SetAttribute("Name", $node) | Out-Null $ecXml.SelectSingleNode("ExecutionContext/Roles/Role/Nodes").AppendChild($nodeElement) | Out-Null } return $ecXml.OuterXml } <# .SYNOPSIS Imports certificate from pfx file if it's not installed. #> function Import-PfxCertificateSafe { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $FilePath, [Parameter(Mandatory=$true)] [SecureString] $Password, [Parameter(Mandatory=$false)] [string] $CertStoreLocation = "Cert:\LocalMachine\My", [Parameter(Mandatory=$false)] [Switch] $Exportable ) $ErrorActionPreference = "Stop" $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($FilePath, $Password) if (($existingCert = Get-ChildItem $CertStoreLocation | Where Thumbprint -EQ $certificate.Thumbprint )) { Trace-Execution "Certificate '$CertStoreLocation\$($certificate.Thumbprint)' already imported from location '$FilePath'" -Verbose $existingCert } else { if ($Exportable) { Import-PfxCertificate -FilePath $FilePath -CertStoreLocation $CertStoreLocation -Password $Password -Exportable -Verbose -ErrorAction Stop } else { Import-PfxCertificate -FilePath $FilePath -CertStoreLocation $CertStoreLocation -Password $Password -Verbose -ErrorAction Stop } if (($installedCertificate = Get-ChildItem $CertStoreLocation | Where Thumbprint -EQ $certificate.Thumbprint )) { Trace-Execution "Successfully installed Certificate '$CertStoreLocation\$($installedCertificate.Thumbprint)' from location '$FilePath'" -Verbose } else { throw "Import-PfxCertificate failed to install certificate from location '$FilePath'" } } } function Install-RoleNugetPackages { [CmdletBinding()] Param( [Parameter()] [ValidateNotNullOrEmpty()] [string] $NugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore", [Parameter()] [ValidateNotNullOrEmpty()] [string] $Destination = "$env:SystemDrive\NuGetStore" ) if(!(Test-Path $destination)) { md $destination -Force } #Install the ProductNugets manifest package Expand-NugetContent -NugetStorePath $NugetStorePath -NugetName 'Microsoft.AzureStack.Solution.Deploy.ProductNugets' -SourcePath '' -DestinationPath $destination -Verbose:$VerbosePreference -IsNugetInstall $productNugetXmlPath = Join-Path -Path (Get-ASArtifactPath -NugetName 'Microsoft.AzureStack.Solution.Deploy.ProductNugets') -ChildPath 'ProductNuGets.xml' if(!(Test-Path -path $productNugetXmlPath)) { #To support backward compatibility for ProductNugets package between Solution-Deploy package and AsZ-Assembly package. $productNugetXmlPath = Join-Path -Path (Get-ASArtifactPath -NugetName 'Microsoft.AzureStack.Solution.Deploy.ProductNugets') -ChildPath 'content\ProductNuGets.xml' } $productNugetXml = [xml](Get-Content -Path $productNugetXmlPath) $roleNugetPackageNames = ($productNugetXml.Manifest.Packages.NuGetPackage| where {$_.ECERole -eq 'true' -or $_.InstallInBoostrap -eq 'true'} ).Name foreach($roleNugetName in $roleNugetPackageNames) { Expand-NugetContent -NugetName $roleNugetName -SourcePath '' -DestinationPath $destination -NugetStorePath $NugetStorePath -Verbose:$VerbosePreference -IsNugetInstall } } function New-CallBackStringForAsZ { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) # If an end step is specified, propagate that information to the invocation after reboot. $endStepValue = $Parameters.RunInformation.EndStep if ( ($endStepValue) -and ($endStepValue -ne 'MAX') ) { $endStepSpecification = "-End $endStepValue " } $retriesValue = $Parameters.RunInformation.Retries if ( ($retriesValue) ) { $retriesValueSpecification = "-Retries $retriesValue " } $ecEngineModulePath = (Resolve-Path -Path "$PSScriptRoot\..\ECEngine\EnterpriseCloudEngine.psd1").Path $callbackString = @" Import-Module $ecEngineModulePath Invoke-EceAction -RolePath Cloud -ActionType CloudDeployment -Rerun $endStepSpecification $retriesValueSpecification -Verbose "@ return $callbackString } <# .SYNOPSIS TEMP helper function for investigating binary corruption in FE build #> function Test-BinaryHash { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $FileSystemRoot, [Parameter(Mandatory=$true)] [string] $OutputFileName, [Parameter(Mandatory=$true)] [string] $BaselineFileName, [Parameter(Mandatory=$false)] [switch] $ThrowIfCorrupted ) $FileSystemRoot = $FileSystemRoot.TrimEnd('\') Trace-Execution "Test-BinaryHash with FileSystemRoot: $FileSystemRoot, OutputFile: $OutputFileName, BaselineFile: $BaselineFileName, ThrowIfCorrupted: $ThrowIfCorrupted" # A pre-defined list of folders to scan $foldersToScan = @( @{ RelativePath = '\Windows\cluster'; Recursive = $true; Include = @('*.exe','*.dll') }, @{ RelativePath = '\Windows\System32\drivers\*'; Recursive = $false; Include = @('*.sys')}, @{ RelativePath = '\Windows\System32\wbem\*'; Recursive = $false; Include = @('*.dll')}, @{ RelativePath = '\Program Files\WindowsPowerShell\Modules'; Recursive = $true; Include = @('*.dll','*.ps*')} ) # Whether set baseline or test against baseline $setBaseline = $OutputFileName -eq $BaselineFileName # Read hashes from baseline file, skip test if baseline file not exists $baselineHashes = $null if (-not $setBaseline) { $baselineHashFile = Join-Path $FileSystemRoot $BaselineFileName if (Test-Path $baselineHashFile) { $baselineHashes = Get-Content -Path $baselineHashFile | ConvertFrom-Json } else { Trace-Execution "Baseline file not found: $baselineHashFile, skip testing." return } } $outPutHashFile = Join-Path $FileSystemRoot $OutputFileName $outPutHashes = @{} $errorMessages = '' $hashTested = 0 $hashFailed = 0 foreach ($folder in $foldersToScan) { $path = Join-Path $FileSystemRoot $folder.RelativePath $recursive = $folder.Recursive $include = $folder.Include Get-ChildItem -Path $path -File -Recurse:$recursive -Include $include | % { # Compute file hash $fileHash = $_ | Get-FileHash -Algorithm MD5 $key = $fileHash.Path.Substring($fileSystemRoot.Length) $value = $fileHash.Hash $outPutHashes[$key] = $value # Compare with baseline if ($baselineHashes -and $baselineHashes.$key) { if($baselineHashes.$key -ne $value) { $errorMessages += "`n$key file hash does not match the baseline." $hashFailed++ } $hashTested++ } } } # Save hashes to output file $outPutHashes | ConvertTo-Json | Out-File -FilePath $outPutHashFile Trace-Execution "Test-BinaryHash completed with HashOutputed: $($outPutHashes.Count), HashTested: $hashTested, HashFailed: $hashFailed" if ($errorMessages) { if ($ThrowIfCorrupted) { throw $errorMessages } else { Trace-Warning $errorMessages } } } <# .SYNOPSIS Wrapper for [Environment]::SetEnvironmentVariable to bypass ConstrainedLanguage issue #> function Set-Env { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $VariableName, [Parameter(Mandatory=$false)] [string] $VariableValue, [Parameter(Mandatory=$false)] [System.EnvironmentVariableTarget] $Target = [System.EnvironmentVariableTarget]::Machine ) [Environment]::SetEnvironmentVariable($variableName, $variableValue, $target) } function Update-ECEAgentSecrets { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [hashtable] $EceSecrets, [Parameter(Mandatory = $true)] [string] $EceAgentPathInPackage, [Parameter(Mandatory = $true)] [string] $EceAgentPackagePath, [Parameter(Mandatory = $true)] [string] $EceAgentCommonDllName, [Parameter(Mandatory = $true)] [string] $EceAgentExeName, [Switch] $Disable ) # Deprecated } <# .SYNOPSIS Wrapper for force flushing registry keys to disk. #> function Flush-RegistryKeysToDisk { $regFlusKeySignature = @" [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] public static extern int RegFlushKey(IntPtr hKey); "@ $ErrorActionPreference = "Stop" $advapi32 = Add-Type -MemberDefinition $regFlusKeySignature -Name "AdvApi32" -Namespace "RegistryFunctions" -PassThru $hklm = [Microsoft.Win32.Registry]::LocalMachine $classRoot = [Microsoft.Win32.Registry]::ClassesRoot $currentConfig = [Microsoft.Win32.Registry]::CurrentConfig $currentUser = [Microsoft.Win32.Registry]::CurrentUser Trace-Execution "Flushing LocalMachine, ClassesRoot, CurrentConfig and CurrentUser reg hives" if ($advapi32::RegFlushKey($hklm.Handle.DangerousGetHandle()) -ne 0) { Trace-Error "Unable to flush hklm hive" } if ($advapi32::RegFlushKey($classRoot.Handle.DangerousGetHandle()) -ne 0) { Trace-Error "Unable to flush classRoot hive" } if ($advapi32::RegFlushKey($currentConfig.Handle.DangerousGetHandle()) -ne 0) { Trace-Error "Unable to flush currentConfig hive" } if ($advapi32::RegFlushKey($currentUser.Handle.DangerousGetHandle()) -ne 0) { Trace-Error "Unable to flush currentUser hive" } Trace-Execution "Successfully flushed LocalMachine, ClassesRoot, CurrentConfig and CurrentUser reg hives" } <# .SYNOPSIS Helper function to get Deployment Launch type #> function Get-DeploymentLaunchype() { $ErrorActionPreference = "Stop" $deploymentLaunchType = "NonCloudDeployment" $stampInformationPath = "HKLM:\Software\Microsoft\AzureStackStampInformation" try { if (Test-Path $stampInformationPath) { $deploymentLaunchType = (Get-ItemProperty -Path $stampInformationPath -Name DeploymentLaunchType).DeploymentLaunchType } } catch { # if deployment launch type not set, return default Write-Verbose "Deployment Launch type not set: $( $_.Exception.Message )" } return $deploymentLaunchType } Export-ModuleMember -Function Install-RoleNugetPackages Export-ModuleMember -Function ConnectPSSession Export-ModuleMember -Function Expand-NugetContent Export-ModuleMember -Function Import-PfxCertificateSafe Export-ModuleMember -Function Initialize-ECESession Export-ModuleMember -Function Invoke-ECECommand Export-ModuleMember -Function Mount-WindowsImageWithRetry Export-ModuleMember -Function New-Credential Export-ModuleMember -Function New-ExecutionContextXmlForNode Export-ModuleMember -Function PublishAndStartDscConfiguration Export-ModuleMember -Function Stop-ServiceWithRetries Export-ModuleMember -Function Test-WSManConnection Export-ModuleMember -Function Trace-Error Export-ModuleMember -Function Trace-Execution Export-ModuleMember -Function Trace-Warning Export-ModuleMember -Function Wait-Result Export-ModuleMember -Function New-CallBackStringForAsZ Export-ModuleMember -Function Test-BinaryHash Export-ModuleMember -Function Set-Env Export-ModuleMember -Function Update-ECEAgentSecrets Export-ModuleMember -Function Flush-RegistryKeysToDisk Export-ModuleMember -Function Get-DeploymentLaunchype # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCvzzTx/mkYoxaf # cxEJC24hEw5HJV5sjmrtny0hH/N8YKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGCv5vl/UPtDmYqzjJfKLdUN # NZNSad2htPIa5lkVWYHhMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAfth0eadHsdCnPGVrJIIsjllkydhxku4cdwE4luFmf8cDqs5BTRmmP7mV # oz5bpWW4x4Pfss6Td83Dxidh8QqvPlW4gZ0a1AcoYsPqida64bZBHb0wRy9tjwju # CecjEekHO98dlvWO9VZhI1Bja4bbiyMIE1QQ9ZaxmuVDNOm31yPygTHndWJq+SQB # Rv3VzfCor+ja9rpSl0PDONheEbm2xqHdswveA0KcvAEOD8ElDixsVrrdPPpVDZMt # crAyAmhbNFjVwwQYf4LvTy5zTk/yF+NlicAPF4cyMw0Lo+d0QtavNCr5Wt2nGkZM # 12Ckzp/N05iEDvAcDxiMCWpe+BtloqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBimAmKmEE30SNB3DFy4xo4a3UU0WxJHx8Tx9oxcAceAgIGZpV/Zb73 # GBMyMDI0MDcyMzExMDMyMi4yNTNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAe+JP1ahWMyo2gABAAAB7zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # NDhaFw0yNTAzMDUxODQ1NDhaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCjC1jinwzgHwhOakZqy17oE4BIBKsm5kX4DUmCBWI0 # lFVpEiK5mZ2Kh59soL4ns52phFMQYGG5kypCipungwP9Nob4VGVE6aoMo5hZ9Nyt # XR5ZRgb9Z8NR6EmLKICRhD4sojPMg/RnGRTcdf7/TYvyM10jLjmLyKEegMHfvIwP # mM+AP7hzQLfExDdqCJ2u64Gd5XlnrFOku5U9jLOKk1y70c+Twt04/RLqruv1fGP8 # LmYmtHvrB4TcBsADXSmcFjh0VgQkX4zXFwqnIG8rgY+zDqJYQNZP8O1Yo4kSckHT # 43XC0oM40ye2+9l/rTYiDFM3nlZe2jhtOkGCO6GqiTp50xI9ITpJXi0vEek8AejT # 4PKMEO2bPxU63p63uZbjdN5L+lgIcCNMCNI0SIopS4gaVR4Sy/IoDv1vDWpe+I28 # /Ky8jWTeed0O3HxPJMZqX4QB3I6DnwZrHiKn6oE38tgBTCCAKvEoYOTg7r2lF0Iu # bt/3+VPvKtTCUbZPFOG8jZt9q6AFodlvQntiolYIYtqSrLyXAQIlXGhZ4gNcv4dv # 1YAilnbWA9CsnYh+OKEFr/4w4M69lI+yaoZ3L/t/UfXpT/+yc7hS/FolcmrGFJTB # YlS4nE1cuKblwZ/UOG26SLhDONWXGZDKMJKN53oOLSSk4ldR0HlsbT4heLlWlOEl # JQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFO1MWqKFwrCbtrw9P8A63bAVSJzLMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAYGZa3aCDudbk9EVdkP8xcQGZuIAIPRx9K # 1CA7uRzBt80fC0aWkuYYhQMvHHJRHUobSM4Uw3zN7fHEN8hhaBDb9NRaGnFWdtHx # mJ9eMz6Jpn6KiIyi9U5Og7QCTZMl17n2w4eddq5vtk4rRWOVvpiDBGJARKiXWB9u # 2ix0WH2EMFGHqjIhjWUXhPgR4C6NKFNXHvWvXecJ2WXrJnvvQGXAfNJGETJZGpR4 # 1nUN3ijfiCSjFDxamGPsy5iYu904Hv9uuSXYd5m0Jxf2WNJSXkPGlNhrO27pPxgT # 111myAR61S3S2hc572zN9yoJEObE98Vy5KEM3ZX53cLefN81F1C9p/cAKkE6u9V6 # ryyl/qSgxu1UqeOZCtG/iaHSKMoxM7Mq4SMFsPT/8ieOdwClYpcw0CjZe5KBx2xL # a4B1neFib8J8/gSosjMdF3nHiyHx1YedZDtxSSgegeJsi0fbUgdzsVMJYvqVw52W # qQNu0GRC79ZuVreUVKdCJmUMBHBpTp6VFopL0Jf4Srgg+zRD9iwbc9uZrn+89odp # InbznYrnPKHiO26qe1ekNwl/d7ro2ItP/lghz0DoD7kEGeikKJWHdto7eVJoJhkr # UcanTuUH08g+NYwG6S+PjBSB/NyNF6bHa/xR+ceAYhcjx0iBiv90Mn0JiGfnA2/h # Lj5evhTcAjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjk2MDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBL # cI81gxbea1Ex2mFbXx7ck+0g/6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6knghjAiGA8yMDI0MDcyMzA3NTU1 # MFoYDzIwMjQwNzI0MDc1NTUwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqSeCG # AgEAMAoCAQACAgwFAgH/MAcCAQACAhOWMAoCBQDqSzIGAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAFhPwM8rDkq7OI6pwC8R6rv54jrdRWWUoEYIIvQZoChV # oSzbDWAsrFJZMgA3jfDvVGEK26xrh/DOkMncwUxVTZNK+RNiigazC+txCkwhAQF0 # +5wOvUc+z/y57rJQi6M6VBM+tcLy8qZbnFCx6/RngwpF2ILBFmznsPeW+EZI9LJI # BcRAqASDMA02wjKe6lpGWO/F3EHjqx37C2LQWS8s8mbhIfz1s8IOTeOztFs1/XWW # 6lFUEfmunYnJ7UxwFt6RdqG4j0JvrNokWc0MUdOB9gaq7Uz5OHPUMFYKMP9LvOzw # 2QgTQSEC5ErLcsckSkFVATHgx3R6Tx/OaTsje+wnpm0xggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe+JP1ahWMyo2gABAAAB # 7zANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCAjXduGcQvBtN4U89d1RyBXTMa4U1xxmqoKlzIEjtQ3 # mjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIPBhKEW4Fo3wUz09NQx2a0Db # cdsX8jovM5LizHmnyX+jMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHviT9WoVjMqNoAAQAAAe8wIgQgEe6Hnhdm4VXtkSWn/V2u7CyT # zfipgVhgOMQ9+jEDrmowDQYJKoZIhvcNAQELBQAEggIANqb3Ftf7AJ/neijp0iKr # vB1PHmZubL4c50UL2Ld8yX0Gdy4Bl2kAdrXlChnb8w4I5hw0g37Pgigpoo+AMioS # fv1xpF934axsqocQzQyyyymq9Sd2NP9utsB6QPx8zBczkkhyPPY0VLyBUmmXbwYz # 7Xxa61ib1/2i5NDh+6SaXgYoML5oXnDYlhm9FL8AzZ1vkZCLDIUFZ+C2FCYFYNAJ # 09EAtDplReVnSTGCw6qnZMYbr29jUhRunroq6qvDUAw3PhSMJSf/KzAlVJN8gZ+g # QFGXQxCBP6eah4BjfCDtepWstv9/QgzALKvkK2H+teCcIF7tXjSQM//N5xT/A8Ev # CcTpYILK0MnzKiH5UGMTzDYKAHGAXvkyjQTZotm1JGn3s+XAPI+Tn9PcXtf22N21 # 2FWy414u+Lv/3t3D/bZ+To2gY38FAShKVT0dpTbwgIR9gxXTjYVfeZEqjW/ImKAq # iKRjewWCqaz+JJ08M/NIUFyxpeQ9wkK4puE9JTTyxPms3f5DfASAp2VsKZBxu+zi # y+rXJuMmPSFGUfA8fgNWz0+1NiVlnBDzFowgFaddixwqizOqFm9CLg5yb2uV+7wh # ei+1YvAYuum5H6dENNKDH/wEFohPdBKjWs/g5H7NQ5Ogs0ltAzgpWooVNGnPetwt # FdGbM788RfpCDfRVOq/BB7w= # SIG # End signature block |