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 Find-LockedFiles { param ( [Parameter(Mandatory=$true)] [string] $shareRelativePath ) Get-SmbOpenFile | Where-Object path -match $shareRelativePath } function Mount-Wim { param ( [Parameter(Mandatory=$true)] [string] $ImagePath, [Parameter(Mandatory=$true)] [UInt32] $ImageIndex ) $errorActionPreference = "Stop" Trace-Execution "Mount image '$ImagePath', image index $ImageIndex." $mountPath = [IO.Path]::GetTempFileName() Trace-Execution "Create temporary mount folder $mountPath." Remove-Item $mountPath -Recurse -Force $null = New-Item -ItemType Directory $mountPath -Force # At this point in Cloud:Build, there shouldn't be any other mounted images. # If there is anything already mounted, it's debris from a previous run. # Make a best attempt to clean up whatever is found (current or old) and # then try to move on. Failure to clean-up will not fail the run. $mountedImages = Get-WindowsImage -Mounted -Verbose:$false foreach ($mount in $mountedImages) { Trace-Execution "Attempt to discard previously mounted image '$($mount.ImagePath)', MountStatus = '$($mount.MountStatus)', Path = '$($mount.MountPath)'" try { Dismount-WindowsImage -Path $mount.MountPath -Discard -Verbose:$false } catch { Trace-Warning "Failed to dismount '$($mount.MountPath)' - continuing anyway. `r`nError: $_ " Trace-Execution "Attempting to clear possibly corrupt mount points." Clear-WindowsCorruptMountPoint -ErrorAction SilentlyContinue } } Trace-Execution "Mount $ImagePath to $mountPath." Mount-WindowsImageWithRetry -ImagePath $ImagePath -Path $mountPath -Index $ImageIndex return $mountPath } function Dismount-Wim { param ( [Parameter(Mandatory=$true)] [string] $MountPath, [switch] $Discard ) $ImagePath = Get-WindowsImage -Mounted -Verbose:$false | ? Path -eq $MountPath | % ImagePath if (-not $imagePath) { Trace-Error "Image '$ImagePath' is not mounted." } Trace-Execution "Dismount $ImagePath." if ($Discard) { $null = Dismount-WindowsImage -Path $MountPath -Discard -Verbose:$false } else { $null = Dismount-WindowsImage -Path $MountPath -Save -Verbose:$false } } 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 } # Creates the registry name value pair only if it does not exists function New-RegistryPropertyNameValue { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Path, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Value, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $ValueType ) $key = Get-Item -LiteralPath $Path -ErrorAction SilentlyContinue if (-not ($key -and ($null -ne $key.GetValue($Name, $null)))) { $null = New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $ValueType -Force Trace-Execution "Creating registry property key: $Path, Name: $Name with Value: $Value (Type: $ValueType) on $env:COMPUTERNAME." } else { $regValue = Get-ItemPropertyValue -Path $Path -Name $Name if ($regValue -ne $Value) { Set-ItemProperty -Path $Path -Name $Name -Value $Value Trace-Execution "Updating the registry key: $Path, Name: $Name from Value: $regValue to Value: $Value on $env:COMPUTERNAME." } } } function Test-WSmanForCredSSP { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Bool]$Connect, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [PSCredential] $Credential, [CloudEngine.Configurations.EceInterfaceParameters] $Parameters = $null ) $maxRetries = 5 $credentialParameter = @{} if ($PSBoundParameters.Credential) { $credentialParameter.Credential = $PSBoundParameters.Credential } foreach ($eachComputerName in $ComputerName) { # to ensure reliability, if the call fails, we'll do 3 retries # however, if we have the parameter passed down, we'll start specific tracing to # understand the root cause (parameter needed for PSDirect) $retry = 0 $tracingOnVM = $false do { try { Trace-Execution "Attempt WSMAN connect to $eachComputerName with tracing: $tracingOnVM" Invoke-Command -ComputerName $eachComputerName @credentialParameter -ScriptBlock { Set-Item "wsman:\localhost\Service\Auth\CredSSP" $using:Connect -Force } break } catch { Trace-Execution "Exception while trying to do initial WSMan test. $_" if ($Parameters) { if ($tracingOnVM -eq $true) { # stop any previous tracing $sbTrace = {netsh wfp capture stop} Trace-Execution "Stop Tracing wfp" Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace $sbTrace = {netsh trace stop} Trace-Execution "Stop Netsh Tracing" Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace $tracingOnVM = $false } # test if the target support internetclient(core server) or internetserver(full server) # the regkey below contains the server roles (only ServerCore for Core, and an array of entries for FullServer) $testServerVersion = {((gci -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server').Property -imatch "Gui").Count -gt 0} $supportInternetClient = Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $testServerVersion $scenarioToTrace = @{$true = 'InternetClient'; $false = 'InternetServer'}[$supportInternetClient] $enableDisable = @{$true = 'enableCredssp'; $false = 'disableCredssp'}[$Connect] $date = get-date $traceCommand1 = "netsh trace start scenario=$($scenarioToTrace) capture=yes report=disabled tracefile=c:\MASLogs\$($scenarioToTrace)_$($enableDisable)_$($date.year)_$($date.month)_$($date.day)_$($date.hour)_$($date.minute)_$($date.second).etl overwrite=yes" $traceCommand2 = "start-process -filepath c:\windows\system32\netsh.exe -argumentlist `"wfp capture start file=c:\MASLogs\WFPTrace_$($enableDisable)_$($date.year)_$($date.month)_$($date.day)_$($date.hour)_$($date.minute)_$($date.second).cab`"" Trace-Execution "Start Tracing $($scenarioToTrace) $($traceCommand1)" $sbTrace = ([scriptblock]::Create($traceCommand1)) Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace Trace-Execution "Start Tracing WFP: $($traceCommand2)" $sbTrace = ([scriptblock]::Create($traceCommand2)) Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace $tracingOnVM = $true } else { Trace-Execution "No PS Direct support, retrying..." Sleep 60 } $retry++ } } while ($retry -lt $maxRetries) #ensure no tracing is leaked if ($tracingOnVM -eq $true) { # stop any previous tracing $sbTrace = {netsh wfp capture stop} Trace-Execution "Stop Tracing wfp" Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace $sbTrace = {netsh trace stop} Trace-Execution "Stop Netsh Tracing" Invoke-PSDirectOnVM -Parameters $Parameters -VMName $eachComputerName -VMCredential $Credential -ScriptBlock $sbTrace $tracingOnVM = $false } if ($retry -ge $maxRetries) { Trace-Error "Cannot establish a WSMAN session to VM: $eachComputerName" } } } function Enable-CredSSP { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [PSCredential] $Credential, [CloudEngine.Configurations.EceInterfaceParameters] $Parameters = $null ) Trace-ECEScript "Enable CredSSP on $($ComputerName -join ', ')" { $credentialParameter = @{} if ($PSBoundParameters.Credential) { $credentialParameter.Credential = $PSBoundParameters.Credential } $displayName = $ComputerName #| % { $CloudBuilder.GetDisplayName($_) } Trace-Execution "Enabling server-side CredSSP on $($displayName -join ', ')." Test-WSmanForCredSSP -Connect $true -ComputerName $ComputerName -Credential $Credential -Parameters $Parameters Trace-Execution "Enabling client-side CredSSP on the local computer." $credsspRegistryKey = "HKLM:\Software\Policies\Microsoft\Windows\CredentialsDelegation" if((Get-Item $credsspRegistryKey -ErrorAction SilentlyContinue) -eq $null) { $null = New-Item -Path $credsspRegistryKey -Force Trace-Execution "Creating $credsspRegistryKey on $env:COMPUTERNAME." } New-RegistryPropertyNameValue -Path $credsspRegistryKey -Name "AllowFreshCredentials" -Value 1 -ValueType Dword New-RegistryPropertyNameValue -Path $credsspRegistryKey -Name "AllowFreshCredentialsWhenNTLMOnly" -Value 1 -ValueType Dword New-RegistryPropertyNameValue -Path $credsspRegistryKey -Name "ConcatenateDefaults_AllowFresh" -Value 1 -ValueType Dword New-RegistryPropertyNameValue -Path $credsspRegistryKey -Name "ConcatenateDefaults_AllowFreshNTLMOnly" -Value 1 -ValueType Dword if((Get-Item -Path "$credsspRegistryKey\AllowFreshCredentials" -ErrorAction SilentlyContinue) -eq $null) { $null = New-Item -Path "$credsspRegistryKey\AllowFreshCredentials" -Force } New-RegistryPropertyNameValue -Path "$credsspRegistryKey\AllowFreshCredentials" -Name 1 -Value wsman/* -ValueType String if((Get-Item -Path "$credsspRegistryKey\AllowFreshCredentialsWhenNTLMOnly" -ErrorAction SilentlyContinue) -eq $null) { $null = New-Item -Path "$credsspRegistryKey\AllowFreshCredentialsWhenNTLMOnly" -Force } New-RegistryPropertyNameValue -Path "$credsspRegistryKey\AllowFreshCredentialsWhenNTLMOnly" -Name 1 -Value wsman/* -ValueType String Set-Item WSMan:\localhost\Client\Auth\CredSSP $true -Force $waitCredSSPEnabled = Wait-Result -ValidationScript { Trace-Execution "Verify that CredSSP is enabled." Invoke-Command { $true } -ComputerName $ComputerName @credentialParameter } -TimeOut 60 -Interval 1 if (-not $waitCredSSPEnabled) { Trace-Error "CredSSP verification has failed on multiple retries." } } } function Disable-CredSSP { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [PSCredential] $Credential, [CloudEngine.Configurations.EceInterfaceParameters] $Parameters = $null ) $displayName = $ComputerName #| % { $CloudBuilder.GetDisplayName($_) } Trace-Execution "Disabling server-side CredSSP on $($displayName -join ', ')." Test-WSmanForCredSSP -Connect $false -ComputerName $ComputerName -Credential $Credential -Parameters $Parameters } function Disable-RemoteClientCredSSP { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [PSCredential] $Credential ) $credentialParameter = @{} if ($PSBoundParameters.Credential) { $credentialParameter.Credential = $PSBoundParameters.Credential } $displayName = $ComputerName Trace-Execution "Disabling client-side CredSSP on $($displayName -join ', ')." foreach ($eachComputerName in $ComputerName) { Invoke-Command -ComputerName $eachComputerName @credentialParameter -ScriptBlock { Set-Item "wsman:\localhost\Client\Auth\CredSSP" $false -Force } } } function Restart-Machine { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")] param ( [Parameter(Mandatory = $true, Position=0, ValueFromPipeline=$true, ParameterSetName = 'ComputerName')] [ValidateNotNullOrEmpty()] [string[]] $ComputerName, [PSCredential] $Credential, [UInt32] $MinutesToWait = 10 ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $VerbosePreference = [System.Management.Automation.ActionPreference]::SilentlyContinue $startTime = [DateTime]::Now $remoteStartTimes = Invoke-Command -ComputerName $ComputerName -Credential $Credential { [DateTime]::Now } $bootStartTimes = @{} foreach ($remoteStartTime in $remoteStartTimes) { $bootStartTimes.($remoteStartTime.PSComputerName) = $remoteStartTime } foreach ($computer in $ComputerName) { $displayName = $computer if ($Credential) { Trace-Execution "Restart $displayName using $($Credential.UserName) account." $cimSession = New-CimSession -ComputerName $computer -Credential $Credential } else { Trace-Execution "Restart $displayName." $cimSession = New-CimSession -ComputerName $computer } # Invoking Win32Shutdown method with two flags - Reboot and Force (2 + 4 = 6). $shutdownResult = Invoke-CimMethod -ClassName Win32_OperatingSystem -MethodName Win32Shutdown -Arguments @{Flags=[int32]6} -CimSession $cimSession -Verbose:$false if ($shutdownResult.ReturnValue) { Trace-Error "Win32Shutdown method has failed on $($shutdownResult.PSComputerName) with an error code $($shutdownResult.ReturnValue)." } } $endTime = $startTime.AddMinutes($MinutesToWait) $computersToFinishReboot = $ComputerName $computersThatFinishedReboot = @() while ([DateTime]::Now -lt $endTime) { foreach ($computer in $computersToFinishReboot) { $displayName = $computer $originalErrorCount = $global:error.Count $os = $null try { if ($Credential) { $cimSession = New-CimSession -ComputerName $computer -Credential $Credential -Verbose:$false } else { $cimSession = New-CimSession -ComputerName $computer -Verbose:$false } $os = Get-CimInstance -CimSession $cimSession -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue -Verbose:$false } catch {} $newErrorCount = $global:error.Count - $originalErrorCount if ($newErrorCount) { $global:error.RemoveRange(0, $newErrorCount) } if ($os.LastBootUpTime -gt $bootStartTimes.$computer) { $computersThatFinishedReboot += $computer Trace-Execution "Reboot complete on $displayName." } } $computersToFinishReboot = $computersToFinishReboot | ? {$_ -notin $computersThatFinishedReboot} if (-not $computersToFinishReboot) { break } Start-Sleep -Seconds 5 # 5 seconds between retries } if ($computersToFinishReboot) { $computersToFinishRebootDisplayNames = $computersToFinishReboot Trace-Error "Some computers have not rebooted during the expected time of $MinutesToWait minutes - $($computersToFinishRebootDisplayNames -join ', ')." } else { Trace-Execution "All computers have rebooted successfully." } } # # Helpers used to join the DVM to the domain. # $REG_KEY_PATH = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" # Enables auto-logon for a given user on the localhost. function Enable-AutoLogon { [CmdletBinding()] param ( [PSCredential] $Credential, [string] $DomainName ) Trace-Execution "Enabling auto-logon for user: '$($Credential.Username)'." Set-ItemProperty -Path $REG_KEY_PATH -Name DefaultDomainName -Value $DomainName -Force Set-ItemProperty -Path $REG_KEY_PATH -Name DefaultUsername -Value $Credential.UserName -Force Set-ItemProperty -Path $REG_KEY_PATH -Name DefaultPassword -Value ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password))) -Force Set-ItemProperty -Path $REG_KEY_PATH -Name AutoAdminLogon -Value 1 -Force Set-ItemProperty -Path $REG_KEY_PATH -Name ForceAutoLogon -Value 1 -Force Set-ItemProperty -Path $REG_KEY_PATH -Name AutoLogonCount -Value 1 -Force } # Resets the registered callback on the localhost. function Reset-RestartCallback { [CmdletBinding()] param ( [string] $TaskName, [ValidateSet('OnLogin','OnStartup')] [string] $ExecutionTime = 'OnLogin' ) Trace-Execution "Deleting $ExecutionTime scheduled task." $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue if ($task) { $task | Unregister-ScheduledTask -Confirm:$false } if ($ExecutionTime -eq 'OnLogin') { Trace-Execution "Disabling auto-logon." Remove-ItemProperty -Path $REG_KEY_PATH -Name DefaultDomainName Remove-ItemProperty -Path $REG_KEY_PATH -Name DefaultUsername Remove-ItemProperty -Path $REG_KEY_PATH -Name DefaultPassword Remove-ItemProperty -Path $REG_KEY_PATH -Name AutoAdminLogon Remove-ItemProperty -Path $REG_KEY_PATH -Name ForceAutoLogon } } # Sets a callback on machine restart on the localhost. function Set-RestartCallback { [CmdletBinding( DefaultParameterSetName='AutoLogonCredential' )] param ( [Parameter( ParameterSetName = 'AutoLogonCredential', Mandatory = $true )] [PSCredential] $AutoLogonCredential, [Parameter( ParameterSetName='GMSA', Mandatory = $true )] [string] $GMSA, [string] $DomainName, [Parameter(Mandatory = $true)] [string] $Callback, [Parameter(Mandatory = $true)] [string] $TaskName, [switch] $Persistent, [ValidateSet('OnLogin','OnStartup')] [string] $ExecutionTime = 'OnLogin', [Microsoft.Management.Infrastructure.CimInstance] $Principal ) $ErrorActionPreference = 'Stop' if ($AutoLogonCredential) { $username = $AutoLogonCredential.UserName # needed incase user name comes in as Domain\User or just User $userNameArray = [Array]$username.Split('\') $usernameOnly = $userNameArray[-1] } else { $usernameOnly = $GMSA } $callbackString = @" Import-Module $PSScriptRoot\..\CloudDeployment.psd1 Import-Module $PSCommandPath "@ if (-not $Persistent) { $callbackString += @" Reset-RestartCallback -TaskName $TaskName -ExecutionTime $ExecutionTime "@ } $callbackString += @" $Callback "@ $callArgs = "-ExecutionPolicy RemoteSigned -NoExit -Command $callbackString" Trace-Execution "Registering the callback for powershell.exe with argument: '$callArgs'." $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $callArgs $registrationParams = @{ TaskName = $TaskName TaskPath = '\' Action = $action Settings = New-ScheduledTaskSettingsSet -Priority 4 Force = $true } if ($ExecutionTime -eq 'OnLogin') { $registrationParams.Trigger = New-ScheduledTaskTrigger -AtLogOn if ($Principal) { $registrationParams.Principal = $Principal } else { $registrationParams.User = "$usernameOnly" $registrationParams.RunLevel = 'Highest' } Enable-AutoLogon -Credential $AutoLogonCredential -DomainName $DomainName } else { $registrationParams.Trigger = New-ScheduledTaskTrigger -AtStartup if ($AutoLogonCredential) { if ($Principal) { $registrationParams.Principal = $Principal } else { $registrationParams.User = $AutoLogonCredential.Username $registrationParams.Password = $AutoLogonCredential.GetNetworkCredential().Password $registrationParams.RunLevel = 'Highest' } } else { $registrationParams.Principal = New-ScheduledTaskPrincipal -UserID $GMSA -LogonType Password -RunLevel Highest } } Trace-Execution "Registering the scheduled task named '$TaskName' under the user '$usernameOnly'." Register-ScheduledTask @registrationParams } function Initialize-ECESession { <# .SYNOPSIS Prepare Infra vm PS Session. .DESCRIPTION Loads all helper functions into a PSSession so that infra vm common functions can be readily used. .EXAMPLE Initialize-ECESession -Session $psSession .PARAMETER Session A PS Session to bootstrap with infra vm helper functions. .PARAMETER RoleId The role id to be used for a transcript recording the operations in this session on the remote machine. .PARAMETER PreLoadNugetName The name of a nuget to preload a script from .PARAMETER PreLoadScript The path of a script to preload into this session. .PARAMETER SuppressVerbose Prevent the redirection of verbose output from the remote session into the current runspace. #> [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 Initialize-NugetScript { <# .SYNOPSIS Dot source a script from a nuget into a ps session. .DESCRIPTION Finds a script from a nuget and loads it into the powershell session so cmdlets from the script can be used. .EXAMPLE Initialize-NugetScript -Session $psSession -PreLoadNugetName TestNuget -PreLoadScript "content\Scripts\Helpers.ps1" .PARAMETER Session A PS Session to bootstrap with infra vm helper functions. .PARAMETER PreLoadNugetName The name of a nuget to preload a script from .PARAMETER PreLoadScript The name of a script to preload into this session. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0)] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $PreLoadNugetName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $PreLoadScript ) PROCESS { $ComputerName = $Session.ComputerName Trace-Execution "Initializing remote powershell session on $ComputerName with custom nuget script $PreLoadScript from nuget $PreLoadNugetName." $ScriptBlock = { $nugetPath = Get-ASArtifactPath -NugetName $using:PreLoadNugetName $preLoadScriptPath = Join-Path $nugetPath $using:PreLoadScript Write-Verbose "Loading pre load script $preLoadScriptPath" . $preLoadScriptPath } Invoke-Command -Session $Session -ScriptBlock $ScriptBlock } } 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:systemdrive\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 Get-NugetVersions { <# .SYNOPSIS Gets all versions of Nuget packages that exist in a specified source location. .EXAMPLE Get-NugetVersions -NugetName "MyNuget" -SourcePath "content" .PARAMETER NugetName The name of the nuget to filter by. .PARAMETER MostRecent Whether to report the most recent version only .PARAMETER NugetStorePath The path from which the nuget packages should be derived. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string[]] $NugetName, [Parameter(Mandatory=$false)] [switch] $MostRecent, [Parameter()] [ValidateNotNullOrEmpty()] [string] $NugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore" ) PROCESS { $ErrorActionPreference = "stop" Trace-Execution "Finding nuget package $NugetName from store $NugetStorePath" $nugetPackageList = Find-Package -Source $NugetStorePath -Name $NugetName -ProviderName "nuget" -AllVersions:$(-not $MostRecent) -Verbose -Force if ($nugetPackageList) { $versionArray = $nugetPackageList.Version } else { $versionArray = @() } return $versionArray } } 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, [Parameter()] [PSCredential] $Credential = $null ) 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) } # if($Credential) # { # Trace-Execution "Creating PS drive 'NugetTempPSDrive' with root $DestinationPath and user $($Credential.UserName)." # New-PSDrive -Name NugetTempPSDrive -PSProvider FileSystem -Root $DestinationPath -Credential $Credential -ErrorAction Stop # } 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 Get-NugetStorePath { <# .SYNOPSIS Gets the current Nuget Store. .DESCRIPTION Gets the current Nuget Store which is on the managment share if it has been built or on the DVM directly if its early in deployment. .PARAMETER Parameters This object is based on the customer configuration. It contains the private information of the Key Vault role, as well as public information of all other roles. It is passed down by the deployment engine. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) Trace-ECEScript "Getting the current Nuget Store" { $virtualMachinesRole = $Parameters.Roles["VirtualMachines"].PublicConfiguration $clusterName = Get-ManagementClusterName $Parameters $nugetStorePath = Get-SharePath $Parameters $virtualMachinesRole.PublicInfo.LibraryShareNugetStoreFolder.Path $clusterName # prefer the library store but if it hasn't been created yet use the local store. if(-not (Test-Path $nugetStorePath)) { $nugetStorePath = "$env:SystemDrive\CloudDeployment\NuGetStore" } } return $nugetStorePath } 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 } } } function Test-PSSessionConnection { <# .SYNOPSIS Test remote PS connectivity to one or more VMs and returns the names of any VMs for which the check failed. .DESCRIPTION Test remote PS connectivity to the VM. Use a PSJob to allow us to set a timeout on the operation as it can hang indefinitely if the VM is in a hung state. .EXAMPLE Test-PSSessionConnection -ComputerName $ComputerName -TimeoutMinutes 10 .PARAMETER ComputerName The name of the computer(s) on which to test PSSession connectivity .PARAMETER TimeoutMinutes The time in minutes to wait for the PSJob to finish before timing out. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [string[]] $ComputerName, [Parameter(Mandatory=$false)] [int] $TimeoutMinutes = 3, [Parameter(Mandatory=$false)] [int] $Retries = 5 ) $ErrorActionPreference = "Stop" # Invoke-Command tests authentication and process creation. [system.collections.arraylist]$computerNameList = $computerName $attempts = 0 while ($attempts -lt $Retries) { $attempts++ Trace-Execution "Attempt $attempts/$Retries : Testing PSSession connectivity on $computerNameList." $successfulConnections = @() foreach ($name in $computerNameList) { Trace-Execution "Testing remote PS connectivity to $name with a timeout of $TimeoutMinutes minutes." try { $session = New-AzsSession -ComputerName $name -ErrorAction Stop $job = Invoke-Command -Session $session -ScriptBlock { $true } -ErrorAction Stop -AsJob $job | Wait-Job -Timeout ($TimeoutMinutes * 60) | Out-Null $jobDuration = [System.Math]::Ceiling(($job.PSEndTime - $job.PSBeginTime).TotalSeconds) if ($job.State -eq "Completed") { $output = $job | Receive-Job if ($output) { Trace-Execution "Remote PS connection to $name succeeded in $jobDuration seconds." $successfulConnections += $name } else { Trace-Execution "Remote PS job to $name succeeded, but produced no output, so the job will be retried. Expected output: '$true'." } } elseif ($job.State -eq "Failed") { $job | Receive-Job -ErrorAction SilentlyContinue -ErrorVariable "exceptionDetails" Trace-Execution "Remote PS connection to $name failed in $jobDuration seconds. Exception: $exceptionDetails" } else { Trace-Execution "Remote PS connection to $name timed out after $TimeoutMinutes minutes." } } catch { Trace-Execution "Failure to create session or invoke a command to $name. Exception: $_" } finally { $session | Remove-PSSession -ErrorAction "Ignore" } } $successfulConnections | % { $computerNameList.Remove($_) } if ($computerNameList.Count -eq 0) { Trace-Execution "All VMs have PSSession connectivity." return $computerNameList } Start-Sleep -Seconds 30 } Trace-Execution "Some VMs still do not have PSSession connectivity. These are: $computerNameList" return $computerNameList } function Test-HostPhysicalDiskSize([string[]] $PhysicalHostNames, [UInt64] $MinimumDiskSize) { <# .SYNOPSIS Determine if physical hosts have boot disks as large or better than the minimum specified. .DESCRIPTION Certain configurations require sufficient physical disk space to be enabled. All drives are expected to be roughly equivalent, but all are checked anyway. .PARAMETER physicalHostNames The name of the computer(s) to check. Caller is expected to pass in only available nodes. .PARAMETER MinimumDiskSize Minimum disk size in bytes for test to evaluate true. #> [UInt64[]] $sizes = Invoke-Command -ComputerName $physicalHostNames ` { $disk = Get-Disk | Where-Object { $_.BootFromDisk } return $disk.Size } # Compare all the returned sizes from hosts, and if any are smaller than minimum the test will fail. if ($sizes -lt $minimumDiskSize) { Trace-Warning "One or more physical host disks is below $($minimumDiskSize/1GB) GiB." return $false } else { return $true } } function Get-VMWSManDiagnostics { <# .SYNOPSIS Capture diagnostic information after a remote command failure .DESCRIPTION If a caller is unable to reach a VM, this function will capture diagnostics information such as cluster node availablity, VM status and network connectivity from the caller to the VM .PARAMETER VMName The name of the computer to check. This is expected to be a single computer name from within the domain as a string. .PARAMETER HostClusterName The name of the cluster hosting the VM specified #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $VMName, [Parameter(Mandatory=$true)] [string] $HostClusterName ) # Cluster cmdlets and net cmdlets throw a nonterminating error, so we must still explicitly call ErrorAction on each command # This preference captures global powershell failures $ErrorActionPreference = "Stop" # If we fail to retrieve the cluster information, it should not fail ECE try { Trace-Execution "Cluster information for cluster '$HostClusterName' is:" $cluster = Get-Cluster $HostClusterName -ErrorAction Stop Trace-Execution "$($cluster | Format-Table | Out-String)" Trace-Execution "Cluster Node information is:" $clusterNodes = $cluster | Get-ClusterNode -ErrorAction Stop Trace-Execution "$($clusterNodes | Format-Table | Out-String)" Trace-Execution "Cluster Group information is:" $clusterGroups = $cluster | Get-ClusterGroup -ErrorAction Stop Trace-Execution "$($clusterGroups | Format-Table | Out-String)" Trace-Execution "Cluster Resource information is:" $clusterResources = $cluster | Get-ClusterResource -ErrorAction Stop Trace-Execution "$($clusterResources | Format-Table | Out-String)" Trace-Execution "Hyper-V VM information is:" $hypervVM = Get-VM -ComputerName $clusterNodes -ErrorAction Stop Trace-Execution "$($hypervVM | Format-Table | Out-String)" } catch { Trace-Warning "An error occurred while retrieving HostCluster information: $_" } # Likewise, failing to test remote connection should not fail ECE # Note that these commands rely on open ports in the destination VM's firewall (ex, ICMP) that may be disabled in the future # These cmdlets raise them as nonterminating errors, so they will not break execution of the try block try { Trace-Execution "Attempting a ping to $VMName" $pingResult = Test-Connection $VMName -ErrorAction Stop Trace-Execution "$($pingResult | Format-Table | Out-String)" Trace-Execution "Tracing route to $VMName" $traceRouteResult = tracert $VMName -ErrorAction Stop Trace-Execution "$($traceRouteResult | Format-Table | Out-String)" Trace-Execution "Attempting a NetConnection via WinRM" $winRMTestResults = Test-NetConnection -CommonTCPPort WinRM -ComputerName $VMName -ErrorAction Stop Trace-Execution "$($winRMTestResults | Format-Table | Out-String)" } catch { Trace-Warning "An error occurred while testing remote connectivity to '$VMName': $_" } } function Set-DriveLetterForSpecificLabel { <# .SYNOPSIS Sets the specified drive letter for the partition with given file system label. .DESCRIPTION While attaching a VHD to an offline VM, it is not possible to set the default drive letter. This cmdlet could be called from the VM after it comes online to set the drive letter of a partition using the file system label. .PARAMETER FileSystemLabel The file system label for which the drive letter needs to be set. .PARAMETER DriveLetter The new drive letter to be set for the FileSystemLabel. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $FileSystemLabel, [Parameter(Mandatory=$true)] [string] $DriveLetter ) # List of existing volumes $volumes = Get-Volume Trace-Execution "List of existing volumes: $($volumes | out-string)" # If file system label volume does not exist. $volume = $volumes | Where-Object FileSystemLabel -ieq $FileSystemLabel if([string]::IsNullOrEmpty($volume)) { Trace-Warning "A volume with file system label '$FileSystemLabel' does not exist." return } # If file system label partition does not exist. $partition = $volume | Get-Partition Trace-Execution "Partition with '$FileSystemLabel' file system label: $($partition | out-string)" if([string]::IsNullOrEmpty($partition.DriveLetter)) { Trace-Warning "A partition for file system label '$FileSystemLabel' does not exist." return } # If drive letter is already in use. if (Test-Path -Path "$($DriveLetter + ':')") { Trace-Warning "Drive letter '$DriveLetter' is already in use. Cannot reuse it without removing existing drive." return } # Set new drive letter for partiion with given file system label Trace-Execution "The current partition for file system label '$FileSystemLabel' exists at drive letter '$($partition.DriveLetter)'." Trace-Execution "Setting the partition with file system label '$FileSystemLabel' to drive letter '$DriveLetter'." Set-Partition -DriveLetter $partition.DriveLetter -NewDriveLetter $DriveLetter -Verbose -ErrorAction Stop } <# .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 -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'} ).Name foreach($roleNugetName in $roleNugetPackageNames) { Expand-NugetContent -NugetName $roleNugetName -SourcePath '' -DestinationPath $destination -NugetStorePath $NugetStorePath -Verbose:$VerbosePreference -IsNugetInstall } } function Create-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 } Export-ModuleMember -Function Install-RoleNugetPackages Export-ModuleMember -Function ConnectPSSession Export-ModuleMember -Function Disable-CredSSP Export-ModuleMember -Function Disable-RemoteClientCredSSP Export-ModuleMember -Function Dismount-Wim Export-ModuleMember -Function Enable-AutoLogon Export-ModuleMember -Function Enable-CredSSP Export-ModuleMember -Function Expand-NugetContent Export-ModuleMember -Function Find-LockedFiles Export-ModuleMember -Function Get-NugetStorePath Export-ModuleMember -Function Get-NugetVersions Export-ModuleMember -Function Get-VMWSManDiagnostics Export-ModuleMember -Function Import-PfxCertificateSafe Export-ModuleMember -Function Initialize-ECESession Export-ModuleMember -Function Initialize-NugetScript Export-ModuleMember -Function Invoke-ECECommand Export-ModuleMember -Function Mount-Wim Export-ModuleMember -Function Mount-WindowsImageWithRetry Export-ModuleMember -Function New-Credential Export-ModuleMember -Function New-ExecutionContextXmlForNode Export-ModuleMember -Function New-RegistryPropertyNameValue Export-ModuleMember -Function PublishAndStartDscConfiguration Export-ModuleMember -Function Reset-RestartCallback Export-ModuleMember -Function Restart-Machine Export-ModuleMember -Function Set-DriveLetterForSpecificLabel Export-ModuleMember -Function Set-RestartCallback Export-ModuleMember -Function Stop-ServiceWithRetries Export-ModuleMember -Function Test-HostPhysicalDiskSize Export-ModuleMember -Function Test-PSSessionConnection Export-ModuleMember -Function Test-WSManConnection Export-ModuleMember -Function Test-WSmanForCredSSP Export-ModuleMember -Function Trace-Error Export-ModuleMember -Function Trace-Execution Export-ModuleMember -Function Trace-Warning Export-ModuleMember -Function Wait-Result Export-ModuleMember -Function Create-CallBackStringForAsZ # SIG # Begin signature block # MIInlgYJKoZIhvcNAQcCoIInhzCCJ4MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCRtsVtqKuc2njA # 0zLbxaJynKke9PnufwlsSJBLRulHQqCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINSdtsGBTWPj9IDwKIfyi1PT # vwGzA8XO6mzpj5hJzkVZMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAjyg0aB1R6YdQzCm6ya4/8yyV/y6fZAjw3Q8HY4droyO+Qtz55dHbL2mm # aQYrbgjsdiPVySlJWlw/CtrXo2IQWmpp8uo5eHVjF4fGZZA7TN2Yl5prsJMfww2a # Bed8ZWhIOnN+64c+0HYrAj/+ErYxTxfvmUbPN8oQtG9p0ew5rduJzJ5HjCu/OMn7 # vZyasT9DCGvRV9P3CkQfOWdWuLkpppO2g0pbcpe66xYzDppyk5b2iRVeELlLw7tX # 1Vy9uHpyywpHNYqTMZo8r1AqkS2BkgmQCSRM5VOMNrLHMSm5Hho33Po3qqMnAWrM # TIW85tjLlq3SJVHz0njyr/IVvobSnqGCFwAwghb8BgorBgEEAYI3AwMBMYIW7DCC # FugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFRBgsq # hkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCC7am9nADrOznFXvVTa1XrmQ1neP0a8et9kseOWbB00uQIGZMl+wlhZ # GBMyMDIzMDgwMzA4MjA0NC45ODFaMASAAgH0oIHQpIHNMIHKMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpBRTJDLUUz # MkItMUFGQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC # EVcwggcMMIIE9KADAgECAhMzAAABv99uuQQVUihYAAEAAAG/MA0GCSqGSIb3DQEB # CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIyMTEwNDE5MDEy # NFoXDTI0MDIwMjE5MDEyNFowgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx # JjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkFFMkMtRTMyQi0xQUZDMSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAuExh0n1UxKMzBvkPHer47nryD4UK2GVy1X6bOVC+hLVh # DlsIWQ1uX/9a8IRI3zXo/y1oTDuj+rJHyX4OZQn42E0iu7x6swPvM34zIOSPn8lg # nWzGEAsRtz9zBrLW9+4w/YhWlXI8hvc7ovqupuL3TXte8BbmNOUDSL+Ou2bBfObG # zsH3yY/BELvqwO13KZ9Z1OxKacnqq1u9E9Rhai90STog22lR2MVRSx55FHi/emnZ # A/IKvsAtEH2K6JmgOyQ7/mDQrWNEA5roUjhQqLQw1/3wz/CIvc9+FPxX2dxR0nvv # Ye5VLqv8Q99cOkO6z6V4stGDyFDuO8CwtiSvCC3QrOOugAl33aPD9YZswywWRk+Y # GyLI+Fw+kCCUY6h1qOjTj5glz0esmds3ue45WaI2hI9usForM8gy//5tDZXj0KKU # 1BxA04xpfEy91RZUbc6pdAvEkpYrN2jlpXhMvTD7pgdYyxkVSaWZv7kWp5y9NjWP # /CTDGXTC6DWiGcXwPQO66QdVNWxuiGdpfPaEUnWXcKnDVua1khBAxO4m9wg/1qM6 # f7HwXf/pHifMej+qB7SUZOiJScX+1HmffmZRAFiJXS0qUDk0ZAZW3oX2xLyl0044 # eHI7Y95GPaw8OlSTeNiNAKl+MyH5OaifsUuyVHOf4rsrE+ZyAuS9e9ERqu5H/10C # AwEAAaOCATYwggEyMB0GA1UdDgQWBBRVAolUT3eV3wK/+Luf/wawCPMYpzAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IC # AQAjCREvjT6yXwJYdvkFUqTGGh6RizAY+ciuB6UOBUm0yqq5QC+5pCEa9WSMvbUG # zxDCEFBgD93gWGnkiyYcHCazlgZK+E7WxtI3bP++Fb4RJZiWLo/IC9hX12hCZZwY # XIGVzC9BVAcNx/zsFqI/9u8u/bhGjDHPad47C4OQNCHrkNqzGYxb4GQq6Psw6o7c # Ety3MU3Jd4uzBazaFhPRvmBfSn+Ufd6pTNZLgIX9BjrLmZblc/d2LIAurEr5W29W # fW5RMRIEZzO9TaMr/zzdmW/cV6VdaDTygy5g4O3UXadt1DraUpn5jcD10TVWNnyz # /paeleHojrGCCksqexpelMkUsiYP0HX9pFUgNglWU10r1wEzFwZM9aX2Rqq3fFRr # N3gu8tCX+H1nKK2AobW1vmsKLTH6PyX1LkyvRwTj45a1paeHIR8TGzm3+iY7wpC1 # MHuzqAqAdDeaIVdVlch807VJJ4hDive6AiOQCV9MwiUyhf5v4P8jTGof8CqjDb3P # nLlNSnFm2BFhMZ35oNTEosc37GZHScM83hTN1E481sLYJrrhhcdtcyNB60juMjqG # UD6uQ/7DbMvtv93tFj5WjxVhMCkkY66EEYgpfFLOCb2ngJJWFuJCIGsCiDfDxGwE # 4RVYAnoFzoa2OfSqijYg2drdZfpptRRvKxMsAzu3oxkS/TCCB3EwggVZoAMCAQIC # EzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoX # DTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC # 0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VG # Iwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP # 2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/P # XfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361 # VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwB # Sru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9 # X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269e # wvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDw # wvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr # 9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+e # FnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAj # BgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+n # FV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEw # PwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9j # cy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3 # FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAf # BgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNS # b29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl # ckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4Swf # ZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTC # j/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu # 2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/ # GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3D # YXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbO # xnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqO # Cb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I # 6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0 # zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaM # mdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNT # TY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIBATCB+KGB0KSBzTCByjEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWlj # cm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBF # U046QUUyQy1FMzJCLTFBRkMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVADgEd+JNrp4dpvFKMZi91txbfic3oIGD # MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF # BQACBQDodU62MCIYDzIwMjMwODAzMDU1MjU0WhgPMjAyMzA4MDQwNTUyNTRaMHcw # PQYKKwYBBAGEWQoEATEvMC0wCgIFAOh1TrYCAQAwCgIBAAICCw0CAf8wBwIBAAIC # EhwwCgIFAOh2oDYCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK # MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAlgSPrXAwb # DHwKImAnyK1n6ONHJS+1YrBXgzSFlj6xf6J4/RUxkK78tJVepluq07slTwMypXGM # JBSC/7SQkZPCcdrvNeSHrYFPpuD4DovHH0xBQ/6hr299XQy1/OrfBwAuAm1eVT73 # 0qXiYR2Y81XpIvbL0pmtElR8iJy8c6e5xDGCBA0wggQJAgEBMIGTMHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABv99uuQQVUihYAAEAAAG/MA0GCWCG # SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI # hvcNAQkEMSIEIJz2PupRvpCC2muXwh3Ewp+fR6kESIxLsT6tlhaDWijZMIH6Bgsq # hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQg/Q4tRz63EiRj4K+19yNUwogBIOsp44CI # uBfnZHCvBa4wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT # MwAAAb/fbrkEFVIoWAABAAABvzAiBCBpSVdmsfmVWBB+AYsk2RK6VyKfK87V+I1U # rbAAylRoHDANBgkqhkiG9w0BAQsFAASCAgAggTMm9Lje7NhUNgQtPcztlKe9Ozq/ # 4HfObQANhamEyq6WQLw9iRK3LJyrFIZl8FFTkQp35mVtLYKN5CdISlfCSuaiyMpA # v60GDveEa0rDQXKStffa5F6lDPeiE7huk37uTjZEYhgsY6LulA/hdAHcKNq+0Xlv # GIu2YVeFP3xQ8y4DLwFl2sSDjqXRuIYtCapst2EyCnF+t/V/Dn/tx3WC10YrlNGJ # 3niSYh8Z2xaC21FbAa6xrO5KxqOCqfkcadW9npxdKr8iXIX48FxIHEwMvXvuIpz8 # Hnt+s/rWaLy16vuij4m0eiQy//5wF50Ndm9qxHOJL7Dx9HaeYz0QIXwMcwoeKDw3 # aw64lVAeH7Hx+VziqVMp+PvB43lT+Ot4SsLP9o7px6q9RP4dGdJleahAaXOqraHv # Ml4/DlYJj1pI+eB9eVhIZStk/7GoGYa6rUMhx9LIubGr1HyKB7gM6tTbJ78e7LA2 # uqkcI0+ISjyW3BpHnQgGpXH0/b21fAB0hWjTsxWl+qpzNmNHyrjBkvKa9TBSRbyq # RQVmdBqoo8uF2voT/ZWCBmiTVOfICT0BSFrdpEumD8bQzTOoK3w4tf1YJEV89BLV # zTc5dElW7bOtjgqDZ181NG8TAINcks4xBmI5FJzvE8v1qFcLHTQfG0q7DjhE0foZ # 5p14qZtOUlPCdw== # SIG # End signature block |