RDWebClientManagement.psm1
# # Copyright (C) Microsoft. All rights reserved. # $defaultInstallationPath = $env:ProgramFiles + '\RemoteDesktopWeb' $configKeyPath = 'HKLM:\Software\Microsoft\RemoteDesktopWeb' $installedPathValueName = 'InstalledPath' $managementToolsVersionName = 'ManagementToolsVersion' $suppressTelemetryValueName = 'SuppressTelemetry' $launchResourceInBrowserValueName = 'LaunchResourceInBrowser' $internalDirName = 'Internal' $clientDirName = 'Clients' $packageTempDirName = 'Temp' $configDataDirName = 'Config' $prodClientVdirName = 'webclient' $testClientVdirName = 'webclient-test' $everybodyIdentity = New-Object System.Security.Principal.SecurityIdentifier 'S-1-1-0' $everybodyReadRule = New-Object System.Security.AccessControl.FileSystemAccessRule $everybodyIdentity,'Read','Allow' $nobodyReadRule = New-Object System.Security.AccessControl.FileSystemAccessRule $everybodyIdentity,'Read','Deny' $rdWebPath = '/RDWeb' $manifestFileName = 'manifest.json' $clientContentDirName = 'content' [System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography.X509Certificates') function GetIISServerManager { $iisServer = $null try { $iisServer = Get-IISServerManager } catch { Write-Error "The operation cannot be completed. RD Web Access does not appear to be installed on the system." return $null } return $iisServer } function UpdateIISContext { [OutputType([Boolean])] [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Low')] Param( [parameter(Mandatory=$true)] $iisContext ) $iisContext.IISServerManager = GetIISServerManager if (!$iisContext.IISServerManager) { return $false } $webSites = Get-IISSite Foreach ($site in $webSites) { $rdWebApp = $site.Applications | Where-Object {$_.Path -eq $rdWebPath } | Select-Object -First 1 if ($rdWebApp) { $iisContext.RdWebApplication = $rdWebApp $iisContext.RdWebSite = $site Break } } if (!$iisContext.RdWebApplication) { Write-Error 'RD Web Access does not appear to be installed on the system.' return $false } else { $iisContext.ProdClientVdir = $iisContext.RdWebApplication.VirtualDirectories | Where-Object {$_.Path -eq "/$prodClientVdirName"} | Select-Object -First 1 $iisContext.ProdClientConfigVdir = $iisContext.RdWebApplication.VirtualDirectories | Where-Object {$_.Path -eq "/$prodClientVdirName/config"} | Select-Object -First 1 $iisContext.TestClientVdir = $iisContext.RdWebApplication.VirtualDirectories | Where-Object {$_.Path -eq "/$testClientVdirName"} | Select-Object -First 1 $iisContext.TestClientConfigVdir = $iisContext.RdWebApplication.VirtualDirectories | Where-Object {$_.Path -eq "/$testClientVdirName/config"} | Select-Object -First 1 } return $true } function EnsureDirectoryExists ( [Parameter(mandatory=$true)] $Path, $AccessRule ) { $targetDir = Get-Item $Path -ErrorAction SilentlyContinue if (!$targetDir) { $targetDir = New-Item -Path $Path -ItemType 'directory' if (!$targetDir) { return $FALSE } if ($AccessRule) { $acl = Get-Acl $Path $acl.SetAccessRule($AccessRule) Set-Acl $Path $acl } } return $TRUE } function UpdateDeploymentSettingsFile ( $context ) { $deploymentSettings = @{ 'deploymentType' = 'rdWeb'; 'suppressTelemetry' = $context.DeploymentSettings.SuppressTelemetry }; $disableLaunchResourcesInBrowser = $context.ConfigKey.GetValue($launchResourceInBrowserValueName) if($disableLaunchResourcesInBrowser -ne $null) { $deploymentSettings.Add('launchResourceInBrowser', ($disableLaunchResourcesInBrowser -gt 0)); } Set-Content -Path $context.DeploymentSettingsPath ('var DeploymentSettings = ' + (ConvertTo-Json $deploymentSettings)) } function EnsureInitialized { [OutputType([Boolean])] [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Low')] Param( $context, [string] $installPath ) if ($env:RdWebClientManagementCatalogURL) { $context.PackageCatalogUrl = $env:RdWebClientManagementCatalogURL } else { $context.PackageCatalogUrl = 'https://go.microsoft.com/fwlink/?linkid=2005418' } if (!$installPath) { $installPath = $defaultInstallationPath } $internalDirPath = Join-Path -Path $installPath -ChildPath $internalDirName $context.ClientPath = Join-Path -Path $internalDirPath -ChildPath $clientDirName $context.PackageTempPath = Join-Path -Path $internalDirPath -ChildPath $packageTempDirName $context.ConfigDataPath = Join-Path -Path $internalDirPath -ChildPath $configDataDirName $context.BrokerCertPath = Join-Path -Path $context.ConfigDataPath -ChildPath 'brokercert.cer' $context.DeploymentSettingsPath = Join-Path -Path $context.ConfigDataPath -ChildPath 'deploymentSettings.js' $installedDir = Get-Item $installPath -ErrorAction SilentlyContinue if (!$installedDir) { if (!$PSCmdlet.ShouldProcess( "RDWebClientManagement in '$installPath'", 'Install')) { return $false } } $freshInstall = $FALSE if (!$context.ConfigKey) { $context.ConfigKey = New-Item -Path $configKeyPath $context.ConfigKey.SetValue($installedPathValueName, $installPath) $context.ConfigKey.SetValue($suppressTelemetryValueName, $false, [Microsoft.Win32.RegistryValueKind]::DWord) $context.InstalledPath = $installPath $freshInstall = $TRUE } $lastVersionStr = $context.ConfigKey.GetValue($managementToolsVersionName) if (!$lastVersionStr) { $lastVersionStr = '0.8' } $lastVersion = [System.Version]$lastVersionStr if (!$freshInstall -and ($lastVersion -lt [System.Version]'1.0')) { Write-Error ("The web client was installed using an older version of RDWebClientManagement and " + "must first be removed before deploying the new version. Using the old version of " + "RDWebClientManagement, uninstall the web client by running 'Uninstall-RDWebClient'. " + "Then reinstall the web client using this new module. For more information, " + "visit https://go.microsoft.com/fwlink/?linkid=870181 .") return $FALSE } $context.ConfigKey | Set-ItemProperty -Name $managementToolsVersionName -Value $context.Version.ToString() if (!$installedDir) { Write-Warning "Initializing RDWebClientManagement in '$installPath'. To uninstall, use Uninstall-RDWebClient." if (!(EnsureDirectoryExists -Path $installPath)) { return $FALSE } } if (!(EnsureDirectoryExists -Path $internalDirPath -AccessRule $nobodyReadRule)) { return $FALSE } if (!(EnsureDirectoryExists -Path $context.ClientPath -AccessRule $everybodyReadRule)) { return $FALSE } if (!(EnsureDirectoryExists -Path $context.PackageTempPath)) { return $FALSE } if (!(EnsureDirectoryExists -Path $context.ConfigDataPath -AccessRule $everybodyReadRule)) { return $FALSE } $context.DeploymentSettings = @{ 'SuppressTelemetry' = $false; } $disableTelemetry = $context.ConfigKey.GetValue($suppressTelemetryValueName) $context.DeploymentSettings.SuppressTelemetry = ($null -ne $disableTelemetry) -and ($disableTelemetry -gt 0) $disableLaunchResourcesInBrowser = $context.ConfigKey.GetValue($launchResourceInBrowserValueName) if($disableLaunchResourcesInBrowser -ne $null) { $context.DeploymentSettings.Add('LaunchResourceInBrowser', ($disableLaunchResourcesInBrowser -gt 0)); } UpdateDeploymentSettingsFile $context return $TRUE } function GetModuleContext { $contextProps = @{}; $contextProps.Version = (Get-Module RDWebClientManagement).Version $contextProps.ConfigKey = Get-Item $configKeyPath -ErrorAction SilentlyContinue [string]$contextProps.InstalledPath = $null [string]$contextProps.ClientPath = $null [string]$contextProps.PackageTempPath = $null [string]$contextProps.ConfigDataPath = $null [string]$contextProps.BrokerCertPath = $null [string]$contextProps.DeploymentSettingsPath = $null $contextProps.PackageCatalogUrl = $null $contextProps.DeploymentSettings = @{} if ($contextProps.ConfigKey) { $contextProps.InstalledPath = $contextProps.ConfigKey.GetValue($installedPathValueName) } $context = New-Object -TypeName PSObject -Property $contextProps return $context; } function GetModuleIISContext { $contextProps = @{}; $contextProps.IISServerManager = $null $contextProps.RdWebApplication = $null $contextProps.RdWebSite = $null [Microsoft.Web.Administration.VirtualDirectory]$contextProps.ProdClientVdir = $null [Microsoft.Web.Administration.VirtualDirectory]$contextProps.ProdClientConfigVdir = $null [Microsoft.Web.Administration.VirtualDirectory]$contextProps.TestClientVdir = $null [Microsoft.Web.Administration.VirtualDirectory]$contextProps.TestClientConfigVdir = $null $iisContext = New-Object -TypeName PSObject -Property $contextProps return $iisContext; } function CheckClientSupport { Param( [Parameter(mandatory=$true)] $context, [Parameter(mandatory=$true)] $package ) if (!$package.minRDWebClientManagementVersion -or ([System.Version]$package.minRDWebClientManagementVersion -gt $context.Version) ) { Write-Error "The requested package cannot be installed with a version of RDWebClientManagement lower than '$($package.minRDWebClientManagementVersion)'." return $FALSE } return $TRUE } function GetRDWebClientPackagesInternal { Param( [Parameter(mandatory=$true)] $context ) foreach ($clientDir in Get-ChildItem $context.ClientPath -Directory) { $clientObject = $null $clientObject = Get-ChildItem $clientDir.FullName $manifestFileName -File | Get-Content -ErrorAction Continue | ConvertFrom-Json -ErrorAction Continue if ($clientObject -and $clientObject.version) { $clientObject | Add-Member 'path' $clientDir.FullName $clientObject | Add-Member '_baseVersion' ([System.Version]::Parse(($clientObject.version -replace '^([0-9.]*).*$','$1'))) Write-Output $clientObject } } } <# .SYNOPSIS Entirely removes the Remote Desktop web client from the system, including any installed client packages and configured settings #> function Uninstall-RDWebClient { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]Param() $context = GetModuleContext if (!$PSCmdlet.ShouldProcess( 'The Remote Desktop web client, including any installed client packages and configured settings.', 'Uninstall' )) { return } if ($context.InstalledPath) { $iis = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Web.Administration') if ($iis) { $iisContext = GetModuleIISContext if (!(UpdateIISContext $iisContext)) { Write-Warning "Installed web client packages not found. Continuing with rest of uninstallation." $iisContext = $null } } else { $iisContext = $null } if ($iisContext -and $iisContext.RdWebApplication -and $iisContext.IISServerManager) { $confChanged = $FALSE if ($iisContext.TestClientVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($iisContext.TestClientVdir) $confChanged = $TRUE } if ($iisContext.TestClientConfigVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($iisContext.TestClientConfigVdir) $confChanged = $TRUE } if ($iisContext.ProdClientVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($iisContext.ProdClientVdir) $confChanged = $TRUE } if ($iisContext.ProdClientConfigVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($iisContext.ProdClientConfigVdir) $confChanged = $TRUE } if ($confChanged) { $iisContext.IISServerManager.CommitChanges() } } $internalPath = Join-Path $context.InstalledPath $internalDirName $acl = Get-Acl $internalPath $acl.RemoveAccessRule($nobodyReadRule) | Out-Null Set-Acl $internalPath $acl Get-Item $context.InstalledPath | Remove-Item -Recurse -Force } else { Write-Error 'The Remote Desktop web client is not installed.' return } Remove-Item $configKeyPath -ErrorAction SilentlyContinue } <# .SYNOPSIS Finds the Remote Desktop web client packages that are available for installation .PARAMETER RequiredVersion Specifies the exact version of the package to find. By default, Find-RDWebClientPackage only returns the newest available version. .PARAMETER AllVersions Indicates that Find-RDWebClientPackage should return all available versions of the client. By default, Find-RDWebClientPackage only returns the newest available version. .EXAMPLE Find-RDWebClientPackage Description ----------- Finds the client package with the highest version .EXAMPLE Find-RDWebClientPackage -AllVersions Description ----------- Finds all available client packages .EXAMPLE Find-RDWebClientPackage -RequiredVersion 0.7.0 Description ----------- Finds the client package with version number 0.7.0, if it is available #> function Find-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true)] [OutputType([PSCustomObject])] Param( [Parameter()] [string]$RequiredVersion, [switch]$AllVersions ) $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $catalog = Invoke-RestMethod $context.PackageCatalogUrl if (!$catalog -or !$catalog.packages) { Write-Error 'The package catalog could not be read or was invalid.' return } $results = $catalog.packages if ($RequiredVersion) { $results = $results | Where-Object {$_.version -eq $RequiredVersion} } foreach ($client in $results) { if ($client.version) { $client | Add-Member '_baseVersion' ([System.Version]::Parse(($client.version -replace '^([0-9.]*).*$','$1'))) } } $results = $results | Sort-Object packageId,_baseVersion -Descending if (!$AllVersions) { $results = $results | Select-Object -First 1 } $results } <# .SYNOPSIS Installs a Remote Desktop web client package locally .PARAMETER RequiredVersion Specifies the exact version of the package to install. If you do not add this parameter, Install-RDWebClientPackage installs the highest available version of the client. .PARAMETER Source File path for the RD web client package to install. The Save-RDWebClientPackage command can be used to download the RD web client package. .EXAMPLE Install-RDWebClientPackage Description ----------- Installs the client package with the highest version .EXAMPLE Install-RDWebClientPackage -RequiredVersion 0.7.0 Description ----------- Installs the client package with version number 0.7.0, if it is available .EXAMPLE Install-RDWebClientPackage -Source c:\rdwebclientpackages\1.0.0.zip Description ----------- Installs the client package located at c:\rdwebclientpackages\1.0.0.zip #> function Install-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter()] [string]$RequiredVersion, [Parameter()] [string]$Source ) $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess('The Remote Desktop web client package','Install') if (!($initialized -and $continue)) { return } $iis = GetIISServerManager if (!$iis) { return } if ($Source) { $newClientPath = Join-Path -Path $context.ClientPath -ChildPath ([System.IO.Path]::GetRandomFileName()) Expand-Archive $Source $newClientPath | Out-Null $newClientObject = Get-ChildItem $newClientPath $manifestFileName -File | Get-Content -ErrorAction Continue | ConvertFrom-Json -ErrorAction Continue if ($newClientObject -and $newClientObject.version) { if ($RequiredVersion -and $RequiredVersion -ne $newClientObject.version) { Write-Warning 'The requested Remote Desktop web client version was not found.' Remove-Item $newClientPath -Recurse -Force return } $clients = GetRDWebClientPackagesInternal($context) foreach ($client in $clients) { if ($client.version -eq $newClientObject.version -and $client.path -ne $newClientPath) { Write-Warning 'The requested Remote Desktop web client is already installed.' Remove-Item $newClientPath -Recurse -Force return } } } else { Write-Warning 'The source Remote Desktop web client package is not valid.' Remove-Item $newClientPath -Recurse -Force return } } else { $packageObject = $null if ($RequiredVersion) { $packageObject = Find-RDWebClientPackage -RequiredVersion $RequiredVersion if (!$packageObject) { Write-Error 'The requested Remote Desktop web client package is not available.' return } } else { $packageObject = Find-RDWebClientPackage if (!$packageObject) { Write-Error 'The Remote Desktop web client is not available for installation' return } } if (GetRDWebClientPackagesInternal $context | Where-Object {$_.version -eq $packageObject.version}) { Write-Warning 'The requested Remote Desktop web client is already installed.' return } if (!(CheckClientSupport $context $packageObject)) { return } $archiveFileName = Join-Path -Path $context.PackageTempPath -ChildPath ([System.IO.Path]::GetRandomFileName() + '.zip') $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($packageObject.url, $archiveFileName); $newClientPath = Join-Path -Path $context.ClientPath -ChildPath ([System.IO.Path]::GetRandomFileName()) Expand-Archive $archiveFileName $newClientPath | Out-Null Remove-Item $archiveFileName } } <# .SYNOPSIS Saves a Remote Desktop web client package locally for installing later on same or different machine. .PARAMETER RequiredVersion Specifies the exact version of the package to install. If you do not add this parameter, Save-RDWebClientPackage saves the highest available version of the client. .PARAMETER Path Folder path for saving the web client package .EXAMPLE Save-RDWebClientPackage c:\rdwebclientpackages Description ----------- Saves the client package with the highest version to c:\rdwebclientpackages .EXAMPLE Save-RDWebClientPackage c:\rdwebclientpackages -RequiredVersion 1.0.0 Description ----------- Saves the client package with version number 1.0.0, if it is available, to c:\rdwebclientpackages directory #> function Save-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(mandatory=$true, Position=0)] [string] $Path, [Parameter(mandatory=$false)] [string]$RequiredVersion ) $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess('The Remote Desktop web client package','Save') if (!($initialized -and $continue)) { return } $packageObject = $null if ($RequiredVersion) { $packageObject = Find-RDWebClientPackage -RequiredVersion $RequiredVersion if (!$packageObject) { Write-Error 'The requested Remote Desktop web client package is not available.' return } } else { $packageObject = Find-RDWebClientPackage if (!$packageObject) { Write-Error 'The Remote Desktop web client is not available' return } } $archiveFileName = Join-Path -Path $Path -ChildPath ('rdwebclient-' + $packageObject.version + '.zip') $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($packageObject.url, $archiveFileName); Write-Information "RD web client package saved '$archiveFileName'." } <# .SYNOPSIS Returns a list of all Remote Desktop web client packages that have been installed locally #> function Get-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true)] [OutputType([PSCustomObject])] Param() $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $result = @() $clients = GetRDWebClientPackagesInternal($context) if ($clients) { $iis = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Web.Administration') if (!$iis) { Write-Error "IIS must be installed to perform this operation" return } $iisContext = GetModuleIISContext if (!(UpdateIISContext $iisContext)) { return } foreach ($client in $clients) { $client | Add-Member 'publishedAs' @() $clientContent = Join-Path $client.path $clientContentDirName if ($iisContext.TestClientVdir -and $iisContext.TestClientVdir.PhysicalPath -eq $clientContent) { $client.publishedAs += 'Test' } if ($iisContext.ProdClientVdir -and $iisContext.ProdClientVdir.PhysicalPath -eq $clientContent) { $client.publishedAs += 'Production' } $result += $client } $result | Sort-Object packageId,_baseVersion -Descending } else { Write-Information 'No clients are installed.' } } <# .SYNOPSIS Uninstalls a Remote Desktop web client package from this system .PARAMETER RequiredVersion Specifies the exact version of the package to uninstall. .EXAMPLE Uninstall-RDWebClientPackage -RequiredVersion 0.7.0 Description ----------- Uninstalls the client package with version number 0.7.0, if it is available #> function Uninstall-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param( [Parameter(mandatory=$true)] [string]$RequiredVersion ) $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess("The Remote Desktop web client package, version '$RequiredVersion'",'Uninstall') if (!($initialized -and $continue)) { return } $iis = GetIISServerManager if (!$iis) { return } $package = Get-RDWebClientPackage | Where-Object {$_.version -eq $RequiredVersion} | Select-Object -First 1 if (!$package) { Write-Error 'The requested client package is not installed.' return } if ($package.publishedAs.Count -gt 0) { Write-Error 'The requested client package has been published. You must unpublish it with Unpublish-RDWebClientPackage before you can uninstall it.' return } Get-Item $package.path | Remove-Item -Recurse -Force } <# .SYNOPSIS Publishes a Remote Desktop web client package through IIS .PARAMETER Type Indicates the type of deployment to publish as. The type must be either 'Test' or 'Production'. If the type is 'Test', the client will be published in the "RDWeb/webclient-test" vdir. If the type is 'Production', the client will be published in the "RDWeb/webclient" vdir. .PARAMETER RequiredVersion Specifies the exact version of the package to publish. .PARAMETER Latest Specifies that the installed web client package with the highest version should be published. .EXAMPLE Publish-RDWebClientPackage -Latest -Type Test Description ----------- Publishes the installed web client with the highest version number into the "RDWeb/webclient-test" vdir .EXAMPLE Publish-RDWebClientPackage -RequiredVersion 0.7.0 -Type Production Description ----------- Publishes the installed web client with version number 0.7.0 into the "RDWeb/webclient" vdir #> function Publish-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param( [Parameter(mandatory=$true, Position=0)] [ValidateSet('Test', 'Production')] [string]$Type, [Parameter(mandatory=$true, ParameterSetName = 'RequiredVersion')] [string]$RequiredVersion, [Parameter(mandatory=$true, ParameterSetName = 'Latest')] [switch]$Latest ) $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess('The Remote Desktop web client package','Publish alongside Remote Desktop Web Access') if (!($initialized -and $continue)) { return } $iis = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Web.Administration') if (!$iis) { Write-Error "IIS must be installed to perform this operation" return } $iisContext = GetModuleIISContext if (!(UpdateIISContext $iisContext)) { return } if (!(Get-Item -Path $context.BrokerCertPath -ErrorAction SilentlyContinue)) { Write-Error 'A broker certificate must be installed before publishing a client. Use Import-RDWebClientBrokerCert.' return } $targetClient = $null if ($Latest) { $targetClient = Get-RDWebClientPackage | Sort-Object -Property _baseVersion -Descending | Select-Object -First 1 } elseif ($RequiredVersion) { $targetClient = Get-RDWebClientPackage | Where-Object {$_.version -eq $RequiredVersion} | Select-Object -First 1 } $previouspackage = Get-RDWebClientPackage | Where-Object {$Type -in $_.publishedAs} | Select-Object -First 1 if ($previouspackage) { if ($previouspackage.version -eq $targetClient.version) { Write-Warning 'The requested package has already been published for this client type.' return } Write-Warning "Replacing the previously published client package for client type '$Type' (version = $($previouspackage.version) ) with (version = $($targetClient.version) )..." } if (!$targetClient) { Write-Error 'The requested client package is not installed.' return } if (!(CheckClientSupport $context $targetClient)) { return } Write-Warning 'Using the Remote Desktop web client with per-device licensing is not supported.' [Microsoft.Web.Administration.VirtualDirectory]$targetVdir = $null [Microsoft.Web.Administration.VirtualDirectory]$targetConfigVdir = $null $targetVdirName = $null $targetConfigVdirPath = $null if ($Type -eq 'Test') { $targetVdir = $iisContext.TestClientVdir $targetVdirName = $testClientVdirName $targetConfigVdir = $iisContext.TestClientConfigVdir $targetConfigVdirPath = "/$testClientVdirName/config" } else { $targetVdir = $iisContext.ProdClientVdir $targetVdirName = $prodClientVdirName $targetConfigVdir = $iisContext.ProdClientConfigVdir $targetConfigVdirPath = "/$prodClientVdirName/config" } $clientContentPath = Join-Path $targetClient.path $clientContentDirName if ($targetVdir) { $targetVdir.PhysicalPath = $clientContentPath } else { $targetVdir = $iisContext.RdWebApplication.VirtualDirectories.Add("/$targetVdirName", $clientContentPath) | Out-Null } if ($targetConfigVdir) { $targetConfigVdir.PhysicalPath = $context.ConfigDataPath } else { $targetConfigVdir = $iisContext.RdWebApplication.VirtualDirectories.Add($targetConfigVdirPath, $context.ConfigDataPath) | Out-Null } Set-WebConfigurationProperty -PSPath "IIS:\Sites\$($iisContext.RdWebSite)\RDWeb" -Location $targetVdirName -Filter 'system.webServer/httpRedirect' -Name '.' -Value @{enabled = 'false'} $mimeTypeConfig = Get-WebConfigurationProperty -PSPath "IIS:\Sites\$($iisContext.RdWebSite)\RDWeb" -Filter "system.webServer/staticContent" -Name "." if(!($mimeTypeConfig.Collection | Where-Object { $_.fileExtension -eq ".wasm"} | select fileExtension)) { Add-WebConfigurationProperty -PSPath "IIS:\Sites\$($iisContext.RdWebSite)\RDWeb" -Filter "system.webServer/staticContent" -Name "." -Value @{ fileExtension='.wasm'; mimeType='application/wasm' } } $iisContext.IISServerManager.CommitChanges() UpdateIISContext($iisContext) | Out-Null } <# .SYNOPSIS Unpublishes a previously-published Remote Desktop web client package .PARAMETER Type Indicates the type of deployment to unpublish. The type must be either 'Test' or 'Production'. If the type is 'Test', the client in the "RDWeb/webclient-test" vdir will be unpublished. If the type is 'Production', the client in the "RDWeb/webclient" vdir will be unpublished. .EXAMPLE Unpublish-RDWebClientPackage -Type Production Description ----------- Unpublishes the currently-published production client #> function Unpublish-RDWebClientPackage { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param( [Parameter(mandatory=$true)] [ValidateSet('Test', 'Production')] [string]$Type ) $isTest = $Type -eq 'Test' $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $iis = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Web.Administration') if (!$iis) { Write-Error "IIS must be installed to perform this operation" return } $iisContext = GetModuleIISContext if (!(UpdateIISContext $iisContext)) { return } if ($isTest) { $vdirName = $testClientVdirName } else { $vdirName = $prodClientVdirName } $continue = $PSCmdlet.ShouldProcess("The Remote Desktop web client in '$vdirName'",'Unpublish') if (!($initialized -and $continue)) { return } [Microsoft.Web.Administration.VirtualDirectory]$targetVdir = $null [Microsoft.Web.Administration.VirtualDirectory]$targetConfigVdir = $null if ($isTest) { $targetVdir = $iisContext.TestClientVdir $targetConfigVdir =$iisContext.TestClientConfigVdir } else { $targetVdir = $iisContext.ProdClientVdir $targetConfigVdir =$iisContext.ProdClientConfigVdir } if ($targetVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($targetVdir) if ($targetConfigVdir) { $iisContext.RdWebApplication.VirtualDirectories.Remove($targetConfigVdir) } Remove-WebConfigurationLocation -PSPath "IIS:\Sites\$($iisContext.RdWebSite)\RDWeb" -Name $vdirName | Out-Null $iisContext.IISServerManager.CommitChanges() } else { Write-Error 'The requested client type is not published.' } } <# .SYNOPSIS Imports a broker certificate file for use with the Remote Desktop web client .PARAMETER Path File path for the certificate file to import .PARAMETER Password The password to use in opening the certificate file. .PARAMETER PromptForPassword Indicates that Import-RDWebClientBrokerCert should interactively prompt the user for a password to use in opening the certificate file. .EXAMPLE Import-RDWebClientBrokerCert brokercert.cer Description ----------- Imports a certificate from a .cer file .EXAMPLE Import-RDWebClientBrokerCert brokercert.pfx -PromptForPassword Description ----------- Imports a certificate from a password-protected .pfx file. An interactive prompt will be used to request the password that is needed to open the file. #> function Import-RDWebClientBrokerCert { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(mandatory=$true, Position=0)] [string] $Path, [Parameter(mandatory=$false, ParameterSetName = 'NoPrompt')] [SecureString] $Password, [Parameter(mandatory=$true, ParameterSetName = 'WithPrompt')] [switch] $PromptForPassword ) $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess('The certificate that identifies the Remote Desktop Connection Broker', 'Import') if (!($initialized -and $continue)) { return } $iis = GetIISServerManager if (!$iis) { return } $certificateCreateArgs = @($Path) $password = $Password if ($PromptForPassword) { $password = Read-Host -Prompt 'Password' -AsSecureString } if ($password) { $certificateCreateArgs += $password } $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certificateCreateArgs if ($cert) { [System.IO.File]::WriteAllBytes($context.BrokerCertPath, $cert.RawData) } } <# .SYNOPSIS Returns information about the broker certificate that is being used by the Remote Desktop web client #> function Get-RDWebClientBrokerCert { [CmdletBinding(SupportsShouldProcess=$true)] [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] Param() $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $iis = GetIISServerManager if (!$iis) { return } if (Get-Item $context.BrokerCertPath -ErrorAction SilentlyContinue) { New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 @($context.BrokerCertPath) } else { Write-Information 'A broker certificate is not installed.' } } <# .SYNOPSIS Removes the broker certificate that is being used by the Remote Desktop web client #> function Remove-RDWebClientBrokerCert { [CmdletBinding(SupportsShouldProcess=$true)]Param() $context = GetModuleContext $initialized = !!(EnsureInitialized $context) $continue = $PSCmdlet.ShouldProcess('The certificate that identifies the Remote Desktop Connection Broker', 'Remove') if (!($initialized -and $continue)) { return } $iis = GetIISServerManager if (!$iis) { return } if (Get-Item $context.BrokerCertPath -ErrorAction SilentlyContinue) { Remove-Item $context.BrokerCertPath } else { Write-Information 'A broker certificate is not installed.' } } <# .SYNOPSIS Sets a deployment setting for the Remote Desktop web client .PARAMETER Name Specifies the name of the setting .PARAMETER Value The new value for the setting .EXAMPLE Set-RDWebClientDeploymentSetting SuppressTelemetry $true Description ----------- Disables telemetry gathering in the client #> function Set-RDWebClientDeploymentSetting { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(mandatory=$true)] [string] $Name, [Parameter(mandatory=$true)] $Value ) $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $iis = GetIISServerManager if (!$iis) { return } switch ($Name) { 'SuppressTelemetry' { if ($Value -is [Boolean]) { New-ItemProperty -Path $configKeyPath -Name $suppressTelemetryValueName -Value $Value -PropertyType DWORD -Force | Out-Null $context.DeploymentSettings[$Name] = $Value UpdateDeploymentSettingsFile $context } else { Write-Error 'This setting requires a value of type [Boolean].' } } 'LaunchResourceInBrowser' { if ($Value -is [Boolean]) { New-ItemProperty -Path $configKeyPath -Name $launchResourceInBrowserValueName -Value $Value -PropertyType DWORD -Force | Out-Null $context.DeploymentSettings[$Name] = $Value UpdateDeploymentSettingsFile $context } else { Write-Error 'This setting requires a value of type [Boolean].' } } default { Write-Error "Setting '$Name' does not exist." } } } <# .SYNOPSIS Resets a deployment setting for the Remote Desktop web client to its default value .PARAMETER Name Specifies the name of the setting .EXAMPLE Reset-RDWebClientDeploymentSetting LaunchResourceInBrowser Description ----------- Allows the user to choose how to launch resources enumerated by the client #> function Reset-RDWebClientDeploymentSetting { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(mandatory=$true)] [string] $Name ) $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $iis = GetIISServerManager if (!$iis) { return } switch ($Name) { 'SuppressTelemetry' { New-ItemProperty -Path $configKeyPath -Name $suppressTelemetryValueName -Value $DefaultValue -PropertyType DWORD -Force | Out-Null $context.DeploymentSettings[$Name] = $false UpdateDeploymentSettingsFile $context } 'LaunchResourceInBrowser' { if($context.ConfigKey.GetValue($launchResourceInBrowserValueName) -ne $null) { Remove-ItemProperty -Path $configKeyPath -Name $launchResourceInBrowserValueName } if ($context.DeploymentSettings[$Name] -ne $null) { $context.DeploymentSettings.Remove($Name) UpdateDeploymentSettingsFile $context } } default { Write-Error "Setting '$Name' does not exist." } } } <# .SYNOPSIS Gets the deployment settings for the Remote Desktop web client .PARAMETER Name Specifies the name of the setting to retrieve .EXAMPLE Get-RDWebClientDeploymentSetting Description ----------- Gets all deployment settings .EXAMPLE Get-RDWebClientDeploymentSetting SuppressTelemetry Description ----------- Gets the SuppressTelemetry deployment setting #> function Get-RDWebClientDeploymentSetting { [CmdletBinding(SupportsShouldProcess=$true)] [OutputType([System.Collections.DictionaryEntry])] Param( [string] $Name ) $context = GetModuleContext if (!(EnsureInitialized $context)) { return } $iis = GetIISServerManager if (!$iis) { return } if ($Name) { if ($context.DeploymentSettings.ContainsKey($Name)) { @{$Name = $context.DeploymentSettings[$Name]}.GetEnumerator() } else { Write-Error "Setting '$Name' does not exist." return } } else { $context.DeploymentSettings.GetEnumerator() } } # SIG # Begin signature block # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDPVinbZPfjExhc # d7nWiZwLl0yvWkIkvOZ/4Ip8HF1yrqCCDYEwggX/MIID56ADAgECAhMzAAAB32vw # LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn # s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw # PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS # yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG # 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh # EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH # tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS # 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp # TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok # t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 # b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao # mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD # Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt # VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G # CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ # Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 # oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLy/JtDnk # /Ae5TqAH2cs8qCHBMlA7Xti+oZpTb/e+t1AwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAmGEQg2EavwmIaOPYZCGzJWf2SAxvzlV0OK1euYEJf # p8HbvaNck6LiiBwdCz8cpzPlllJ0EkjCrX+nUWr2vnqmz5P05+dyg2KmY6Yx7yxX # +tRni7mfitnj38nzqUtOp2UwB4hrnOjjfn/Zt1QWcKDcVleX5zet8+Qcs07JWGdc # Uk01hvdwjdT2FwoLS5MyYe+ND/Myq44BH9U7Crh31hR1pBixGVdEOqxYiqImjzDf # RKJ4QgiAj5E6ioXIlJXUkVkcZL5w0l9T+phGutzdh2ejqVEWSEMoRAjO2F95zZtV # ARzz6rHma0uur6W7wAFEg8YQC+pbIjkALZEqK/JJLfPwoYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIKju0JdzAT/sO1Lq6sPBUZVzmtLooJdirspfZrDr # xXIvAgZgrrtzEtoYEzIwMjEwNjE2MDAzMzM5LjQ5MlowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYtD+AvMB5c1JAAAA # AAFiMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIxMDExNDE5MDIyMloXDTIyMDQxMTE5MDIyMlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJE # LUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO+GodT2ucL3Mr2DQsv2 # ELNbSvKyBpYdUKtUBWiZmFVy18pG/pucgkrc5i9tu8CY7GpWV/CQNmHG2mVeSHMJ # vbwCc/AAv7JP3bFCt6Zg75IbVSNOGA1eqLbmQiC6UAfSKXLN3dHtQ5diihb3Ymzp # NP9K0cVPZfv2MXm+ZVU0RES8cyPkXel7+UEGE+kqdiBNDdb8yBXd8sju+90+V4nz # YC+ZWW7SFJ2FFZlASpVaHpjv+eGohXlQaSBvmM4Q0xe3LhzQM8ViGz9cLeFSKgFf # SY7qizL7wUg+eqYvDUyjPX8axEQHmk0th23wWH5p0Wduws43qNIo0OQ0mRotBK71 # nykCAwEAAaOCARswggEXMB0GA1UdDgQWBBTLxEoRYEpDtzp84B5WlZN2kP4qazAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAtQa3DoXYbW/cXACbcVSFGe4gC8GXs # FxSHT3JgwFU/NdJOcbkcFTVvTp6vlmTvHm6sIjknRBB0Xi1NBTqPw20u6u/T7Cnc # /z0gT6mf9crI0VR9C+R1CtjezYKZEdZZ7fuNQWjsyftNDhQy+Rqnqryt0VoezLal # heiinHzZD/4Y4hZYPf0u8TSv1ZfKtdBweWG3QU0Lp/I9SbIoemDG97RULMcPvq2u # fhUp3OMiYQGL1WqkykSnqRJsM2IcA4l4dmoPNP6dLg5Dr7NVoYKIMInaQVZjSwDM # ZhWryvfizX0SrzyLgkMPhLMVkfLxQQSQ37NeFk7F1RfeAkNWAh6mCORBMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz # MkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAmrP6Chrbz0ax7s57n5Pop3VC8gyggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AORzl2swIhgPMjAyMTA2MTYwMTE3MzFaGA8yMDIxMDYxNzAxMTczMVowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA5HOXawIBADAKAgEAAgIcnQIB/zAHAgEAAgIRgTAK # AgUA5HTo6wIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAH7PfmF1/cmP6g7M # LnQZBuroEt3e7n8HaQfgQxB/ry9VAgX/gCjzNlNgnAu7jk8rmhCdt2qOCiWKQnao # g6u5P1ROMwl2MxSHwPzB7FabamQl0Wntrov94OEE4FVExHDeC9Ri+EACH5rPN3w3 # vmToz+mZYkmCOcxnuSVZD3XGcl+gMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAFi0P4C8wHlzUkAAAAAAWIwDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgD4jhmVp+Bb2+PT0si2xyDs/RQHQrwbNlH+t0T494v3EwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCCKqhiV+zwNDrpU7DRB7Mi57xi6GBNYsGjgZqq2 # qVMKMjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # YtD+AvMB5c1JAAAAAAFiMCIEIHeOTH0ZO+VAn/o9lcoxReV3LIWyONAsdM87qeEv # nvCFMA0GCSqGSIb3DQEBCwUABIIBAEri2YfX6ZGA6KObtFUuNs+/s/f7l1snfs41 # v1FkEtqe4PZYAiN8v5AXJVE28SqBoXy3uIG+VYv0PukLFsCHyTBr5pQvDOwzkSEE # 0MsSwmqDCKK9Po9oxMZj1AVfLVda4h80GEK5UkQrXdF5nZ8EoBEXBKwJmBDpLuWw # 5kQTq8bnYfKlFiOc7ouFwBEi8kZGddbrFy/ly/IZVe6ajZR8NxaDoMok8CKrOBVN # ZcvQRKfQyCpR53A67BbXzEdowVt5draGwnoxnt+5u3/aAZ2ybaGK1SWTkH2TtiKc # r9UqN71Jep092c94QXdleQBmU2DaaxJR1F2GrXDDQdu8F03bYY8= # SIG # End signature block |