Obs/bin/ObsDep/content/Powershell/Roles/Common/VhdWim.psm1
using module ..\Common\MountedImage.psm1 Import-Module -Name "$PSScriptRoot\..\..\Common\Helpers.psm1" Import-Module -Name "$PSScriptRoot\..\..\Common\NetworkHelpers.psm1" Import-Module -Name "$PSScriptRoot\..\..\Common\StorageHelpers.psm1" Import-Module -Name "$PSScriptRoot\RoleHelpers.psm1" $azureStackNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Role.SBE" Import-Module (Join-Path $azureStackNugetPath "content\CloudDeployment\Roles\OEM\OEM.psm1") -DisableNameChecking function Get-MountedDisk { Get-Disk | Where-Object Manufacturer -like 'Msft*' | Where-Object Model -like 'Virtual Disk*' } function Dismount-MountedDisk { Get-MountedDisk | ForEach-Object { Dismount-DiskImage -DevicePath $_.Path } } function Get-MountedDiskDriveLetter { Get-MountedDisk | Get-Partition | ForEach-Object DriveLetter } function New-VhdFromWim { param ( [Parameter(Mandatory=$true)] [string] $VhdPath, [Parameter(Mandatory=$true)] [string] $WimPath, [Parameter(Mandatory=$true)] [int] $WimImageIndex, [uint64] $SizeBytes = 40GB, [string[]] $DismFeaturesToInstall, [string[]] $ServerFeaturesToInstall, # Specifies if the VHD will be used for VHD boot rather than a primary boot device. # Do not set this if your VHD is going to be a primary boot disk on a VM. [switch] $VhdBoot, [switch] $Force ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop if (Test-Path $VhdPath) { # the other BVT disk (multi-node) is also a virtual disk but can not be dis-mounted (error will occur) # we have to target the only disk mapped to Vhd path Dismount-DiskImage -ImagePath $VhdPath if ($Force) { Remove-Item $VhdPath -Force } else { throw "VHD file $VhdPath already exists." } } # Try to start Hyper-V on the DVM $tryCount = 1 while (($tryCount -lt 4) -and ((Get-Service vmms).Status -ne "Running")) { Trace-Execution "Attempt #$tryCount to start VMMS on '$Env:COMPUTERNAME'" Start-Service vmms Start-Sleep 30 $tryCount++ } if ((Get-Service vmms).Status -ne "Running") { Trace-Error "Failed to start VMMS on $Env:COMPUTERNAME" } $null = New-VHD -SizeBytes $SizeBytes -Path $VhdPath -Dynamic $hwDetectionService = Get-Service -Name ShellHWDetection -ErrorAction Ignore if ($hwDetectionService) { Trace-Execution "Stopping ShellHWDetection Service" Stop-Service ShellHWDetection } else { Trace-Execution "ShellHWDetection Service was not running, no need to stop the service." } Trace-Execution "Mounting VHD from: $VhdPath" Mount-DiskImage -ImagePath $VhdPath # Attempt to start defender if not already running $service = Get-Service -Name "windefend" -ErrorAction SilentlyContinue if ($null -ne $service -and $service.Status -ine "Running") { $service | Start-Service -ErrorAction SilentlyContinue | Out-Null } try { $virtualDisk = Get-Disk | Where-Object Manufacturer -like 'Msft*' | Where-Object Model -like 'Virtual Disk*' | Where-Object PartitionStyle -like 'RAW' $diskNumber = $virtualDisk.Number Trace-Execution "Performing initialize on disk number: $diskNumber" Initialize-Disk -Number $diskNumber -PartitionStyle MBR $partition = New-Partition -DiskNumber $diskNumber -UseMaximumSize -AssignDriveLetter -IsActive if ($null -eq $partition) { throw "Failed to create new partition during the process of creating WinPE VHD from WIM." } $driveLetter = $partition.DriveLetter $driveName = $driveLetter + ':' $diskPath = $driveName + '\' $volume = Format-Volume -Partition $partition -FileSystem NTFS -Confirm:$false $null = New-PSDrive -PSProvider FileSystem -Root "FileSystem::$diskPath" -Name $driveLetter # set exclusion path when windefend is running if ($null -ne $service -and $service.Status -eq "Running") { Trace-Execution "Exclude mount folders $diskPath from Windows Defender scan to speed up configuration." Add-MpPreference -ExclusionPath $diskPath -ErrorAction SilentlyContinue } # Use a randomly generated log path because Expand-WindowsImage sometimes crashes because of not being able to write to default log file DISM.LOG. $dismLogPath = [IO.Path]::GetTempFileName() Trace-Execution "Using the following path for the DISM log: $dismLogPath" $null = Expand-WindowsImage -ImagePath $WimPath -ApplyPath $diskPath -Index $WimImageIndex -LogPath $dismLogPath if (-not $VhdBoot) { $null = bcdboot "$driveName\Windows" /S $driveName } } finally { if ($null -ne $service -and $service.Status -eq "Running") { Trace-Execution "Remove Windows Defender exclusions for paths $diskPath" Remove-MpPreference -ExclusionPath $diskPath -ErrorAction SilentlyContinue } Get-PSDrive | Where-Object Name -eq $driveLetter | Remove-PSDrive Dismount-DiskImage -ImagePath $VhdPath if ($hwDetectionService) { Start-Service ShellHWDetection Trace-Execution "Started ShellHWDetection Service" } else { Trace-Execution "ShellHWDetection Service was not previously running, no need to restart the service." } } if ($DismFeaturesToInstall) { Enable-WindowsOptionalFeature -FeatureName $DismFeaturesToInstall -Online -All } if ($ServerFeaturesToInstall) { Install-WindowsFeature -Vhd $VhdPath -Name $ServerFeaturesToInstall -IncludeAllSubFeature -IncludeManagementTools } } function New-WimFromVhd { param ( [Parameter(Mandatory=$true)] [string] $WimPath, [Parameter(Mandatory=$true)] [string] $VhdPath, [switch] $Force ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Dismount-MountedDisk if (Test-Path $WimPath) { if ($Force) { Remove-Item $WimPath -Force } else { throw "WIM file $WimPath already exists." } } try { Mount-DiskImage -ImagePath $VhdPath $driveLetter = Get-MountedDiskDriveLetter $null = New-WindowsImage -ImagePath $WimPath -CapturePath "$driveLetter`:" -Name "CPS Host Backup" } finally { Dismount-DiskImage -ImagePath $VhdPath } } <# $newVhdFromWimParameters = @{ VhdPath = 'C:\CloudDeployment\Images\AD-DNS-DHCP.vhdx' WimPath = 'C:\CloudDeployment\Images\install.wim' WimImageIndex = 4 # Full server ServerFeaturesToInstall = 'DHCP', 'DNS', 'AD-Domain-Services' } New-VhdFromWim @newVhdFromWimParameters -Force $newVhdFromWimParameters = @{ VhdPath = 'C:\CloudDeployment\Images\Host.vhdx' WimPath = 'C:\CloudDeployment\Images\install.wim' WimImageIndex = 4 # Full server SizeBytes = 100GB } New-VhdFromWim @newVhdFromWimParameters -Force #> #Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-Offline, Microsoft-Hyper-V-Online -Online -all # Returns back username and password as array count 0 and 1 function Get-UsernameAndPassword { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PSCredential] $Credential ) $networkCredential = $Credential.GetNetworkCredential() $username = $networkCredential.UserName # Replace any password single quotes with double quotes so that we dont break the powershell syntax $password = $networkCredential.Password.Replace("'","''") if (-not $networkCredential.Domain) { # Localhost is needed to create networkshares from winPE $username = 'Localhost\' + $networkCredential.UserName } elseif (-not $username.Contains('\')) { $username = $networkCredential.Domain + '\' + $networkCredential.UserName } $username $password } <# .Synopsis Function to output a path to a deploydirect file that is intended to run from a WinPE context on a booting machine. This must only run from the physical machine role. .Parameter DeployDirectPath The template deploydirect path. .Parameter UsePxeServerRole Whether the deployment script should attempt to communicate with the on-stamp PXE server or with the initial deployment DVM. .Parameter Parameters This object is based on the customer manifest. 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. .Example New-DeployDirect -DeployDirectPath "$PSScriptRoot\DeployDirect.ps1" -UsePxeServerRole $false -Parameters $Parameters This will customize the template deploy direct file using entries from the parameters object, and will use the IP of the DVM in various locations. #> function New-DeployDirect { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] $DeployDirectPath, [Parameter(Mandatory=$true)] [bool] $UsePxeServerRole, [Parameter(Mandatory=$false)] [PSCredential] $PXEAccountCredential, [Parameter(Mandatory=$false)] [PSCredential] $VHDShareAccountCredential, [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory=$false)] [bool] $UseVersionedWim = $true, [Parameter(Mandatory=$true)] [string] $HypervisorSchedulerType ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $physicalMachinesRolePrivateInfo = $Parameters.Configuration.Role.PrivateInfo $securityInfo = $cloudRole.PublicInfo.SecurityInfo # # $PXEAccountCredential is the account used to write logs back to the PXE server or the DVM. It can be either specified by the caller of # this function, or (by default) the local administrator of the DVM. # # $VHDShareAccountCredential is the account used by WinPE to download the VHD to the local host. It can be either the local administrator of # the DVM, or an account specified by the caller of this function (mandatory when an on-stamp PXE server is used) # if ($UsePxeServerRole) { # For domain-joined PXE servers, using the local administrator is not an option, so credentials for both accounts must be specified if ($null -eq $PXEAccountCredential) { throw "Must specify PXEAccountCredential when using an on-stamp PXE server" } if ($null -eq $VHDShareAccountCredential) { throw "Must specify VHDShareAccountCredential when using an on-stamp PXE server" } } else { # For the DVM, we can use the local builtin administrator account, so explicit credentials are not needed $administratorUser = $securityInfo.LocalUsers.User | Where-Object Role -eq $physicalMachinesRolePrivateInfo.Accounts.BuiltInAdminAccountID if ($PXEAccountCredential -eq $null) { $PXEAccountCredential = $Parameters.GetCredential($administratorUser.Credential) } if ($VHDShareAccountCredential -eq $null) { $VHDShareAccountCredential = $Parameters.GetCredential($administratorUser.Credential) } } if ($UsePxeServerRole) { $pxeRole = $Parameters.Roles["PXE"].PublicConfiguration $pxeServerName = $pxeRole.Nodes.Node.Name $vmRoleDefinition = $Parameters.Roles["VirtualMachines"].PublicConfiguration $pxeNode = $vmRoleDefinition.Nodes.Node | Where-Object { $_.Name -eq $pxeServerName } $pxeIPAddress = @($pxeNode.NICs.NIC)[0].IPv4Address.Split("/")[0] } else { $deploymentMachineRole = $Parameters.Roles["DeploymentMachine"].PublicConfiguration $pxeIPAddress = $deploymentMachineRole.Nodes.Node.IPv4Address $pxeServerName = $Env:COMPUTERNAME } if ($UsePxeServerRole) { # Take image content from the PXE server $hostImageDir = "\\$pxeServerName\Images" } else { # Install Image is generated. $hostImageDir = $ExecutionContext.InvokeCommand.ExpandString($physicalMachinesRole.PublicInfo.VhdImageTargetDir.Path) if ($UseVersionedWim) { $hostImageDir = Get-AzSVersionedPath -Path $hostImageDir } if ($hostImageDir -like "*{*}*") { $clusterName = Get-ManagementClusterName $Parameters $hostImageDir = Get-SharePath $Parameters $hostImageDir $clusterName } } Trace-Execution "Using host image folder $hostImageDir for DeployDirect script" if ([Environment]::GetEnvironmentVariable("OSImageType") -eq "ISO") { Trace-Execution "OSImageType is ISO" $hostImagePath = $hostImageDir + '\' + $physicalMachinesRole.PublicInfo.InstallImage.ISOName Trace-Execution "Using host ISO $hostImagePath for DeployDirect script" # Copy SetupComplete.cmd script needed for ISO-based OS deployments to RemoteUnattend folder $isoSetupCompleteScript = "C:\CloudDeployment\Roles\Common\SetupComplete_ISO.cmd" if (Test-Path -Path $isoSetupCompleteScript) { Trace-Execution "Copy '$($isoSetupCompleteScript)' to RemoteUnattend folder" $dest = "C:\RemoteUnattend\SetupComplete.cmd" if (-not(Test-Path -Path (Split-Path -Path $dest -Parent))) { $null = New-Item -ItemType File -Path $dest -Force } Copy-Item -Path $isoSetupCompleteScript -Destination $dest -Force $version = ([string]($Parameters.Roles["Cloud"].PublicConfiguration.PublicInfo.Version)).Trim() # Add line to SetupComplete.cmd to create file on the host when deployment has completed Add-Content -Path $dest -Value "echo $version > C:\CloudBuilderTemp\$($physicalMachinesRole.PublicInfo.DeploymentGuid).txt" } else { Trace-Execution "Could not find 'C:\CloudDeployment\Roles\Common\SetupComplete_ISO.cmd'" } } else { Trace-Execution "OSImageType is VHD" if ($physicalMachinesRole.PublicInfo.InstallImage.Index) { $hostImagePath = $hostImageDir + '\' + $physicalMachinesRole.PublicInfo.InstallImage.VHDName } else { # Use the initial Image. $hostImagePath = $ExecutionContext.InvokeCommand.ExpandString($physicalMachinesRole.PublicInfo.InstallImage.Path) } Trace-Execution "Using host VHD $hostImagePath for DeployDirect script" } # This VHD should be unused $winPEVHDImagePath = $hostImageDir + '\' + $physicalMachinesRole.PublicInfo.BootImage.VHDName $deployScriptContent = Get-Content $DeployDirectPath $deployScriptContent = $deployScriptContent.Replace('[DeploymentGuid]', $physicalMachinesRole.PublicInfo.DeploymentGuid) $deployScriptContent = $deployScriptContent.Replace('[DVMIPAddress]', $pxeIPAddress) $deployScriptContent = $deployScriptContent.Replace('[DVMName]', $pxeServerName) $deployScriptContent = $deployScriptContent.Replace('[HostImagePath]',$hostImagePath) $deployScriptContent = $deployScriptContent.Replace('[WinPEVHDImagePath]', $winPEVHDImagePath) if ($UsePxeServerRole) { # On the PXE server C:\RemoteUnattend must be accessed as \\PXE\RemoteUnattend $deployScriptContent = $deployScriptContent.Replace('[RemoteUnattendShare]', "RemoteUnattend") } else { # Local administrator credentials are used on the DVM, so the C$ share is available $deployScriptContent = $deployScriptContent.Replace('[RemoteUnattendShare]', "C$\RemoteUnattend") } if (-not $hostImagePath.StartsWith('\\')) { # Used to create a host entry in WinPE. $deployScriptContent = $deployScriptContent.Replace('[HostShareIPAddress]', $pxeIPAddress) $deployScriptContent = $deployScriptContent.Replace('[HostShareName]', $pxeServerName) } else { # Get the IP and Hostname from the share. $shareComputerName = $hostImagePath.Substring(2).Split('\')[0] $shareIPv4 = Resolve-DnsName $shareComputerName | Where-Object QueryType -eq 'A' $ip = $null foreach ($shareIp in $shareIPv4.IPAddress) { $succcess = Test-Connection $shareIp -ErrorAction SilentlyContinue -ErrorVariable connectionError if ($succcess) { $ip = $shareIp break } else { Trace-Warning "Share endpoint failed with: $connectionError" } } Trace-Execution "Resolved: '$shareComputerName' to IP: '$ip'" # Used to create a host entry in WinPE $deployScriptContent = $deployScriptContent.Replace('[HostShareIPAddress]', $ip) $deployScriptContent = $deployScriptContent.Replace('[HostShareName]',$shareComputerName) } $user = Get-UsernameAndPassword -Credential $PXEAccountCredential Trace-Execution "Using the account '$($user[0])' for DVM share." $deployScriptContent = $deployScriptContent.Replace('[DVMUser]', $user[0]) $deployScriptContent = $deployScriptContent.Replace('[DVMPassword]', $user[1]) $deployScriptContent = $deployScriptContent.Replace('[HypervisorSchedulerType]', $HypervisorSchedulerType) if ($VHDShareAccountCredential) { $user = Get-UsernameAndPassword -Credential $vhdShareAccountCredential Trace-Execution "Using the account '$($user[0])' for host VHD share." $deployScriptContent = $deployScriptContent.Replace('[VHDSharePathUser]', $user[0]) $deployScriptContent = $deployScriptContent.Replace('[VHDSharePathPassword]',$user[1]) } else { Trace-Execution "Not setting host VHD share credentials." } $networkDefinition = Get-NetworkDefinitions -Parameters $Parameters $networks = $networkDefinition.Networks.Network $hostNicNetworkName = Get-NetworkNameForCluster -ClusterName "s-cluster" -NetworkName "HostNIC" foreach ($network in $networks) { if ($network.Name -eq $hostNicNetworkName) { $deployScriptContent = $deployScriptContent.Replace('[HostNetworkBeginAddress]', $network.IPv4.StartAddress) $deployScriptContent = $deployScriptContent.Replace('[HostNetworkEndAddress]', $network.IPv4.EndAddress) } } $deployScriptContent = $deployScriptContent.Replace('[RebootOnComplete]', '$true') $oneNodeRestore = [Environment]::GetEnvironmentVariable("OneNodeRestore", "Machine") Trace-Execution "OneNodeRestore: $($oneNodeRestore)" if (($UsePxeServerRole) -or (-not [System.String]::IsNullOrEmpty($oneNodeRestore))) { Trace-Execution "Setting ClearExistingStorage to false." $deployScriptContent = $deployScriptContent.Replace('[ClearExistingStorage]', '$false') } else { Trace-Execution "Setting ClearExistingStorage to true." $deployScriptContent = $deployScriptContent.Replace('[ClearExistingStorage]', '$true') } $isAnacortesBuild = [System.Environment]::GetEnvironmentVariable('ANACORTES_DEPLOYMENT', [System.EnvironmentVariableTarget]::Machine) if(((Get-Random -Maximum 100)% 2 -eq 0) -and ([System.String]::IsNullOrWhiteSpace($isAnacortesBuild))) { $deployScriptContent = $deployScriptContent.Replace('[IsOneDrive]', '$true') } else { $deployScriptContent = $deployScriptContent.Replace('[IsOneDrive]', '$false') } $newDeployDirectPath = "$PSScriptRoot\DeployDirectReplaced.ps1" Set-Content -Value $deployScriptContent -Path $newDeployDirectPath -Force return $newDeployDirectPath } function Test-SkipDriverInjection { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [bool] $SkipDriverInjection ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $driverPath = "$env:SystemDrive\CloudDeployment\Drivers" if ($skipDriverInjection) { Trace-Warning 'Driver injection will be skipped as instructed by $CloudBuilder.SkipDriverInjection setting.' } elseif (-not (Test-Path $driverPath)) { Trace-Execution "Driver injection will be skipped as the driver path '$driverPath' does not exist." $skipDriverInjection = $true } elseif (-not (Get-ChildItem $driverPath)) { Trace-Execution "Driver injection will be skipped as the driver path '$driverPath' does not contain any files." $skipDriverInjection = $true } elseif (-not (Get-ChildItem $driverPath -Filter *.inf -Recurse)) { Trace-Execution "Driver injection will be skipped as the driver path '$driverPath' does not contain any drivers (no *.inf files found)." $skipDriverInjection = $true } return $skipDriverInjection } function New-PxeUnattendFile { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] $ComputerName, [Parameter(Mandatory=$false)] [String] $SLPKey, [Parameter(Mandatory=$true)] [string] $MachineIdentifier, [Parameter(Mandatory=$true)] [PSCredential] $LocalAdministratorCredential, [Parameter(Mandatory=$false)] [String] $DomainJoinBlob, [Parameter(Mandatory=$false)] [String] $DomainFqdn, [Parameter(Mandatory=$false)] [PSCredential] $DomainAdminCredential, [Parameter(Mandatory=$false)] [PSCredential] $LocalAccountCredential, [Parameter(Mandatory=$false)] [PSCredential] $LocalCloudAdminCredential, [Parameter(Mandatory=$true)] [String] $UnattendFilePath, [Parameter(Mandatory=$true)] [String] $OutputPath ) $outputGuid = [Guid]::NewGuid(); if (-not [Guid]::TryParse($MachineIdentifier, [ref] $outputGuid)) { # $OFS which is a special variable in powershell . OFS stands for Output field separator . $ofs = '-' $MacAddressString = "$(([System.Net.NetworkInformation.PhysicalAddress]$MachineIdentifier).GetAddressBytes() | ForEach-Object {'{0:X2}' -f $_})" $MachineIdentifier = $MacAddressString } $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $unattendContent = (Get-Content $UnattendFilePath) $unattendContent = $unattendContent.Replace('[ComputerName]', $ComputerName) # Commented out code is not needed for AsZ reset # Clean up later to have AsZ specific method #if ($SLPKey) { # $unattendContent = $unattendContent.Replace('[SLPKey]', $SLPKey) #} #else { # $unattendContent = $unattendContent -replace '<ProductKey>.*</ProductKey>', '' #} $unattendContent = $unattendContent.Replace('[LocalAdministratorPassword]', [System.Security.SecurityElement]::Escape(([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($LocalAdministratorCredential.Password))))) #$unattendContent = $unattendContent.Replace('[LocalAdministratorName]', $LocalAdministratorCredential.UserName) # Multi-node tokens #$unattendContent = $unattendContent.Replace('[DomainJoinBlob]', $DomainJoinBlob) #$unattendContent = $unattendContent.Replace('[DomainFqdn]', $DomainFqdn) #if ($DomainAdminCredential) { # $unattendContent = $unattendContent.Replace('[DomainAdminUsername]', $DomainAdminCredential.UserName) # $unattendContent = $unattendContent.Replace('[DomainAdminPassword]', [System.Security.SecurityElement]::Escape(([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($DomainAdminCredential.Password))))) #} #if ($LocalAccountCredential) { # $unattendContent = $unattendContent.Replace('[LocalAccountUsername]', $LocalAccountCredential.UserName) # $unattendContent = $unattendContent.Replace('[LocalAccountPassword]', [System.Security.SecurityElement]::Escape(([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($LocalAccountCredential.Password))))) #} #if ($LocalCloudAdminCredential) { # $unattendContent = $unattendContent.Replace('[LocalCloudAdminUsername]', $LocalCloudAdminCredential.UserName) # $unattendContent = $unattendContent.Replace('[LocalCloudAdminPassword]', [System.Security.SecurityElement]::Escape(([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($LocalCloudAdminCredential.Password))))) #} $unattendFilePath = "$OutputPath\$MachineIdentifier.xml" if (Test-Path $unattendFilePath) { Remove-Item $unattendFilePath -Force } Set-Content -Value $unattendContent -Path "$OutputPath\$MachineIdentifier.xml" -Force } function New-WinPEImage { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] $DeployDirectPath, [Parameter(Mandatory=$false)] [bool] $UseVersionedWim = $true, [Parameter(Mandatory=$false)] [PSCredential] $PXEAccountCredential, [Parameter(Mandatory=$false)] [PSCredential] $VHDShareAccountCredential, [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Switch]$PostDeployment ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $physicalMachinesRolePrivateInfo = $Parameters.Configuration.Role.PrivateInfo $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $OEMRole = $Parameters.Roles["OEM"].PublicConfiguration $OEMModel = $OEMRole.PublicInfo.UpdatePackageManifest.UpdateInfo.Model $clusterName = Get-ManagementClusterName $Parameters $winPEBootImage = $physicalMachinesRole.PublicInfo.BootImage if ($PostDeployment.IsPresent) { $UsePxeServerRole = $true $bootImageSrcDir = $physicalMachinesRolePrivateInfo.LibraryShareImageFolder.Path } else { $UsePxeServerRole = $false $bootImageSrcDir = $ExecutionContext.InvokeCommand.ExpandString($winPEBootImage.SrcDir) } if ($bootImageSrcDir -like "*{*}*") { $bootImageSrcDir = Get-SharePath $Parameters $bootImageSrcDir $clusterName } # Use versioned path if specified if ($UseVersionedWim) { $bootImageSrcDir = Get-AzSVersionedPath -Path $bootImageSrcDir } $bootImagePath = Join-Path $bootImageSrcDir $winPEBootImage.BaseImage $bootImageIndex = $winPEBootImage.Index $vhdTargetDirPath = $ExecutionContext.InvokeCommand.ExpandString($physicalMachinesRole.PublicInfo.VhdImageTargetDir.Path) if ($vhdTargetDirPath -like "*{*}*") { $vhdTargetDirPath = Get-SharePath $Parameters $vhdTargetDirPath $clusterName } # Use versioned path if specified if ($UseVersionedWim) { $vhdTargetDirPath = Get-AzSVersionedPath -Path $vhdTargetDirPath } $winpeVhdImagePath = Join-Path $vhdTargetDirPath -ChildPath $winPEBootImage.VHDName $mountPath = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid()) if ($PostDeployment.IsPresent) { $wimStagingPath = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid()) $GenerateWim = $true $GenerateVhd = $false } else { $wimStagingPath = $bootImageSrcDir $skipCopy = $true $Script:IsIdempotentRun = [Bool]::Parse($physicalMachinesRolePrivateInfo.ExecutionContext.IdempotentRun) $GenerateWim = ((-not $Script:IsIdempotentRun) -or (-not (Test-Path $winpeVhdImagePath))) $GenerateVhd = $GenerateWim } if ($GenerateWim) { # Attempt to start defender if not already running $service = Get-Service -Name "windefend" -ErrorAction SilentlyContinue if ($null -ne $service -and $service.Status -ine "Running") { $service | Start-Service -ErrorAction SilentlyContinue | Out-Null } # On virtual environments to avoid infra VM reboots change hypervisor scheduler type to Classic. # On physical enviornments the scheduler type will be Core if (IsVirtualAzureStack($Parameters)) { $hypervisorSchedulerType = "Classic" } else { $hypervisorSchedulerType = "Core" } try { $newDeployDirectPath = New-DeployDirect ` -DeployDirectPath $deployDirectPath ` -PXEAccountCredential $PXEAccountCredential ` -VHDShareAccountCredential $VHDShareAccountCredential ` -UsePxeServerRole $UsePxeServerRole ` -Parameters $Parameters ` -UseVersionedWim $UseVersionedWim ` -HypervisorSchedulerType $hypervisorSchedulerType Dismount-AllMountedImages if (-not (Test-Path $mountPath)) { $null = New-Item -Path $mountPath -ItemType Directory } if (-not (Test-Path $wimStagingPath)) { $null = New-Item -Path $wimStagingPath -ItemType Directory } # set exclusion path when windefend is running if ($null -ne $service -and $service.Status -eq "Running") { Trace-Execution "Exclude mount folders $mountPath and $wimStagingPath from Windows Defender scan to speed up configuration." Add-MpPreference -ExclusionPath $mountPath, $wimStagingPath -ErrorAction SilentlyContinue } Trace-Execution "Copying required content from '$bootImageSrcDir' to '$wimStagingPath'." if (-not $skipCopy) { $out = Robocopy.exe $bootImageSrcDir $wimStagingPath $physicalMachinesRole.PublicInfo.BootImage.BaseImage /R:2 /W:10 # Check for exit code. If exit code is greater than 7, it means an error occured while peforming a copy operation. if ($LASTEXITCODE -ge 8) { $message = "There were errors copying files from '$bootImageSrcDir' to '$wimStagingPath'. Robocopy exit code $LASTEXITCODE.`n" $message += ($out | Out-String) throw $message } } $wimStagingFile = Join-Path $wimStagingPath $physicalMachinesRole.PublicInfo.BootImage.BaseImage Trace-Execution "Mount '$wimStagingFile' to '$mountPath'." Mount-WindowsImageWithRetry -ImagePath $wimStagingFile -Path $mountPath -Index $bootImageIndex ######### This section is associated with Work Item 15253266 and should be removed after the data corruption bug is fixed. ######### Trace-Execution "Mount '$wimStagingFile' successful." $registryPath = Join-Path $mountPath "\Windows\System32\config\SYSTEM" reg load HKLM\OFFLINE $registryPath reg add "HKLM\OFFLINE\ControlSet001\Control\Session Manager\Memory Management" /v VerifyDrivers /t REG_SZ /d * /f reg add "HKLM\OFFLINE\ControlSet001\Control\Session Manager\Memory Management" /v VerifyDriverLevel /t REG_DWORD /d 0x89 /f reg unload HKLM\OFFLINE Trace-Execution "Temporary registry setting complete." ######### Copy-Item "$newDeployDirectPath" "$mountPath\DeployDirect.ps1" -Recurse -Force Copy-Item "$PSScriptRoot\..\Common\DeployDirectCommon.psm1" "$mountPath\DeployDirectCommon.psm1" -Recurse -Force Copy-Item "$PSScriptRoot\..\Common\startnet.cmd" "$mountPath\Windows\System32\startnet.cmd" -Recurse -Force Copy-Item "$PSScriptRoot\..\..\Common\Helpers.psm1" "$mountPath\Helpers.psm1" -Force Copy-Item "$PSScriptRoot\..\..\Common\Tracer.psm1" "$mountPath\Tracer.psm1" -Force Trace-Execution "Dismount and save mounted image." Dismount-WindowsImage -Path $mountPath -Save -Verbose:$false if (-not $skipCopy) { Trace-Execution "Copying updated boot image '$wimStagingFile' back to '$bootImageSrcDir'." $out = Robocopy.exe $wimStagingPath $bootImageSrcDir $physicalMachinesRole.PublicInfo.BootImage.BaseImage /R:2 /W:10 # Check for exit code. If exit code is greater than 7, it means an error occured while performing a copy operation. if ($LASTEXITCODE -ge 8) { $message = "There were errors copying files from '$wimStagingPath' to '$bootImageSrcDir'. Robocopy exit code $LASTEXITCODE.`n" $message += ($out | Out-String) throw $message } Remove-Item -Path $wimStagingFile -Force -Confirm:$false -ErrorAction Ignore } } finally { Dismount-AllMountedImages if ($null -ne $service -and $service.Status -eq "Running") { Trace-Execution "Remove Windows Defender exclusions for paths $MountPath and $wimStagingPath" Remove-MpPreference -ExclusionPath $MountPath, $wimStagingPath -ErrorAction SilentlyContinue } } if ($GenerateVhd) { Trace-Execution "Converting WIM image '$bootImagePath' to VHDBoot image '$winpeVhdImagePath'." $newVhdFromWimParameters = @{ WimPath = $BootImagePath VhdPath = $winpeVhdImagePath WimImageIndex = $BootImageIndex SizeBytes = 120GB VhdBoot = $true } New-VhdFromWim @newVhdFromWimParameters -Force -Verbose } } } <# Mounts the Wim file and injects desired content into it #> function Update-BootWimFile { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [Parameter(Mandatory=$false)] [bool] $UseVersionedWim = $true ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration Trace-Execution "Update boot.wim file" $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $virtualMachinesRole = $Parameters.Roles["VirtualMachines"].PublicConfiguration $clusterName = Get-ManagementClusterName $Parameters # Windows Update staging folder $windowsUpdateStagingFolder = $physicalMachinesRole.PublicInfo.WindowsUpdateStagingFolder.Path if ($windowsUpdateStagingFolder -like "*{*}*") { $windowsUpdateStagingFolder = Get-SharePath $Parameters $windowsUpdateStagingFolder $clusterName } else { $windowsUpdateStagingFolder = $ExecutionContext.InvokeCommand.ExpandString($windowsUpdateStagingFolder) } # Boot image $winPEBootImage = $physicalMachinesRole.PublicInfo.BootImage $bootImageSrcDir = $ExecutionContext.InvokeCommand.ExpandString($winPEBootImage.SrcDir) if ($bootImageSrcDir -like "*{*}*") { $bootImageSrcDir = Get-SharePath $Parameters $bootImageSrcDir $clusterName } if ($UseVersionedWim) { $bootImagePath = Join-Path -Path (Get-AzSVersionedPath -Path $bootImageSrcDir) -ChildPath $winPEBootImage.BaseImage } else { $bootImagePath = Join-Path $bootImageSrcDir $winPEBootImage.BaseImage } $bootImageIndex = $winPEBootImage.Index if (Test-Path -Path $bootImagePath) { # Get rid of any half-built previous WIM. Clear-ImageMount -Path $bootImagePath -AlternateFileServerName $clusterName } # Mount the WIM file $mountedImage = [MountedImage]::new($bootImagePath, $bootImageIndex) # Get the image build and associated drivers path $windowsBuild = $mountedImage.GetBuild() $driverPath = Get-OEMDriverPath -Parameter $Parameters -Build $windowsBuild # Update the WIM file try { # Whenever Build runs on a seed ring node, there is very limited disk space. # Use a scratch drive for applying KBs. [string[]] $seedRingNodeNames = $Parameters.Roles["SeedRing"].PublicConfiguration.Nodes.Node | ForEach-Object Name if ($seedRingNodeNames -icontains $env:COMPUTERNAME) { Import-Module -Name "$PSScriptRoot\..\..\Roles\Common\Servicing\Scripts\Modules\HostTempDrive.psm1" $physicalHostName = Get-NodeRefNodeId $Parameters -RoleName "VirtualMachines" -NodeName $env:COMPUTERNAME $drive = New-ScratchDiskForNonHAVM -ComputerName $env:COMPUTERNAME -HyperVHosts @($physicalHostName) try { Trace-Execution "Running UpdateImageWithWindowsUpdates on a seed ring, using update scratch directory." # Inject Windows updates # TEMP: Ignore applicability check for packages when adding them to WinPE image. $mountedImage.UpdateImageWithWindowsUpdates($windowsUpdateStagingFolder, "$drive`:\UpdateTemp", $true) } finally { Remove-ScratchDiskForNonHAVM -ComputerName $env:COMPUTERNAME -HyperVHosts @($physicalHostName) } } else { # Inject Windows updates Trace-Execution "Injecting updates from non-seedring orchestrator." $mountedImage.UpdateImageWithWindowsUpdates($windowsUpdateStagingFolder) } # Inject drivers $skipDriverInjection = [Bool]::Parse($physicalMachinesRole.PublicInfo.SkipDriverInjection) $mountedImage.AddDriversToImage($driverPath, $skipDriverInjection) # Inject deployment content if ($Parameters.Configuration.Role.PrivateInfo.DeploymentContent) { $libraryShareNugetStorePath = Get-SharePath $Parameters $virtualMachinesRole.PublicInfo.LibraryShareNugetStoreFolder.Path $clusterName if (Test-Path -Path $libraryShareNugetStorePath) { $mountedImage.ExpandDeploymentArtifacts($Parameters.Configuration.Role.PrivateInfo.DeploymentContent, $libraryShareNugetStorePath) } else { $mountedImage.ExpandDeploymentArtifacts($Parameters.Configuration.Role.PrivateInfo.DeploymentContent) } } if ($UseVersionedWim) { # Get current or UpdateVersion in case of update $updateVersion = Get-InProgressUpdateVersion -Parameters $Parameters if ($null -ne $updateVersion) { $version = $updateVersion } else { $version = ([string]($cloudRole.PublicInfo.Version)).Trim() } $mountedImage.UpdateImageStatusFile($version, "BuildCompleted") } } finally { $mountedImage.Dispose() } } <# .SYNOPSIS Dismounts all the mounted images .DESCRIPTION Dismounts all the mounted images #> function Dismount-AllMountedImages { Import-Module Dism Clear-WindowsCorruptMountPoint $mountedImages = Get-WindowsImage -Mounted -Verbose:$false if ($mountedImages) { Trace-Execution "Found the following mounted images:`r`n$($mountedImages | Out-String)" Trace-Execution "Discard previously mounted images." $null = $mountedImages | ForEach-Object { $mountPath = $_.MountPath try { Trace-Execution "Unmounting '$mountPath'" Dismount-WindowsImage -Path $mountPath -Discard -Verbose:$false } catch { # It is possible we may have hit mount point invalid mount point state due to ECE failover # so applying remediation and retrying. See VSO 8588300 for more details. Trace-Execution "Failed to unmount '$mountPath' so unloading mounted keys and retrying" $mountPathKey = $mountPath.Replace('\','/') $(reg query HKLM) | ForEach-Object { if ($_ -like "*$mountPathKey*") { reg unload $_ } } Dismount-WindowsImage -Path $mountPath -Discard -Verbose:$false } } } # If the mounted image was undergoing a DISM operation while ECE service was failed over or crashed, # it's possible that the image is still mounted. We can look for any such disks mounted from the SU1FileServer # and clean them up. $mountedDisks = Get-Disk | Where-Object Location -match "SU1FileServer" if ($mountedDisks) { Trace-Execution "Found the following mounted disks:`r`n$($mountedDisks | Format-List Number, FriendlyName, Size, Location)" foreach ($disk in $mountedDisks) { Dismount-VHD -DiskNumber $disk.Number } } Import-Module Storage Get-Volume | Where-Object FileSystemLabel -eq 'Deployment' | ForEach-Object { Get-DiskImage -DevicePath $_.Path.TrimEnd('\') } | Dismount-DiskImage } <# .Synopsis Reliable atomic copy of file. .Description This function performs a reliable copy of a file by using temporary staging copy and hash validation mechanism. This function is designed to be re-entrant. #> function Copy-FileAtomic { [CmdletBinding()] param ( [Parameter(Mandatory = $true, HelpMessage="Literal path to the source file which is to be copied.")] [string] $SourceFilePath, [Parameter(Mandatory = $true, HelpMessage="Literal path to the destination file where the source file would be copied to.")] [string] $DestinationFilePath ) $destFile = Split-Path -path $DestinationFilePath -Leaf $destFolder = Split-Path -path $DestinationFilePath -Parent $newFileName = '{0}.new' -f $destFile $prevFileName = '{0}.prev' -f $destFile $newPath = Join-Path $destFolder $newFileName $prevPath = Join-Path $destFolder $prevFileName # Handle code re-entrancy if (Test-Path $prevPath) { if (Test-Path $DestinationFilePath) { Write-Verbose "Removing stale file: $prevPath" Remove-Item -Path $prevPath } else { Write-Verbose "Renaming stale file: $prevPath to $DestinationFilePath" Rename-Item -Path $prevPath -NewName $DestinationFilePath } } # Copy file to a temp file in destination folder Write-Verbose "Copying file $SourceFilePath to $newPath" Copy-Item -Path $SourceFilePath -Destination $newPath -Force -ErrorAction Stop # Validate copied staging file against original source file if ((Get-FileHash $SourceFilePath).hash -ne (Get-FileHash $newPath).hash) { throw "$newPath is not copied correctly." } # Handle atomicity and overwrite issues $overwrite = Test-Path $DestinationFilePath if ($overwrite) { Write-Verbose "Renaming existing destination file: $DestinationFilePath to $prevPath" Rename-Item -Path $DestinationFilePath -NewName $prevPath -ErrorAction Stop } Write-Verbose "Renaming new file: $newPath to $DestinationFilePath" Rename-Item -Path $newPath -NewName $DestinationFilePath -ErrorAction Stop if ($overwrite) { Write-Verbose "Removing stale file: $prevPath" Remove-Item -Path $prevPath } } <# .SYNOPSIS Copy base images into the verioned folder .DESCRIPTION Creates copies of the base images in the versioned folder. #> function New-VersionedImages { [CmdletBinding()] param ( [string] $InProgressImageFolder, [string] $BaseImageFolder, [string[]] $Images ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # If the build folder exists, delete it and start again if (Test-Path $InProgressImageFolder) { Trace-Execution "Clean up old content as build did not complete during previous run." Remove-Item -Path $InProgressImageFolder -Recurse -Force } Trace-Execution "Creating build folder $InProgressImageFolder" $null = New-Item -Path $InProgressImageFolder -ItemType Directory -Force # Loop through all name of all the images needed foreach ($imageName in $Images) { $newImagePath = Join-Path -Path $InProgressImageFolder -ChildPath $imageName if (Test-Path $newImagePath) { Trace-Execution "The new image: $newImagePath already exists. Skipping creation of this image" } else { $sourceImagePath = Join-Path -Path $BaseImageFolder -ChildPath $imageName if (-not(Test-Path -Path $sourceImagePath)) { $sourceImagePath = Join-Path -Path 'C:\CustomArtifacts\Core\Image' -ChildPath $imageName if (-not(Test-Path -Path $sourceImagePath)) { throw "Unable to find '$($imageName)' in any known source image path." } } Trace-Execution "Copying source image at '$sourceImagePath' to destination image path '$newImagePath'." Copy-FileAtomic -SourceFilePath $sourceImagePath -DestinationFilePath $newImagePath -Verbose -ErrorAction Stop } } } <# .SYNOPSIS Get names of the images needed for deployment and updates .DESCRIPTION Creates copies of the base images in the versioned folder. .PARAMETER Parameters The EceInterfaceParameters object which is populated by ECE #> function Get-SourceImageNames { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [switch] $SkipWim, [switch] $SkipArtifacts ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $images = @() # Roles $physicalMachinesRole = $Parameters.Roles["BareMetal"].PublicConfiguration $virtualMachinesRole = $Parameters.Roles["VirtualMachines"].PublicConfiguration if ($SkipWim.IsPresent -eq $false) { # Image to be used for host # Boot Image (boot.wim) $bootImageName = $physicalMachinesRole.PublicInfo.BootImage.BaseImage if ($bootImageName) { $images += $bootImageName } } # Image to be used for host OS. $hostOSImageName = $physicalMachinesRole.PublicInfo.InstallImage.BaseImage if ($hostOSImageName -and $images -notcontains $hostOSImageName) { $images += $hostOSImageName } if (($SkipWim.IsPresent -eq $false) -and ([Environment]::GetEnvironmentVariable("OSImageType") -eq "ISO")) { $images += $physicalMachinesRole.PublicInfo.InstallImage.ISOName } # Identify names of the VHDs to be copied, update the base images to be used for the guest VMs $guestVMs = $virtualMachinesRole.Nodes.Node foreach ($vm in $guestVMs) { $osImageName = $vm.Vhds.Vhd | Where-Object Index -eq 0 | ForEach-Object Path if ($images -notcontains $osImageName) { # Add it to the array to avoid configuration of the same image multiple times $images += $osImageName } # Add Artifacts VHD to list of required images if specified in any VM role definition. $artifactsVhdName = $vm.Vhds.Vhd | Where-Object Path -eq "Artifacts.vhdx" | ForEach-Object Path if (![string]::IsNullOrEmpty($artifactsVhdName)) { Trace-Execution "Artifacts VHD '$artifactsVhdName' is requested by role definition of VM '$($vm.Name)'." Trace-Execution "SkipArtifacts switch is '$SkipArtifacts'." Trace-Execution "Current image list: '$images'." if (!$SkipArtifacts.IsPresent -and $images -notcontains $artifactsVhdName) { Trace-Execution "Add Artifacts VHD '$artifactsVhdName' to list of images required for deployment or update." $images += $artifactsVhdName Trace-Execution "Updated image list: '$images'." } } } $images = $images | where {$_ -inotmatch 'WindowsServerCore.vhdx'} return $images } # This function clears the mounted image. function Clear-ImageMount { param ( # This will be \\XXXX\XXVM_temp*.vhdx [Parameter(Mandatory=$true)] [Alias("UncPath")] [string] $Path, [switch] $DeleteImage, # If the path is on the file server this script will attempt to close any open SMB handles. # Doing so requires a PS session to the file server. However the file server name (SU1FileServer) can resolve # to changing IPs, which can break the underlying WinRM session. As a workaround, an alternate target server name # can be provided by the caller (typically the cluster name), which always resolves to the same IP address. [string] $AlternateFileServerName ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Check if file or its dism mount exists. if (-not (Test-Path $Path*)) { Trace-Execution "The specified image '$Path' does not exist, no cleanup is necessary." return } if (Get-WindowsImage -Mounted | Where-Object MountStatus -eq 'Invalid') { Trace-Execution "Clear corrupt WindowsImage/DISM mount points." $null = Clear-WindowsCorruptMountPoint -Verbose:$False } Get-Disk | Where-Object Location -like $Path | ForEach-Object { Trace-Execution "Dismount image mounted by Mount-DiskImage on the local host." Dismount-DiskImage -ImagePath $_.Location -Confirm:$false } if ((Get-ChildItem "$Path.dism*" -Force) -or ((Get-ChildItem -Path $Path).Extension -eq '.wim')) { Trace-Execution "The file appears to be mounted by Mount-WindowsImage command, attempting to dismount it gracefully." Get-WindowsImage -Mounted -Verbose:$False | Where-Object ImagePath -like $Path | ForEach-Object { if ($_.MountStatus -eq 'NeedsRemount') { $null = New-Item -Path $_.Path -ItemType Directory -Force Mount-WindowsImage -Path $_.Path -Remount -Verbose:$False } $null = Dismount-WindowsImage -Path $_.Path -Discard -Verbose:$False } } # Check if path points to a network share. $uri = [System.Uri]$Path if ($uri.IsUnc) { $serverName = $uri.Host # WinNextWorkaround: do not use SU1FileServer as the target name for remote sessions. Use the cluster name instead. if ($serverName -eq "SU1FileServer" -and $AlternateFileServerName) { Trace-Execution "WinNextWorkaround: Known issues with creating remote sessions to SU1FileServer. Connecting to $AlternateFileServerName instead." $serverName = $AlternateFileServerName } $sharePath = [System.IO.Path]::GetPathRoot($Path) $shareRelativePath = $Path.Replace($sharePath, '').TrimStart('\') Trace-Execution "Establish remote connection to file server '$serverName' to remove potential locks on the file '$Path'." Invoke-Command $serverName { $verbose = $using:VerbosePreference -eq [System.Management.Automation.ActionPreference]::Continue Trace-Execution "Get SMB file handles to '$using:shareRelativePath'." -Verbose:$verbose $smbOpenFiles = Get-SmbOpenFile | Where-Object ShareRelativePath -like "$using:shareRelativePath*" if ($smbOpenFiles) { Trace-Execution "Remove SMB file handles to $($smbOpenFiles.ShareRelativePath -join ', ')." -Verbose:$verbose $smbOpenFiles | Close-SmbOpenFile -Force -Verbose:$verbose } } } # If the mounted image was undergoing a DISM operation it's possible that the image is still mounted. # We can look for any such disks mounted from the SU1FileServer and clean them up. $mountedDisks = $null try { $mountedDisks = Get-Disk | Where-Object Location -match $Path } catch { Trace-Execution "Failed to get disk using location match. '$_' . Trying exact match." try { $mountedDisks = Get-Disk | Where-Object Location -eq $Path } catch { Trace-Execution "Failed to get disk using exact match. '$_'." } } try { if ($mountedDisks) { Trace-Execution "Found the following mounted disks:`r`n$($mountedDisks | Format-List Number, FriendlyName, Size, Location | Out-String)" foreach ($disk in $mountedDisks) { Dismount-VHD -DiskNumber $disk.Number } } } catch { Trace-Execution "Best-effort error during dismount share disks: $_" } if (Get-ChildItem "$Path.dism*" -Force) { Trace-Execution "Failed to dismount image gracefully, attempting to forcefully remove the file." $dismFilePath = Get-ChildItem "$Path.dism*" -Force Trace-Execution "Remove '$dismFilePath'." $dismFilePath | Remove-Item -Force } if ($DeleteImage -and (Test-Path $Path)) { Trace-Execution "Delete image '$Path'." Remove-Item -Path $Path -Force } } Export-ModuleMember -Function Add-GuestCluster Export-ModuleMember -Function Add-IDnsConfiguration Export-ModuleMember -Function Add-IPAddress Export-ModuleMember -Function Add-LoadBalancerToNetworkAdapter Export-ModuleMember -Function Add-NetworkAdapterToNetwork Export-ModuleMember -Function Clear-ImageMount Export-ModuleMember -Function ConnectPSSession Export-ModuleMember -Function ConvertFrom-IPAddress Export-ModuleMember -Function Convert-IPv4IntToString Export-ModuleMember -Function Convert-IPv4StringToInt Export-ModuleMember -Function ConvertTo-IPAddress Export-ModuleMember -Function ConvertTo-MacAddress Export-ModuleMember -Function ConvertTo-PrefixLength Export-ModuleMember -Function ConvertTo-SubnetMask Export-ModuleMember -Function Dismount-AllMountedImages Export-ModuleMember -Function Dismount-MountedDisk Export-ModuleMember -Function Expand-DeploymentArtifacts Export-ModuleMember -Function Expand-NugetContent Export-ModuleMember -Function Expand-UpdateContent Export-ModuleMember -Function Get-BareMetalCredential Export-ModuleMember -Function Get-BroadcastAddress Export-ModuleMember -Function Get-ClusterShare Export-ModuleMember -Function Get-ClusterShareNames Export-ModuleMember -Function Get-DomainCredential Export-ModuleMember -Function Get-DomainIPMapping Export-ModuleMember -Function Get-ExecutionContextNodeName Export-ModuleMember -Function Get-GatewayAddress Export-ModuleMember -Function Get-HostUpdateShare Export-ModuleMember -Function Get-InProgressUpdateVersion Export-ModuleMember -Function Get-IsVirtualNetworkAlreadyConfigured Export-ModuleMember -Function Get-JeaSession Export-ModuleMember -Function Get-LocalCsvPathFromSharePath Export-ModuleMember -Function Get-MacAddress Export-ModuleMember -Function Get-MacAddressString Export-ModuleMember -Function Get-MountedDisk Export-ModuleMember -Function Get-MountedDiskDriveLetter Export-ModuleMember -Function Get-NCAccessControlList Export-ModuleMember -Function Get-NCCredential Export-ModuleMember -Function Get-NCGateway Export-ModuleMember -Function Get-NCGatewayPool Export-ModuleMember -Function Get-NCIPPool Export-ModuleMember -Function Get-NCLoadBalancer Export-ModuleMember -Function Get-NCLoadbalancerManager Export-ModuleMember -Function Get-NCLoadBalancerMux Export-ModuleMember -Function Get-NCLogicalNetwork Export-ModuleMember -Function Get-NCLogicalNetworkSubnet Export-ModuleMember -Function Get-NCMACPool Export-ModuleMember -Function Get-NCNetworkInterface Export-ModuleMember -Function Get-NCNetworkInterfaceInstanceId Export-ModuleMember -Function Get-NCNetworkInterfaceResourceId Export-ModuleMember -Function Get-NCPublicIPAddress Export-ModuleMember -Function Get-NCServer Export-ModuleMember -Function Get-NCSwitch Export-ModuleMember -Function Get-NCVirtualGateway Export-ModuleMember -Function Get-NCVirtualNetwork Export-ModuleMember -Function Get-NCVirtualServer Export-ModuleMember -Function Get-NCVirtualSubnet Export-ModuleMember -Function Get-NetworkAddress Export-ModuleMember -Function Get-NetworkDefinitionForCluster Export-ModuleMember -Function Get-NetworkDefinitions Export-ModuleMember -Function Get-NetworkMap Export-ModuleMember -Function Get-NetworkNameForCluster Export-ModuleMember -Function Get-PortProfileId Export-ModuleMember -Function Get-RangeEndAddress Export-ModuleMember -Function Get-ScopeRange Export-ModuleMember -Function Get-ServerResourceId Export-ModuleMember -Function Get-SharePath Export-ModuleMember -Function Get-SourceImageNames Export-ModuleMember -Function Get-StorageEndpointName Export-ModuleMember -Function Get-UsernameAndPassword Export-ModuleMember -Function Initialize-ECESession Export-ModuleMember -Function Invoke-ECECommand Export-ModuleMember -Function Invoke-PSDirectOnVM Export-ModuleMember -Function Invoke-ScriptBlockInParallel Export-ModuleMember -Function Invoke-ScriptBlockWithRetries Export-ModuleMember -Function Invoke-WebRequestWithRetries Export-ModuleMember -Function IsIpPoolRangeValid Export-ModuleMember -Function IsIpWithinPoolRange Export-ModuleMember -Function JSONDelete Export-ModuleMember -Function JSONGet Export-ModuleMember -Function JSONPost Export-ModuleMember -Function Mount-WindowsImageWithRetry Export-ModuleMember -Function New-ACL Export-ModuleMember -Function New-Credential Export-ModuleMember -Function New-DeployDirect Export-ModuleMember -Function New-LoadBalancerVIP Export-ModuleMember -Function New-NCAccessControlList Export-ModuleMember -Function New-NCAccessControlListRule Export-ModuleMember -Function New-NCBgpPeer Export-ModuleMember -Function New-NCBgpRouter Export-ModuleMember -Function New-NCBgpRoutingPolicy Export-ModuleMember -Function New-NCBgpRoutingPolicyMap Export-ModuleMember -Function New-NCCredential Export-ModuleMember -Function New-NCGateway Export-ModuleMember -Function New-NCGatewayPool Export-ModuleMember -Function New-NCGreTunnel Export-ModuleMember -Function New-NCIPPool Export-ModuleMember -Function New-NCIPSecTunnel Export-ModuleMember -Function New-NCL3Tunnel Export-ModuleMember -Function New-NCLoadBalancer Export-ModuleMember -Function New-NCLoadBalancerBackendAddressPool Export-ModuleMember -Function New-NCLoadBalancerFrontEndIPConfiguration Export-ModuleMember -Function New-NCLoadBalancerLoadBalancingRule Export-ModuleMember -Function New-NCLoadBalancerMux Export-ModuleMember -Function New-NCLoadBalancerMuxPeerRouterConfiguration Export-ModuleMember -Function New-NCLoadBalancerOutboundNatRule Export-ModuleMember -Function New-NCLoadBalancerProbe Export-ModuleMember -Function New-NCLoadBalancerProbeObject Export-ModuleMember -Function New-NCLogicalNetwork Export-ModuleMember -Function New-NCLogicalNetworkSubnet Export-ModuleMember -Function New-NCLogicalSubnet Export-ModuleMember -Function New-NCMACPool Export-ModuleMember -Function New-NCNetworkInterface Export-ModuleMember -Function New-NCPublicIPAddress Export-ModuleMember -Function New-NCServer Export-ModuleMember -Function New-NCServerConnection Export-ModuleMember -Function New-NCServerNetworkInterface Export-ModuleMember -Function New-NCSlbState Export-ModuleMember -Function New-NCSwitch Export-ModuleMember -Function New-NCSwitchPort Export-ModuleMember -Function New-NCVirtualGateway Export-ModuleMember -Function New-NCVirtualNetwork Export-ModuleMember -Function New-NCVirtualServer Export-ModuleMember -Function New-NCVirtualSubnet Export-ModuleMember -Function New-NCVpnClientAddressSpace Export-ModuleMember -Function New-PxeUnattendFile Export-ModuleMember -Function New-VersionedImages Export-ModuleMember -Function New-VhdFromWim Export-ModuleMember -Function New-WimFromVhd Export-ModuleMember -Function New-WinPEImage Export-ModuleMember -Function NormalizeIPv4Subnet Export-ModuleMember -Function PublishAndStartDscConfiguration Export-ModuleMember -Function PublishAndStartDscForJea Export-ModuleMember -Function Remove-LoadBalancerFromNetworkAdapter Export-ModuleMember -Function Remove-NCAccessControlList Export-ModuleMember -Function Remove-NCCredential Export-ModuleMember -Function Remove-NCGateway Export-ModuleMember -Function Remove-NCGatewayPool Export-ModuleMember -Function Remove-NCIPPool Export-ModuleMember -Function Remove-NCLoadBalancer Export-ModuleMember -Function Remove-NCLoadBalancerMux Export-ModuleMember -Function Remove-NCLogicalNetwork Export-ModuleMember -Function Remove-NCMACPool Export-ModuleMember -Function Remove-NCNetworkInterface Export-ModuleMember -Function Remove-NCPublicIPAddress Export-ModuleMember -Function Remove-NCServer Export-ModuleMember -Function Remove-NCSwitch Export-ModuleMember -Function Remove-NCVirtualGateway Export-ModuleMember -Function Remove-NCVirtualNetwork Export-ModuleMember -Function Remove-NCVirtualServer Export-ModuleMember -Function Remove-PortProfileId Export-ModuleMember -Function Set-MacAndIPAddress Export-ModuleMember -Function Set-NCConnection Export-ModuleMember -Function Set-NCLoadBalancerManager Export-ModuleMember -Function Set-PortProfileId Export-ModuleMember -Function Set-PortProfileIdHelper Export-ModuleMember -Function Test-IPConnection Export-ModuleMember -Function Test-NetworkMap Export-ModuleMember -Function Test-PSSession Export-ModuleMember -Function Test-SkipDriverInjection Export-ModuleMember -Function Test-WSManConnection Export-ModuleMember -Function Trace-Error Export-ModuleMember -Function Trace-Execution Export-ModuleMember -Function Trace-Warning Export-ModuleMember -Function Update-BootWimFile Export-ModuleMember -Function Update-JEAEndpointsForUpdate Export-ModuleMember -Function Update-NCCredential Export-ModuleMember -Function Update-NCServer Export-ModuleMember -Function Update-NCVirtualServer Export-ModuleMember -Function Wait-Result Export-ModuleMember -Function Wait-VirtualNetwork # SIG # Begin signature block # MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBW7frEEy9LDFln # B2FNjd9HOu0VLVpEEoWhT/Q9gxCqrqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIAbqGo6n2JnnkcMzo9PLqhAP # L3WelPHJNNUXriYNKJpJMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAxxAQWVsPbn5XhBXVCUsjRW3Mm531K59ua3NM2dbURPCnIn5nSBWLnj4N # YGGg5iOONQ1wzDId1fdESuh+vz3fTHV395zv8ihMPU6vVD/d9VkP7w0yELJU/xYi # EVLGX+ah5g4DF4LamMCbvIy+FdfHOuSw3rlN/mdvcyOmtU8opFimj9y92XqvH9HZ # 2H91dbEQu1vo0Nc5bHKxlfGEzab0h9681210P8/cyw+rcD0N3b6ubEZaQFVd0dLM # XD1RBZhlWeONYncSiLosCUX/fx9lUBjJGDd+37OUJfT52A+B+A+HFk0cbznkB702 # hVkKjqc6meXJpYqMiAenHciUBzyCJ6GCFykwghclBgorBgEEAYI3AwMBMYIXFTCC # FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq # hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBWKfWFNKcgy+CyKxBFqMovFYnfDkgt4ZLd+FmcwTuc/QIGZjOp4a5y # GBMyMDI0MDUxNjE4NDQzOC41MThaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO # OjhENDEtNEJGNy1CM0I3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHj372bmhxogyIAAQAAAeMwDQYJ # KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx # MDEyMTkwNzI5WhcNMjUwMTEwMTkwNzI5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl # cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4RDQxLTRC # RjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL6kDWgeRp+fxSBUD6N/yuEJ # pXggzBeNG5KB8M9AbIWeEokJgOghlMg8JmqkNsB4Wl1NEXR7cL6vlPCsWGLMhyqm # scQu36/8h2bx6TU4M8dVZEd6V4U+l9gpte+VF91kOI35fOqJ6eQDMwSBQ5c9ElPF # UijTA7zV7Y5PRYrS4FL9p494TidCpBEH5N6AO5u8wNA/jKO94Zkfjgu7sLF8SUdr # c1GRNEk2F91L3pxR+32FsuQTZi8hqtrFpEORxbySgiQBP3cH7fPleN1NynhMRf6T # 7XC1L0PRyKy9MZ6TBWru2HeWivkxIue1nLQb/O/n0j2QVd42Zf0ArXB/Vq54gQ8J # IvUH0cbvyWM8PomhFi6q2F7he43jhrxyvn1Xi1pwHOVsbH26YxDKTWxl20hfQLdz # z4RVTo8cFRMdQCxlKkSnocPWqfV/4H5APSPXk0r8Cc/cMmva3g4EvupF4ErbSO0U # NnCRv7UDxlSGiwiGkmny53mqtAZ7NLePhFtwfxp6ATIojl8JXjr3+bnQWUCDCd5O # ap54fGeGYU8KxOohmz604BgT14e3sRWABpW+oXYSCyFQ3SZQ3/LNTVby9ENsuEh2 # UIQKWU7lv7chrBrHCDw0jM+WwOjYUS7YxMAhaSyOahpbudALvRUXpQhELFoO6tOx # /66hzqgjSTOEY3pu46BFAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUsa4NZr41Fbeh # Z8Y+ep2m2YiYqQMwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD # VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG # CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw # MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD # CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBALe+my6p1NPMEW1t # 70a8Y2hGxj6siDSulGAs4UxmkfzxMAic4j0+GTPbHxk193mQ0FRPa9dtbRbaezV0 # GLkEsUWTGF2tP6WsDdl5/lD4wUQ76ArFOencCpK5svE0sO0FyhrJHZxMLCOclvd6 # vAIPOkZAYihBH/RXcxzbiliOCr//3w7REnsLuOp/7vlXJAsGzmJesBP/0ERqxjKu # dPWuBGz/qdRlJtOl5nv9NZkyLig4D5hy9p2Ec1zaotiLiHnJ9mlsJEcUDhYj8PnY # nJjjsCxv+yJzao2aUHiIQzMbFq+M08c8uBEf+s37YbZQ7XAFxwe2EVJAUwpWjmtJ # 3b3zSWTMmFWunFr2aLk6vVeS0u1MyEfEv+0bDk+N3jmsCwbLkM9FaDi7q2HtUn3z # 6k7AnETc28dAvLf/ioqUrVYTwBrbRH4XVFEvaIQ+i7esDQicWW1dCDA/J3xOoCEC # V68611jriajfdVg8o0Wp+FCg5CAUtslgOFuiYULgcxnqzkmP2i58ZEa0rm4LZymH # BzsIMU0yMmuVmAkYxbdEDi5XqlZIupPpqmD6/fLjD4ub0SEEttOpg0np0ra/MNCf # v/tVhJtz5wgiEIKX+s4akawLfY+16xDB64Nm0HoGs/Gy823ulIm4GyrUcpNZxnXv # E6OZMjI/V1AgSAg8U/heMWuZTWVUMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ # mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh # dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1 # WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK # NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg # fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp # rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d # vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9 # 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR # Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu # qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO # ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb # oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6 # bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t # AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW # BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb # UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku # aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA # QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2 # VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu # bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw # LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt # MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q # XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6 # U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt # I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis # 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp # kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0 # sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e # W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ # sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7 # Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0 # dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ # tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh # bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 # RDQxLTRCRjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAPYiXu8ORQ4hvKcuE7GK0COgxWnqggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOnwnAgwIhgPMjAyNDA1MTYyMjUxNTJaGA8yMDI0MDUxNzIyNTE1MlowdDA6Bgor # BgEEAYRZCgQBMSwwKjAKAgUA6fCcCAIBADAHAgEAAgIXqDAHAgEAAgIRgTAKAgUA # 6fHtiAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID # B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAF9eSLSLQKipJTTTaY4c # 0F1vlCQAMyG6Qt0jL02K36614fHgV8B0ErR/iXHlNRZdFTI2782ssG/D6CijJ3AH # TxafoggArkBMNiLuXdqJVmPvVqrKRMsghmv3sw5C1hUvao5DbIREZZptic1WQxq4 # juoCt2jLCFJr6m5/xQ7y4/L1MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTACEzMAAAHj372bmhxogyIAAQAAAeMwDQYJYIZIAWUDBAIB # BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx # IgQgok5fy+buuCV6zy1GH0a+ctFNT00EOwbqR1KfkLN73SgwgfoGCyqGSIb3DQEJ # EAIvMYHqMIHnMIHkMIG9BCAz1COr5bD+ZPdEgQjWvcIWuDJcQbdgq8Ndj0xyMuYm # KjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB49+9 # m5ocaIMiAAEAAAHjMCIEINSVRnUuYBeZqM4TwZYP+bWXmQhX8PsgoHifPm9NVR98 # MA0GCSqGSIb3DQEBCwUABIICACQxQxbuDccHh7UWK7b7Q7Ikr8/LcJM3zsyKHagA # LZE3gECnaKCeDY+bBKeqHmtk0XT1pJfu2OPp7SND2r2XAHhuiqJfRrwoM7u0aYhP # vTkH8Ehg5/UuaWTSVPSWcn81V8zj2epLHpe7FkHC90qoKEEIVgU6NL7mqEHov9/2 # OBcIR8EShTfXGIH6SYu/pUSThEqeu+wFeIPd9FNms+ntgu6Or6yL+MtuJIf5NldM # +ddLxQzDoleYruD+8aCAKqYNS6QzDQpT79RlajngzjXn6cpGsOFzk6N93PtKGdpi # +6S/sjS8LoNLlwBXrlTlmnspV73/lav+TBiHG7uMpzbkQy70zIXi0DgrXrGJbmS3 # A3iV2y1OgsQqJ9PcRDgdVyF0a63Rzc0iBdhw+wb13bOkD/PjuXfszLGHVKhjqxrd # tUdi+OQKIIQLPmzwS5d9AZJ5KCEHpm8XcXMomerO3PS5xhFaHf2ehOoQ5q2zjpSs # hIQMQ+1RRhXzp630G4igsVktNXXIQ/H+AJspQhaNfUgWsFKy97TIEWVPQ2+2VjOq # 63JqlDO5FLbUl9mOuqmJs/ssE07Ch9Ibnp2k12ELYoJJioLnmg+LLoGyCG631E/n # WuP/V9F0DZYsW/IryKd4UEhYWwdVMkIGsqzY9AVDerGIxMBF0td/eFIpc3uQg78o # kq3g # SIG # End signature block |