ExportImportRdsDeployment.psm1
Function MyRdsFunctions_FixConnectionBrokerName { PARAM ( [Parameter(Mandatory = $True)] [string]$ConnectionBroker ) #Check if "localhost" is entered IF ($ConnectionBroker -eq "localhost") { $ConnectionBroker = ("{0}.{1}" -f (Get-WmiObject win32_computersystem).DNSHostName, (Get-WmiObject win32_computersystem).Domain) Write-Host -ForegroundColor Cyan ("Changing ConnectionBroker from localhost to {0}" -f $ConnectionBroker) } #Check if simple computername is entered (not FQDN) IF ($ConnectionBroker.IndexOf('.') -eq -1) { $ConnectionBroker += (".{0}" -f (Get-WmiObject win32_computersystem).Domain) Write-Host -ForegroundColor Cyan ("Changing ConnectionBroker to FQDN: {0}" -f $ConnectionBroker) } return $ConnectionBroker } Function MyRdsFunctions_FixXMLFileLocation { PARAM ( [Parameter(Mandatory = $True)] [string]$ConnectionBroker, [string]$XmlFile, [string]$FunctionName ) #Filling in default value if $XmlFile is empty. IF ($XmlFile -eq $null -OR $XmlFile -eq "") { $XmlFile = ("c:\temp\{0}_{1}.xml" -f $FunctionName, $ConnectionBroker) Write-Host -ForegroundColor Cyan ("Changing XmlFile to default value: {0}" -f $XmlFile) } return $XmlFile } Function MyRdsFunctions_CheckReboot { param ( $serverName ) $scriptResult = Invoke-Command -ComputerName $serverName -ScriptBlock { try { $computer = $env:COMPUTERNAME $invokeWmiMethodParameters = @{ Namespace = 'root/default' Class = 'StdRegProv' Name = 'EnumKey' ComputerName = $computer ErrorAction = 'Stop' } $hklm = [UInt32] "0x80000002" if ($PSBoundParameters.ContainsKey('Credential')) { $invokeWmiMethodParameters.Credential = $Credential } ## Query the Component Based Servicing Reg Key $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\') $registryComponentBasedServicing = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootPending' ## Query WUAU from the registry $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\') $registryWindowsUpdateAutoUpdate = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootRequired' ## Query JoinDomain key from the registry - These keys are present if pending a reboot from a domain join operation $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Services\Netlogon') $registryNetlogon = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames $pendingDomainJoin = ($registryNetlogon -contains 'JoinDomain') -or ($registryNetlogon -contains 'AvoidSpnSet') ## Query ComputerName and ActiveComputerName from the registry and setting the MethodName to GetMultiStringValue $invokeWmiMethodParameters.Name = 'GetMultiStringValue' $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\', 'ComputerName') $registryActiveComputerName = Invoke-WmiMethod @invokeWmiMethodParameters $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\', 'ComputerName') $registryComputerName = Invoke-WmiMethod @invokeWmiMethodParameters $pendingComputerRename = $registryActiveComputerName -ne $registryComputerName -or $pendingDomainJoin ## Query PendingFileRenameOperations from the registry if (-not $PSBoundParameters.ContainsKey('SkipPendingFileRenameOperationsCheck')) { $invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\Session Manager\', 'PendingFileRenameOperations') $registryPendingFileRenameOperations = (Invoke-WmiMethod @invokeWmiMethodParameters).sValue $registryPendingFileRenameOperationsBool = [bool]$registryPendingFileRenameOperations } ## Query ClientSDK for pending reboot status, unless SkipConfigurationManagerClientCheck is present if (-not $PSBoundParameters.ContainsKey('SkipConfigurationManagerClientCheck')) { $invokeWmiMethodParameters.NameSpace = 'ROOT\ccm\ClientSDK' $invokeWmiMethodParameters.Class = 'CCM_ClientUtilities' $invokeWmiMethodParameters.Name = 'DetermineifRebootPending' $invokeWmiMethodParameters.Remove('ArgumentList') try { $sccmClientSDK = Invoke-WmiMethod @invokeWmiMethodParameters $systemCenterConfigManager = $sccmClientSDK.ReturnValue -eq 0 -and ($sccmClientSDK.IsHardRebootPending -or $sccmClientSDK.RebootPending) } catch { $systemCenterConfigManager = $null } } $isRebootPending = $registryComponentBasedServicing -or ` $pendingComputerRename -or ` $pendingDomainJoin -or ` $registryPendingFileRenameOperationsBool -or ` $systemCenterConfigManager -or ` $registryWindowsUpdateAutoUpdate if ($PSBoundParameters.ContainsKey('Detailed')) { [PSCustomObject]@{ ComputerName = $computer ComponentBasedServicing = $registryComponentBasedServicing PendingComputerRenameDomainJoin = $pendingComputerRename PendingFileRenameOperations = $registryPendingFileRenameOperationsBool PendingFileRenameOperationsValue = $registryPendingFileRenameOperations SystemCenterConfigManager = $systemCenterConfigManager WindowsUpdateAutoUpdate = $registryWindowsUpdateAutoUpdate IsRebootPending = $isRebootPending } } else { [PSCustomObject]@{ ComputerName = $computer IsRebootPending = $isRebootPending } } } catch { Write-Warning "$Computer`: $_" } } return $scriptResult } Function MyRdsFunctions_RebootServer { param ( $serverName ) $scriptResult = Invoke-Command -ComputerName $serverName -ScriptBlock { Restart-Computer -Force } } Function Export-RDDeploymentFromConnectionBroker { <# .SYNOPSIS Export Deployment & Removes all RD Servers from a Connection Broker, except CB & Licensing role (does not uninstall a server or server role) and stores it into an XML file. .DESCRIPTION Export Deployment & Removes all RD Servers from a Connection Broker, except CB & Licensing role (does not uninstall a server or server role) and stores it into an XML file. Then you can reconnect the servers to another or updated Connection Broker. .PARAMETER ConnectionBroker Specifies FQDN of the Connection Broker. .PARAMETER XmlFile Specifies the XML File location to store the information. "c:\temp\AllRDServersFrom<<ConnectionBroker>>.xml" is the default. .PARAMETER RemoveServers Switch to specify if the servers need to be removed or not after extraction. $false is the default (for safety reasons). .INPUTS System.String. You can pipe the Connection Broker FQDN name. .OUTPUTS System.String. Returns location to the XML File. .EXAMPLE C:\PS> Export-RDDeploymentFromConnectionBroker -ConnectionBroker "MySessionBroker.mydomain.hosting" -RemoveServers .EXAMPLE C:\PS> Export-RDDeploymentFromConnectionBroker -ConnectionBroker "MySessionBroker.mydomain.hosting" -RemoveServers:$true .EXAMPLE C:\PS> Export-RDDeploymentFromConnectionBroker -ConnectionBroker "localhost" -XmlFile "c:\myshare\myDeployment.xml" -RemoveServers:$false .LINK https://blog.cloud-architect.be #> [CmdletBinding()] PARAM ( [Parameter(Mandatory = $True, ValueFromPipeline = $true)] [string]$ConnectionBroker, [string]$XmlFile = "", [switch]$RemoveServers ) # Setting ErrorActionPreference to stop script execution when error occurs $ErrorActionPreference = "Stop" #First check ConnectionBroker name $ConnectionBroker = MyRdsFunctions_FixConnectionBrokerName -ConnectionBroker $ConnectionBroker #Then check XMLFile location $XmlFile = MyRdsFunctions_FixXMLFileLocation -ConnectionBroker $ConnectionBroker -XmlFile $XmlFile -FunctionName AllRDServersFrom #Write-Test to XMLFile try { "WriteTest" | Out-File -FilePath $XmlFile -Force } catch { Write-Host -ForegroundColor Red ("Error while testing XMLFile location. Please verify access to the location '{0}'" -f $XmlFile) return $null } if ($RemoveServers) { Write-Host ("`r`n`r`n--------------") -ForegroundColor Red Write-Host ("WARNING!!") -ForegroundColor Red Write-Host ("You added the parameter '-RemoveServers'!! ") -ForegroundColor Yellow Write-Host ("Did you export your Collections (using 'Export-RDCollectionsFromConnectionBroker') before running this cmdlet? ") -ForegroundColor Yellow Write-Host ("When confirming, all Servers & the Deployment will be removed from this Connection Broker after the export!!") -ForegroundColor Yellow Write-Host ("--------------") -ForegroundColor Red do { $confirmation = Read-Host -Prompt "Continue? (Y/N)" } while ($confirmation -ne "y" -and $confirmation -ne "n") if ($confirmation -eq "n") { Write-Host -ForegroundColor Yellow "Aborting..." return } } #Next: Export Deployment Config $DeploymentInfo = [ordered]@{} $GatewayConfig = Get-RDDeploymentGatewayConfiguration -ConnectionBroker $ConnectionBroker $DeploymentInfo.Add("GatewayInfo",$GatewayConfig) $CertificateConfig = Get-RDCertificate -ConnectionBroker $ConnectionBroker $DeploymentInfo.Add("CertificateInfo",$CertificateConfig) $allServersToRemove = Get-RDServer -ConnectionBroker $ConnectionBroker $allRemovedServers = @() foreach ($serverToRemove in $allServersToRemove) { foreach ($role in $serverToRemove.Roles) { IF (($role -eq "RDS-CONNECTION-BROKER") -or ($role -eq "RDS-VIRTUALIZATION") -or ($role -eq "RDS-LICENSING")) { Write-Host -ForegroundColor Cyan ("Skipping role '{0}' on server '{1}'" -f $role, $serverToRemove.Server) continue } #Create empty HashTable $properties = [ordered]@{} Write-Host -ForegroundColor Cyan ("Exporting server '{0}' with role '{1}' from CB" -f $serverToRemove.Server, $role) $properties.Add("ServerName",$serverToRemove.Server) $properties.Add("ServerRole",$role) #Create Custom object using the properties $object = New-Object -TypeName PSObject -Prop $properties $object.PSObject.TypeNames.Insert(0,"ASPEX&CloudArchitect.RDS.Servers.Export") #Finally, add all info to the Array of collections $allRemovedServers += $object } } $DeploymentInfo.Add("ServerInfo",$allRemovedServers) #Export all Collection info to an XML so we can Import it later Write-Host -ForegroundColor Cyan ("Exporting Deployment to XMLFile '{0}' ..." -f $XmlFile) $DeploymentInfo | Export-CliXml -Path $XmlFile if ($RemoveServers) { #Removing servers from CB Write-Host -ForegroundColor Cyan ("Removing servers from deployment ...") foreach ($serverToRemove in $allServersToRemove) { foreach ($role in $serverToRemove.Roles) { IF (($role -eq "RDS-CONNECTION-BROKER") -or ($role -eq "RDS-VIRTUALIZATION") -or ($role -eq "RDS-LICENSING")) { Write-Host -ForegroundColor Cyan ("Skipping role '{0}'" -f $role) continue } Write-Host -ForegroundColor Cyan ("Removing server '{0}' with role '{1}' from CB" -f $serverToRemove.Server, $role) IF ($role -eq "RDS-GATEWAY") { Remove-RDServer -Server $serverToRemove.Server -Role $role -ConnectionBroker $ConnectionBroker -Force -ErrorAction SilentlyContinue } ELSE { Remove-RDServer -Server $serverToRemove.Server -Role $role -ConnectionBroker $ConnectionBroker -Force } } } } Write-Host -ForegroundColor Green ("All done") return $XmlFile } Function Export-RDCollectionsFromConnectionBroker { <# .SYNOPSIS Export all RDS Collections from a Connection Broker, stores it into an XML file and removes the Collections .DESCRIPTION Export all RDS Collections from a Connection Broker, stores it into an XML file and removes the Collections Then you can import it into another or updated Connection Broker. .PARAMETER ConnectionBroker Specifies FQDN of the Connection Broker. .PARAMETER XmlFile Specifies the XML File location to store the information. "c:\temp\AllCollectionsFrom_<<ConnectionBroker>>.xml" is the default. .PARAMETER RemoveCollections Switch to specify if the collections need to be removed or not after extraction. $false is the default (for safety reasons). .INPUTS System.String. You can pipe the Connection Broker FQDN name. .OUTPUTS System.String. Returns location to the XML File. .EXAMPLE C:\PS> Export-RDCollectionsFromConnectionBroker -ConnectionBroker localhost -RemoveCollections Exports all Collections from the localhost (ex: "MySessionBroker.mydomain.hosting") to "c:\temp\AllCollectionsFrom_MySessionBroker.mydomain.hosting.xml" .EXAMPLE C:\PS> Export-RDCollectionsFromConnectionBroker -ConnectionBroker localhost -RemoveCollections:$true .EXAMPLE C:\PS> Export-RDCollectionsFromConnectionBroker -ConnectionBroker "MySessionBroker.mydomain.hosting" -XmlFile "c:\myshare\mycollections.xml" -RemoveCollections:$false -Verbose Exports all Collections from "MySessionBroker.mydomain.hosting" to "c:\myshare\mycollections.xml", with full detail output .LINK https://blog.cloud-architect.be #> [CmdletBinding()] PARAM ( [Parameter(Mandatory = $True, ValueFromPipeline = $true)] [string]$ConnectionBroker, [string]$XmlFile = "", [switch]$RemoveCollections ) # Setting ErrorActionPreference to stop script execution when error occurs $ErrorActionPreference = "Stop" #First check ConnectionBroker name $ConnectionBroker = MyRdsFunctions_FixConnectionBrokerName -ConnectionBroker $ConnectionBroker #Then check XMLFile location $XmlFile = MyRdsFunctions_FixXMLFileLocation -ConnectionBroker $ConnectionBroker -XmlFile $XmlFile -FunctionName AllCollectionsFrom #Write-Test to XMLFile try { "WriteTest" | Out-File -FilePath $XmlFile -Force } catch { Write-Host -ForegroundColor Red ("Error while testing XMLFile location. Please verify access to the location '{0}'" -f $XmlFile) return $null } if ($RemoveCollections) { Write-Host ("`r`n`r`n--------------") -ForegroundColor Red Write-Host ("WARNING!!") -ForegroundColor Red Write-Host ("You added the parameter '-RemoveCollections'!! ") -ForegroundColor Yellow Write-Host ("When confirming, all Collections will be removed from this Connection Broker after the export!!") -ForegroundColor Yellow Write-Host ("--------------") -ForegroundColor Red do { $confirmation = Read-Host -Prompt "Continue? (Y/N)" } while ($confirmation -ne "y" -and $confirmation -ne "n") if ($confirmation -eq "n") { Write-Host -ForegroundColor Yellow "Aborting..." return } } #Create an Array of collections $allCollections = @() #Get all Collection on this Connection Broker $allSessionCollections = Get-RDSessionCollection -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue if ($null -eq $allSessionCollections) { Write-Host -ForegroundColor Red ("No collections found on ConnectionBroker {0} ... Exiting." -f $ConnectionBroker) return $null } Write-Host -ForegroundColor Cyan ("Found {0} collections..." -f $allSessionCollections.Count) #Loop through all Collections and collect information foreach ($sessionCollection in $allSessionCollections) { Write-Host -ForegroundColor Cyan ("Processing collection '{0}' ..." -f $sessionCollection.CollectionName) #Create empty HashTable $properties = [ordered]@{} $properties.Add("CollectionName",$sessionCollection.CollectionName) #First, some basic Collection Config Info & add it to the HastTable $properties.Add("GeneralInfo",$sessionCollection) #If CollectionType = PersonalUnmanaged, get extra info if (($null -ne $sessionCollection.CollectionType) -and ($sessionCollection.CollectionType -eq [Microsoft.RemoteDesktopServices.Management.RDSessionCollectionType]::PersonalUnmanaged)) { $personalSessionDesktopInfo = Get-RDPersonalSessionDesktopAssignment -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName $properties.Add("PersonalSessionDesktopInfo",$personalSessionDesktopInfo) } #Next, Client Info $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -Client $properties.Add("ClientInfo",$SessionCollectionInfo) #Next, Loadbalancing Info (suppress errors, because Collections without SessionsHosts returns an error $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -LoadBalancing -ErrorAction SilentlyContinue $properties.Add("LoadBalancingInfo",$SessionCollectionInfo) #Next, Security Info $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -Security $properties.Add("SecurityInfo",$SessionCollectionInfo) #Next, UserProfileDisk Info $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -UserProfileDisk $properties.Add("UserProfileDiskInfo",$SessionCollectionInfo) #Next, Connection Info $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -Connection $properties.Add("ConnectionInfo",$SessionCollectionInfo) #Next, UserGroup Info $SessionCollectionInfo = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -UserGroup $properties.Add("UserGroupInfo",$SessionCollectionInfo) #Last info: Remote Apps in this collection $allRemoteApps = Get-RDRemoteApp -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName $properties.Add("RemoteApps",$allRemoteApps) #Create Custom object using the properties $object = New-Object -TypeName PSObject -Prop $properties $object.PSObject.TypeNames.Insert(0,"ASPEX&CloudArchitect.RDS.Collection.Export") #Finally, add all info to the Array of collections $allCollections += $object } #Export all Collection info to an XML so we can Import it later Write-Host -ForegroundColor Cyan ("Exporting all collections to XMLFile '{0}' ..." -f $XmlFile) $allCollections | Export-CliXml -Path $XmlFile if ($RemoveCollections) { #Loop through all Collections and Remove collections Write-Host -ForegroundColor Cyan ("Removing all collections ...") foreach ($sessionCollection in $allSessionCollections) { Write-Host -ForegroundColor Magenta ("Removing collection '{0}' ..." -f $sessionCollection.CollectionName) Remove-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $sessionCollection.CollectionName -Force } } Write-Host -ForegroundColor Green ("All done") return $XmlFile } Function Import-RDCollectionsToConnectionBroker { <# .SYNOPSIS Imports all RDS Collections from an XML export file into a Connection Broker .DESCRIPTION Imports all RDS Collections from an XML export file into a Connection Broker XMLfile can be created using the Export-RDCollectionsFromConnectionBroker cmdlet. .PARAMETER ConnectionBroker Specifies FQDN of the Connection Broker. .PARAMETER XmlFile Specifies the XML File location containing the export information. .INPUTS None. .OUTPUTS None. .EXAMPLE C:\PS> Import-RDCollectionsToConnectionBroker -ConnectionBroker localhost -XmlFile "c:\temp\AllCollectionsFrom_MySessionBroker.mydomain.hosting.xml" Imports all Collections from "c:\temp\AllCollectionsFrom_MySessionBroker.mydomain.hosting.xml" into "MySessionBroker.mydomain.hosting" .EXAMPLE C:\PS> Import-RDCollectionsToConnectionBroker -ConnectionBroker "MySessionBroker.mydomain.hosting" -XmlFile "c:\myshare\mycollections.xml" -verbose Exports all Collections from "c:\myshare\mycollections.xml" into "MySessionBroker.mydomain.hosting", with full detail output .LINK https://blog.cloud-architect.be #> [CmdletBinding()] PARAM ( [Parameter(Mandatory = $True)] [string]$ConnectionBroker, [Parameter(Mandatory = $True)] [string]$XmlFile ) # Setting ErrorActionPreference to stop script execution when error occurs $ErrorActionPreference = "Stop" #First check ConnectionBroker name $ConnectionBroker = MyRdsFunctions_FixConnectionBrokerName -ConnectionBroker $ConnectionBroker #Then check XMLFile location if (!(Test-Path -Path $XmlFile)) { Write-Host -ForegroundColor Red ("The XML file was not found at '{0}'. Stopping function" -f $XmlFile) return } $allCollections = Import-Clixml -Path $XmlFile Write-Host -ForegroundColor Cyan ("Found {0} collections in XMLFile..." -f $allCollections.Count) foreach ($collection in $allCollections) { Write-Host -ForegroundColor Cyan ("Processing collection '{0}' ..." -f $collection.CollectionName) Write-Verbose ("CollectionType is '{0}'" -f $collection.GeneralInfo.CollectionType) if (($null -eq $sessionCollection.CollectionType) -or ($collection.GeneralInfo.CollectionType -eq [Microsoft.RemoteDesktopServices.Management.RDSessionCollectionType]::PooledUnmanaged)) { #Create collection Write-Verbose ("Creating PooledUnmanaged collection...") $collectionSessionHosts = $collection.LoadBalancingInfo | Select-Object -ExpandProperty SessionHost New-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -SessionHost $collectionSessionHosts -CollectionDescription $collection.GeneralInfo.CollectionDescription #Set Loadbalancing Write-Verbose ("Setting Loadbalancing...") $LoadBalanceObjectsArray = New-Object System.Collections.Generic.List[Microsoft.RemoteDesktopServices.Management.RDSessionHostCollectionLoadBalancingInstance] foreach ($lbInfo in $collection.LoadBalancingInfo) { $LoadBalanceSessionHost = New-Object Microsoft.RemoteDesktopServices.Management.RDSessionHostCollectionLoadBalancingInstance( $lbInfo.CollectionName, $lbInfo.RelativeWeight, $lbInfo.SessionLimit, $lbInfo.SessionHost ) $LoadBalanceObjectsArray.Add($LoadBalanceSessionHost) } Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -LoadBalancing $LoadBalanceObjectsArray #Set RemoteApps Write-Verbose ("Publishing RemoteApps...") Write-Verbose ("Found {0} RemoteApps in Export" -f $collection.RemoteApps.Count) foreach ($remoteApp in $collection.RemoteApps) { New-RDRemoteApp -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -Alias $remoteApp.Alias -DisplayName $remoteApp.DisplayName -FilePath $remoteApp.FilePath ` -FileVirtualPath $remoteApp.FileVirtualPath -ShowInWebAccess $remoteApp.ShowInWebAccess -FolderName $remoteApp.FolderName -CommandLineSetting $remoteApp.CommandLineSetting ` -RequiredCommandLine $remoteApp.RequiredCommandLine -UserGroups $remoteApp.UserGroups -IconPath $remoteApp.IconPath -IconIndex $remoteApp.IconIndex } } else { #Create collection Write-Verbose ("Creating PersonalUnmanaged collection...") $collectionSessionHosts = $collection.LoadBalancingInfo | Select-Object -ExpandProperty SessionHost if (($collection.GeneralInfo.GrantAdministrativePrivilege) -and ($collection.GeneralInfo.AutoAssignPersonalDesktop)) { New-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -SessionHost $collectionSessionHosts -PersonalUnmanaged -GrantAdministrativePrivilege -AutoAssignUser } elseif ($collection.GeneralInfo.GrantAdministrativePrivilege) { New-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -SessionHost $collectionSessionHosts -PersonalUnmanaged -GrantAdministrativePrivilege } elseif ($collection.GeneralInfo.AutoAssignPersonalDesktop) { New-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -SessionHost $collectionSessionHosts -PersonalUnmanaged -AutoAssignUser } else { New-RDSessionCollection -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -SessionHost $collectionSessionHosts -PersonalUnmanaged } #Setting Desktop Assignment Write-Verbose ("Setting Desktop Assignment...") foreach ($assignment in $collection.PersonalSessionDesktopInfo) { Set-RDPersonalSessionDesktopAssignment -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -User $assignment.User -Name $assignment.DesktopName } } #Set UPD Write-Verbose ("Setting UPD...") if ($collection.UserProfileDiskInfo.EnableUserProfileDisk) { Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -EnableUserProfileDisk ` -MaxUserProfileDiskSizeGB $collection.UserProfileDiskInfo.MaxUserProfileDiskSizeGB -DiskPath $collection.UserProfileDiskInfo.DiskPath ` -IncludeFolderPath $collection.UserProfileDiskInfo.IncludeFolderPath -IncludeFilePath $collection.UserProfileDiskInfo.IncludeFilePath ` -ExcludeFolderPath $collection.UserProfileDiskInfo.ExcludeFolderPath -ExcludeFilePath $collection.UserProfileDiskInfo.ExcludeFilePath } #Set ClientInfo Write-Verbose ("Setting ClientInfo...") Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -ClientDeviceRedirectionOptions $collection.ClientInfo.ClientDeviceRedirectionOptions ` -MaxRedirectedMonitors $collection.ClientInfo.MaxRedirectedMonitors -ClientPrinterRedirected $collection.ClientInfo.ClientPrinterRedirected ` -ClientPrinterAsDefault $collection.ClientInfo.ClientPrinterAsDefault -RDEasyPrintDriverEnabled $collection.ClientInfo.RDEasyPrintDriverEnabled #Set SecurityInfo Write-Verbose ("Setting SecurityInfo...") Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -AuthenticateUsingNLA $collection.SecurityInfo.AuthenticateUsingNLA ` -EncryptionLevel $collection.SecurityInfo.EncryptionLevel -SecurityLayer $collection.SecurityInfo.SecurityLayer #Set ConnectionInfo Write-Verbose ("Setting ConnectionInfo...") Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -DisconnectedSessionLimitMin $collection.ConnectionInfo.DisconnectedSessionLimitMin ` -BrokenConnectionAction $collection.ConnectionInfo.BrokenConnectionAction -TemporaryFoldersDeletedOnExit $collection.ConnectionInfo.TemporaryFoldersDeletedOnExit ` -AutomaticReconnectionEnabled $collection.ConnectionInfo.AutomaticReconnectionEnabled -ActiveSessionLimitMin $collection.ConnectionInfo.ActiveSessionLimitMin ` -IdleSessionLimitMin $collection.ConnectionInfo.IdleSessionLimitMin #Set UserGroupInfo Write-Verbose ("Setting UserGroupInfo...") Set-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker -CollectionName $collection.CollectionName -UserGroup $collection.UserGroupInfo.UserGroup Write-Host -ForegroundColor Cyan ("Done.") } Write-Host -ForegroundColor Green ("All done") } Function Import-RDDeploymentToConnectionBroker { <# .SYNOPSIS Imports RDS Deployment from an XML export file into a Connection Broker .DESCRIPTION Imports RDS Deployment from an XML export file into a Connection Broker XMLfile must be created using the Export-RDDeploymentFromConnectionBroker cmdlet. .PARAMETER ConnectionBroker Specifies FQDN of the Connection Broker. .PARAMETER XmlFile Specifies the XML File location containing the export information. .PARAMETER RDGatewayCertPath Specifies the Certificate File location for the RDGateway role. .PARAMETER RDGatewayCertPassword Specifies the password for the certificate for the RDGateway role. .PARAMETER RDWebAccessCertPath Specifies the Certificate File location for the RDWebAccess role. .PARAMETER RDWebAccessCertPassword Specifies the password for the certificate for the RDWebAccess role. .PARAMETER RDRedirectorCertPath Specifies the Certificate File location for the RDRedirector role (server authentication). .PARAMETER RDRedirectorCertPassword Specifies the password for the certificate for the RDRedirector role. .PARAMETER RDPublishingCertPath Specifies the Certificate File location for the RDPublishing role (Signing RDP Files). .PARAMETER RDPublishingCertPassword Specifies the password for the certificate for the RDPublishing role. .INPUTS None. .OUTPUTS None. .EXAMPLE C:\PS> $RDGatewayCertPath = "C:\Temp\*************.pfx" C:\PS> $RDWebAccessCertPath = "C:\Temp\*************.pfx" C:\PS> $RDRedirectorCertPath = "C:\Temp\*************.pfx" C:\PS> $RDPublishingCertPath = "C:\Temp\*************.pfx" C:\PS> $RDGatewayCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDWebAccessCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDRedirectorCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDPublishingCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> Import-RDDeploymentToConnectionBroker -ConnectionBroker localhost -XmlFile "C:\Temp\AllRDServersFrom_MySessionBroker.mydomain.hosting.xml" -RDGatewayCertPath $RDGatewayCertPath -RDGatewayCertPassword $RDGatewayCertPassword -RDWebAccessCertPath $RDWebAccessCertPath -RDWebAccessCertPassword $RDWebAccessCertPassword -RDRedirectorCertPath $RDRedirectorCertPath -RDRedirectorCertPassword $RDRedirectorCertPassword -RDPublishingCertPath $RDPublishingCertPath -RDPublishingCertPassword $RDPublishingCertPassword -Verbose Imports Deployment from "c:\temp\AllCollectionsFrom_MySessionBroker.mydomain.hosting.xml" into "MySessionBroker.mydomain.hosting", using the supplied certificates .EXAMPLE C:\PS> $RDGatewayCertPath = "C:\Temp\*************.pfx" C:\PS> $RDWebAccessCertPath = "C:\Temp\*************.pfx" C:\PS> $RDRedirectorCertPath = "C:\Temp\*************.pfx" C:\PS> $RDPublishingCertPath = "C:\Temp\*************.pfx" C:\PS> $RDGatewayCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDWebAccessCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDRedirectorCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> $RDPublishingCertPassword = ConvertTo-SecureString -String "*************" -AsPlainText -Force C:\PS> Import-RDDeploymentToConnectionBroker -ConnectionBroker localhost -XmlFile "C:\Temp\AllRDServersFrom_MySessionBroker.mydomain.hosting.xml" -RDGatewayCertPath $RDGatewayCertPath -RDGatewayCertPassword $RDGatewayCertPassword -RDWebAccessCertPath $RDWebAccessCertPath -RDWebAccessCertPassword $RDWebAccessCertPassword -RDRedirectorCertPath $RDRedirectorCertPath -RDRedirectorCertPassword $RDRedirectorCertPassword -RDPublishingCertPath $RDPublishingCertPath -RDPublishingCertPassword $RDPublishingCertPassword -Verbose Imports Deployment from "c:\myshare\mycollections.xml" into "MySessionBroker.mydomain.hosting", using the supplied certificates, with full detail output .LINK https://blog.cloud-architect.be #> [CmdletBinding()] PARAM ( [Parameter(Mandatory = $True)] [string]$ConnectionBroker, [Parameter(Mandatory = $True)] [string]$XmlFile, [Parameter(Mandatory = $True)] [string]$RDGatewayCertPath, [Parameter(Mandatory = $True)] [SecureString]$RDGatewayCertPassword, [Parameter(Mandatory = $True)] [string]$RDWebAccessCertPath, [Parameter(Mandatory = $True)] [SecureString]$RDWebAccessCertPassword, [Parameter(Mandatory = $True)] [string]$RDRedirectorCertPath, [Parameter(Mandatory = $True)] [SecureString]$RDRedirectorCertPassword, [Parameter(Mandatory = $True)] [string]$RDPublishingCertPath, [Parameter(Mandatory = $True)] [SecureString]$RDPublishingCertPassword ) # Setting ErrorActionPreference to stop script execution when error occurs $ErrorActionPreference = "Stop" #First check ConnectionBroker name $ConnectionBroker = MyRdsFunctions_FixConnectionBrokerName -ConnectionBroker $ConnectionBroker #Then check XMLFile location if (!(Test-Path -Path $XmlFile)) { Write-Host -ForegroundColor Red ("The XML file was not found at '{0}'. Stopping function" -f $XmlFile) return } if ($null -ne (Get-RDSessionCollection -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue)) { Write-Host -ForegroundColor Red ("A deployment already exists! You should start with a new Connection Broker & License Server`r`nStopping Function") return } $deploymentInfo = Import-Clixml -Path $XmlFile Write-Host -ForegroundColor Cyan ("Found {0} RD Server Roles in XMLFile..." -f $deploymentInfo.ServerInfo.Count) #Check for pending reboot Write-Host -ForegroundColor Cyan ("Checking pending reboots on servers ...") $displayWarning = $false $pendingRebootResults = @() foreach ($server in $deploymentInfo.ServerInfo) { $properties = [ordered]@{} $properties.Add("ServerName",$server.ServerName) $ConnectionAvailable = $true $IsRebootPending = $true $result = MyRdsFunctions_CheckReboot -serverName $server.ServerName if ($null -eq $result) { $ConnectionAvailable = $false $displayWarning = $true } else { if ($result.IsRebootPending -eq $false) { $IsRebootPending = $false } else { $displayWarning = $true } } $properties.Add("ConnectionAvailable",$ConnectionAvailable) $properties.Add("IsRebootPending",$IsRebootPending) #Create Custom object using the properties $object = New-Object -TypeName PSObject -Prop $properties $object.PSObject.TypeNames.Insert(0,"ASPEX&CloudArchitect.RDS.Deployment.RebootPending") $pendingRebootResults += $object #$pendingRebootResults += ("{0}`t`t{1}`t`t{2}" -f $server.ServerName, $ConnectionAvailable, $IsRebootPending) } #Testing broker server $IsRebootPending = $true $result = MyRdsFunctions_CheckReboot -serverName $ConnectionBroker if ($result.IsRebootPending -eq $false) { $IsRebootPending = $false } else { $displayWarning = $true } #Create Custom object using the properties $properties = [ordered]@{} $properties.Add("ServerName",$ConnectionBroker) $properties.Add("ConnectionAvailable",$true) $properties.Add("IsRebootPending",$IsRebootPending) $object = New-Object -TypeName PSObject -Prop $properties $object.PSObject.TypeNames.Insert(0,"ASPEX&CloudArchitect.RDS.Deployment.RebootPending") $pendingRebootResults += $object #$pendingRebootResults += ("{0}`t`t{1}`t`t{2}" -f $ConnectionBroker, $true, $IsRebootPending) Write-Host -ForegroundColor Yellow "RebootPending overview" Write-Host -ForegroundColor Yellow ($pendingRebootResults | Format-Table | Out-String) if ($displayWarning) { Write-Host ("`r`n`r`n--------------") -ForegroundColor Red Write-Host ("WARNING!!") -ForegroundColor Red Write-Host ("Some servers have a reboot pending (or unavailable to query for Reboot Pendings)") -ForegroundColor Yellow Write-Host ("If you continue, the import might fail!") -ForegroundColor Yellow Write-Host ("Do you want to reboot servers (Y/N)? Or Continue without reboot (C): not recommended [press Y/N/C]") -ForegroundColor Yellow Write-Host ("--------------") -ForegroundColor Red do { $confirmation = Read-Host -Prompt "Reboot servers (Y/N)? Or Continue without reboot (C): not recommended [press Y/N/C]" } while ($confirmation -ne "y" -and $confirmation -ne "n" -and $confirmation -ne "c") if ($confirmation -eq "c") { Write-Host -ForegroundColor Yellow "Continuing without reboots..." } if ($confirmation -eq "n") { Write-Host -ForegroundColor Yellow "Aborting..." return } if ($confirmation -eq "y") { foreach ($server in ($pendingRebootResults | Where-Object {$_.IsRebootPending -eq $true})) { MyRdsFunctions_RebootServer -serverName $server.ServerName } Write-Host -ForegroundColor Yellow "Wait until the server(s) is (are) rebooted and restart the import." return } } #start with new RD Deployment Write-Verbose ("Starting with new RD Deployment") $deploymentSessionHosts = $deploymentInfo.ServerInfo | Where-Object {$_.ServerRole -eq "RDS-RD-SERVER"} | Select-Object -ExpandProperty ServerName New-RDSessionDeployment -ConnectionBroker $ConnectionBroker -SessionHost $deploymentSessionHosts #Add Gateway servers to deployment Write-Verbose ("Add Gateway servers") $DeploymentGatewayServers = $deploymentInfo.ServerInfo | Where-Object {$_.ServerRole -eq "RDS-GATEWAY"} | Select-Object -ExpandProperty ServerName foreach ($gateway in $DeploymentGatewayServers) { Add-RDServer -ConnectionBroker $ConnectionBroker -Server $gateway -Role "RDS-GATEWAY" -GatewayExternalFqdn $deploymentInfo.GatewayInfo.GatewayExternalFqdn } #Set Gateway settings Write-Verbose ("Set Gateway settings") Set-RDDeploymentGatewayConfiguration -ConnectionBroker $ConnectionBroker -GatewayMode $deploymentInfo.GatewayInfo.GatewayMode ` -LogonMethod $deploymentInfo.GatewayInfo.LogonMethod -UseCachedCredentials $deploymentInfo.GatewayInfo.UseCachedCredentials ` -BypassLocal $deploymentInfo.GatewayInfo.BypassLocal -GatewayExternalFqdn $deploymentInfo.GatewayInfo.GatewayExternalFqdn -Force #Add Web Access servers to deployment Write-Verbose ("Add Web Access servers") $DeploymentWebAccessServers = $deploymentInfo.ServerInfo | Where-Object {$_.ServerRole -eq "RDS-WEB-ACCESS"} | Select-Object -ExpandProperty ServerName foreach ($waServer in $DeploymentWebAccessServers) { Add-RDServer -ConnectionBroker $ConnectionBroker -Server $waServer -Role "RDS-WEB-ACCESS" } #Set Certificate info Write-Verbose ("Set Certificate info") Set-RDCertificate -ConnectionBroker $ConnectionBroker -Role RDGateway -Password $RDGatewayCertPassword -ImportPath $RDGatewayCertPath -Force Set-RDCertificate -ConnectionBroker $ConnectionBroker -Role RDWebAccess -Password $RDWebAccessCertPassword -ImportPath $RDWebAccessCertPath -Force Set-RDCertificate -ConnectionBroker $ConnectionBroker -Role RDPublishing -Password $RDPublishingCertPassword -ImportPath $RDPublishingCertPath -Force Set-RDCertificate -ConnectionBroker $ConnectionBroker -Role RDRedirector -Password $RDRedirectorCertPassword -ImportPath $RDRedirectorCertPath -Force Write-Host -ForegroundColor Green ("All done") } # Setting ErrorActionPreference to stop script execution when error occurs $ErrorActionPreference = "Stop" Write-Host -ForegroundColor Yellow ("`r`n`r`nExportImportRdsDeployment Module loaded") Write-Host -ForegroundColor Cyan ("Created by Micha Wets, visit www.cloud-architect.be for more info`r`n") Write-Host -ForegroundColor Green ("All set...`r`n`r`n") # SIG # Begin signature block # MIIcUgYJKoZIhvcNAQcCoIIcQzCCHD8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUe3D0+ZtnoLRNxLGJokzqTGXJ # D8OggheBMIIFCjCCA/KgAwIBAgIQClzi1HoMqW5Ja08vqvhtZDANBgkqhkiG9w0B # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz # c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTE3MTIwNDAwMDAwMFoXDTIxMDEz # MTEyMDAwMFowRzELMAkGA1UEBhMCQkUxEjAQBgNVBAcTCUFudHdlcnBlbjERMA8G # A1UEChMIQXNwZXggU0ExETAPBgNVBAMTCEFzcGV4IFNBMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEA1XyMo+aVWPbPwkWojG8YQjb46JVm/nUTS6PV+0hu # xmEKfdZ3pQqcQMb/2XG6QePgGsViAM8ahCzj9CvnnKvIEkdTTfnnydPy4iLRwq2/ # 66SeZBNBBHd/UVtX7vPZ38oQ5JgLDa6RSCamfA1W5UmVn8gDmN/eWIAXeTx6XnqJ # QW4vsyvZZb+gW3SwTQs02I1v/mBz3TjGpbG0zVwV62pbnvAqSKdiVfAezrye0R6L # +iEsjn8rnzOC+Y5jLZA9fZpiCkyPs7zFGgioKsf3w23UN4swzAvB/m1Gllfm8g1b # RiVqYwUCz6L1WN4ZpVgB3J1NKgts925A7Sf3dfqUipM0zwIDAQABo4IBxTCCAcEw # HwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYEFK51Vq/R # xW6y0cvL1KiEsUYz3aItMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEF # BQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2VydC5jb20v # c2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQuZGlnaWNl # cnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3BglghkgB # hv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ # UzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYYaHR0cDov # L29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2FjZXJ0cy5k # aWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25pbmdDQS5j # cnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAlYvDJ3RpjntEqE+K # Pp/xmS2u4cjUwuA6ptD/FXrhp09ebbgCk6dvBG2N/jkWWTRTgn8DaRbdpiJk1tLW # ffHaag0WfqNX+b5xZcTj4Id2OO9gsPDLei79Iz/HHb/6s7os6X7D9Wy6mCFPxvlN # dq8gTWy43ikrN0skBbzjGPuT1zL8vzYEIrbeRFxKz4+tShRC8JdX6+3S+v4YBvrP # C/L1RhwVBHnTUbOkHT2kEPs9VXGvKvFmlzdF4/8lnpQf7j4BeCAebPdnDc/dwhL6 # fZAyfJUdT4OJoN6NGL5JpXTp567hV2hkgMN8TbzZjtRdlbbwAr4d32zJ5xdOyb81 # cblhUjCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQEL # BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ # RCBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENv # ZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjT # sxx/DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3 # Gde2qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEo # JrskacLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCi # q85/6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xn # TrXE94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAP # ZHM8np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAw # DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEB # BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG # AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1 # cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5k # aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu # Y3JsME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRw # czovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRa # xLl7KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd # 823IDzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE # 7zBh134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4T # S63XX0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwu # kaPAJRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2 # iiQC/i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/G # kxAG/AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBmowggVS # oAMCAQICEAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0 # MTAyMjAwMDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNV # BAoTCERpZ2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9u # ZGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2 # xirmSXU7zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnU # h3SIRDsZyeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUO # plsU/UUjjoZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQv # MXeBMB+7NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg # 27xzdPpO2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76Tw # iRGPdwIDAQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAw # FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCG # SAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv # bS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYA # IAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkA # dAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAA # RABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAA # UgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAA # dwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4A # ZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkA # bgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1Ud # IwQYMBaAFBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoq # eRqDS/QeicHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2Vy # dC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2Ny # bDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYB # BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w # QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dEFzc3VyZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFc # m4bOIyAPgIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7Gd # JH7+IKRXrVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9 # OB0obzpSCfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVd # kkHa+Q+B0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXa # rsQuWaBh3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqv # ao173NhEMIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEw # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKe # VlRcqcTSQQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBx # MevPOkAMRk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7 # os1vcyP+rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwr # mdDGXRJCgeGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c # 9qjxL6/siSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJ # AgMBAAGjggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUH # AwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYD # VR0gBIIByTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0 # cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQG # CCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMA # IABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMA # IABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMA # ZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkA # bgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgA # IABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUA # IABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAA # cgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB # /wIBADB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig # NoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v # dENBLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # QXNzdXJlZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStn # As0wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEF # BQADggEBAEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKc # lcNQ5IK5I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuL # LZDCwNK1lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1f # wksW5EtwTACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFp # jE/7WeAjD9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNH # lXdx3LEbS0scEJx3FMGdTy9alQgpECYxggQ7MIIENwIBATCBhjByMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT # aWduaW5nIENBAhAKXOLUegypbklrTy+q+G1kMAkGBSsOAwIaBQCgeDAYBgorBgEE # AYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG # CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBT3tSor # uMEs491CMg5B92At1/zm3DANBgkqhkiG9w0BAQEFAASCAQAZIhNpj6KAXWgusGE8 # UB4dMIFnkeS3U6R0EHSO9r1+FdK/HNfuEnVW6RD+mfJYND8m5vvv6YtSjgzkHIBI # gj03VzxSfgT4PnshwFccm5L1tyS/hJkFJgbeJxq060hUPBuYcC0YoNhz94lzJX3u # gXnfKFjxnDASBpofnIRSZI9V8QKPiT+P88BdaFq75aJDa46yJiGvUrmhvJQdOFBT # +YY8xCfokVcI7JHy5wcotYHeHXF+2+O9ylODLbb2wRoAlz3SV5PJTi2lq+fZ8ZIJ # Y0EU258ZaX+q/igcpg/bQ0jGIO9R+G16eNAPkva2ysSYjBZi3XCOh+SdkSXZ4ja2 # 3oRKoYICDzCCAgsGCSqGSIb3DQEJBjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9Y # sWvW1ermF/BmMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB # MBwGCSqGSIb3DQEJBTEPFw0yMDAxMDkyMDAyMzFaMCMGCSqGSIb3DQEJBDEWBBST # B0k+yYKyX903BduifWmikklgwTANBgkqhkiG9w0BAQEFAASCAQByo8lHgpPOLQud # BEla3HDX2EAVad9BwmsQZnMEWkGXFf0K2nUXSj9yqB9z1YPZ1xp9tSB/9VRyeuNZ # v2fJc8kfWGx5vE3PkowwZHvpMIcNevPrFrZMM05yVottRFgUANcQacmc02KtpT/j # YCU/k4pHVv4MF4rWlEH673rJjbGv+HLPqGqILI0g021l1rsq3NyBmVd7RoED9Ugs # 4HJ+emPk043TaUN62BTnolPIrcYEtb9lOwQUoiGMkFRuJxLZpdwgv9XzSbsrv9x9 # 5OCeP6cRlMxREz8b89A7gAekj/DTEJhzcenjQiVYu8UYSOJoAc7BqkO1TpAKESU2 # 2Ge9Y9jU # SIG # End signature block |