DSCResources/VE_XD7Common/VE_XD7Common.psm1
Import-LocalizedData -BindingVariable localized -FileName VE_XD7Common.Resources.psd1; #region Private Functions function AddInvokeScriptBlockCredentials { <# .SYNOPSIS Adds the required Invoke-Command parameters for loopback processing with CredSSP. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory)] [System.Collections.Hashtable] $Hashtable, [Parameter(Mandatory)] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential ) process { $Hashtable['ComputerName'] = $env:COMPUTERNAME; $Hashtable['Credential'] = $Credential; $Hashtable['Authentication'] = 'Credssp'; } } #end function AddInvokeScriptBlockCredentials function GetHostname { [CmdletBinding()] [OutputType([System.String])] param ( ) process { $globalIpProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties(); if ($globalIpProperties.DomainName) { return '{0}.{1}' -f $globalIpProperties.HostName, $globalIpProperties.DomainName; } else { return $globalIpProperties.HostName; } } #end process } #end function GetHostname function GetRegistryValue { <# .SYNOPSIS Returns a registry string value. .NOTES This is an internal function and shouldn't be called from outside. This function enables registry calls to be unit tested. #> [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType([System.String])] param ( # Registry key name/path to query. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Alias('Path')] [System.String] $Key, # Registry value to return. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name ) process { $itemProperty = Get-ItemProperty -Path $Key -Name $Name -ErrorAction SilentlyContinue; if ($itemProperty.$Name) { return $itemProperty.$Name; } return ''; } } #end function GetRegistryValue function StartWaitProcess { <# .SYNOPSIS Starts and waits for a process to exit. .NOTES This is an internal function and shouldn't be called from outside. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Int32])] param ( # Path to process to start. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $FilePath, # Arguments (if any) to apply to the process. [Parameter()] [AllowNull()] [System.String[]] $ArgumentList, # Credential to start the process as. [Parameter()] [AllowNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, # Working directory [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $WorkingDirectory = (Split-Path -Path $FilePath -Parent) ) process { $startProcessParams = @{ FilePath = $FilePath; WorkingDirectory = $WorkingDirectory; NoNewWindow = $true; PassThru = $true; }; $displayParams = '<None>'; if ($ArgumentList) { $displayParams = [System.String]::Join(' ', $ArgumentList); $startProcessParams['ArgumentList'] = $ArgumentList; } Write-Verbose ($localized.StartingProcess -f $FilePath, $displayParams); if ($Credential) { Write-Verbose ($localized.StartingProcessAs -f $Credential.UserName); $startProcessParams['Credential'] = $Credential; } if ($PSCmdlet.ShouldProcess($FilePath, 'Start Process')) { $process = Start-Process @startProcessParams -ErrorAction Stop; } if ($PSCmdlet.ShouldProcess($FilePath, 'Wait Process')) { Write-Verbose ($localized.ProcessLaunched -f $process.Id); Write-Verbose ($localized.WaitingForProcessToExit -f $process.Id); $process.WaitForExit(); $exitCode = [System.Convert]::ToInt32($process.ExitCode); Write-Verbose ($localized.ProcessExited -f $process.Id, $exitCode); } return $exitCode; } #end process } #end function StartWaitProcess function FindXDModule { <# .SYNOPSIS Locates a module's manifest (.psd1) file. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Name = 'Citrix.XenDesktop.Admin', [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Path = 'C:\Program Files\Citrix\XenDesktopPoshSdk\Module\Citrix.XenDesktop.Admin.V1' ) process { $module = Get-ChildItem -Path $Path -Include "$Name.psd1" -File -Recurse; if (-not $module) { # If we have no .psd1 file, search for a .psm1 (for StoreFront) $module = Get-ChildItem -Path $Path -Include "$Name.psm1" -File -Recurse; } return $module.FullName; } #end process } #end function FindModule function TestXDModule { <# .SYNOPSIS Tests whether Powershell modules or Snapin are available/registered. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Name = 'Citrix.XenDesktop.Admin', [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Path = 'C:\Program Files\Citrix\XenDesktopPoshSdk\Module\Citrix.XenDesktop.Admin.V1', [Parameter()] [System.Management.Automation.SwitchParameter] $IsSnapin ) process { if ($IsSnapin) { if (Get-PSSnapin -Name $Name -Registered -ErrorAction SilentlyContinue) { return $true; } } ## If testing a snap-in and it fails, try resolving it as a module (#18) if (FindXDModule -Name $Name -Path $Path) { return $true; } return $false; } #end process } #end TestModule function AssertXDModule { <# .SYNOPSIS Asserts whether all the specified modules are present, throwing if not. #> [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $Name, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Path = 'C:\Program Files\Citrix\XenDesktopPoshSdk\Module\Citrix.XenDesktop.Admin.V1', [Parameter()] [System.Management.Automation.SwitchParameter] $IsSnapin ) process { foreach ($moduleName in $Name) { if (-not (TestXDModule -Name $moduleName -Path $Path -IsSnapin:$IsSnapin)) { ThrowInvalidProgramException -ErrorId $moduleName -ErrorMessage $localized.XenDesktopSDKNotFoundError; } } #end foreach module } #end process } #end function AssertXDModule function AssertModule { <# .SYNOPSIS Asserts whether all the specified modules are present, throwing if not. #> [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $Name ) process { foreach ($moduleName in $Name) { if (-not (Get-Module -Name $moduleName -ListAvailable -ErrorAction SilentlyContinue)) { ThrowInvalidProgramException -ErrorId $moduleName -ErrorMessage $localized.XenDesktopModuleNotFoundError; } } #end foreach module } #end process } #end function AssertModule function Add-PSSnapin { <# .SYNOPSIS Proxy function to load Citrix PowerShell snapins within a module at the global scope. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [System.String[]] $Name ) process { foreach ($snapinName in $Name) { $modulePath = Join-Path -Path $PSScriptRoot -ChildPath "$snapinName.psm1"; Import-Module -Name $modulePath -Global -Verbose:$false; } #end foreach snapin } #end process } #end function Add-PSSnapin function GetXDBrokerMachine { <# .SYNOPSIS Searches for a registered Citrix XenDesktop machine by name. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [System.String] $MachineName ) process { if ($MachineName.Contains('.')) { ## Attempt to locate the machine by FQDN $brokerMachine = Get-BrokerMachine -DNSName $MachineName -ErrorAction SilentlyContinue; } elseif ($MachineName.Contains('\')) { ## Otherwise attempt to locate the machine by DomainName\NetBIOSName $brokerMachine = Get-BrokerMachine -MachineName $MachineName -ErrorAction SilentlyContinue; } else { ## Failing all else, perform a wildcard search $brokerMachine = Get-BrokerMachine -MachineName "*\$MachineName" -ErrorAction SilentlyContinue; } if ($null -eq $brokerMachine) { Write-Error -ErrorId 'MachineNotFound' -Message ($localized.MachineNotFoundError -f $Machine); return; } elseif (($brokerMachine).Count -gt 1) { Write-Error -ErrorId 'AmbiguousMachineReference' -Message ($localized.AmbiguousMachineReferenceError -f $MachineName); return; } else { return $brokerMachine; } } #end process } #end function GetXDBrokerMachine function TestXDMachineIsExistingMember { <# .SYNOPSIS Tests whether a machine is an existing member of a list of FQDN machine members. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory)] [System.String] $MachineName, [Parameter()] [System.String[]] $ExistingMembers ) process { if ((-not $MachineName.Contains('\')) -and (-not $MachineName.Contains('.'))) { Write-Warning -Message ($localized.MachineNameNotFullyQualifiedWarning -f $MachineName); $netBIOSName = $MachineName; } elseif ($MachineName.Contains('\')) { $netBIOSName = $MachineName.Split('\')[1]; } if ($ExistingMembers -contains $MachineName) { return $true; } elseif ($ExistingMembers -like '{0}.*' -f $netBIOSName) { return $true; } else { return $false; } } #end process } #end function TestXDMachine function TestXDMachineMembership { <# .SYNOPSIS Provides a centralised function to test whether machine membership of a Machine Catalog or Delivery Group are correct - evaluating FQDNs, DOMAINNAME\NETBIOS and NETBIOS name formats. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory)] [System.String[]] $RequiredMembers, [Parameter(Mandatory)] [ValidateSet('Present','Absent')] [System.String] $Ensure, [Parameter()] [System.String[]] $ExistingMembers ) process { $isInCompliance = $true; foreach ($member in $RequiredMembers) { if (TestXDMachineIsExistingMember -MachineName $member -ExistingMembers $ExistingMembers) { if ($Ensure -eq 'Absent') { Write-Verbose ($localized.SurplusMachineReference -f $member); $isInCompliance = $false; } } else { if ($Ensure -eq 'Present') { Write-Verbose ($localized.MissingMachineReference -f $member); $isInCompliance = $false; } } } #end foreach member return $isInCompliance; } #end process } #end function TestXDMachineMembers function ResolveXDBrokerMachine { <# .SYNOPSIS Returns a machine machine from an existing collection of Citrix XenDesktop machines assigned to a Machine Catalog or Delivery Group #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory)] [System.String] $MachineName, [Parameter(Mandatory)] [AllowNull()] [System.Object[]] $BrokerMachines ) process { foreach ($machine in $brokerMachines) { ## Try matching on DNS name if (($machine.DNSName -eq $MachineName) -or ($machine.MachineName -eq $MachineName)) { return $machine; } elseif ((-not $MachineName.Contains('\')) -and ($machine.MachineName -match '^\S+\\{0}$' -f $MachineName)) { ## Try matching based on DOMAIN\NETBIOS name return $machine } } #end foreach machine return $null; } #end process } #end function ResolveXDBrokerMachine function ThrowInvalidOperationException { <# .SYNOPSIS Throws terminating error of category NotInstalled with specified errorId and errorMessage. #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.String] $ErrorId, [Parameter(Mandatory)] [System.String] $ErrorMessage ) process { $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument; $exception = New-Object -TypeName 'System.InvalidOperationException' -ArgumentList $ErrorMessage; $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList $exception, $ErrorId, $errorCategory, $null; throw $errorRecord; } #end process } #end function ThrowInvalidOperationException function ThrowInvalidProgramException { <# .SYNOPSIS Throws terminating error of category NotInstalled with specified errorId and errorMessage. #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.String] $ErrorId, [Parameter(Mandatory)] [System.String] $ErrorMessage ) process { $errorCategory = [System.Management.Automation.ErrorCategory]::NotInstalled; $exception = New-Object -TypeName 'System.InvalidProgramException' -ArgumentList $ErrorMessage; $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList $exception, $ErrorId, $errorCategory, $null; throw $errorRecord; } #end process } #end function ThrowInvalidProgramException function ThrowOperationCanceledException { <# .SYNOPSIS Throws terminating error of category InvalidOperation with specified errorId and errorMessage. #> param( [Parameter(Mandatory)] [System.String] $ErrorId, [Parameter(Mandatory)] [System.String] $ErrorMessage ) process { $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation; $exception = New-Object -TypeName 'System.OperationCanceledException' -ArgumentList $ErrorMessage; $errorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList $exception, $ErrorId, $errorCategory, $null; throw $errorRecord; } #end process } #end function ThrowOperationCanceledException function TestXDInstalledRole { <# .SYNOPSIS Tests whether a Citrix XenDesktop 7.x role is installed. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## Citrix XenDesktop 7.x role to query. [Parameter(Mandatory)] [ValidateSet('Controller','Studio','Storefront','Licensing','Director','DesktopVDA','SessionVDA')] [System.String[]] $Role ) process { $installedRoles = GetXDInstalledRole -Role $Role; foreach ($r in $Role) { if ($installedRoles -notcontains $r) { return $false; } } return $true; } #end process } #end function TestXDRole function GetXDInstalledRole { <# .SYNOPSIS Returns installed Citrix XenDesktop 7.x installed products. #> [CmdletBinding()] [OutputType([System.String[]])] param ( ## Citrix XenDesktop 7.x role to query. [Parameter(Mandatory)] [ValidateSet('Controller','Studio','Storefront','Licensing','Director','DesktopVDA','SessionVDA')] [System.String[]] $Role ) process { $installedProducts = Get-ItemProperty 'HKLM:\SOFTWARE\Classes\Installer\Products\*' -ErrorAction SilentlyContinue | Where-Object { $_.ProductName -like '*Citrix*' -and $_.ProductName -notlike '*snap-in' } | Select-Object -ExpandProperty ProductName; $installedRoles = @(); foreach ($r in $Role) { switch ($r) { 'Controller' { $filter = 'Citrix Broker Service'; } 'Studio' { $filter = 'Citrix Studio'; } 'Storefront' { $filter = 'Citrix Storefront$'; } 'Licensing' { $filter = 'Citrix Licensing'; } 'Director' { $filter = 'Citrix Director(?!.VDA Plugin)'; } 'DesktopVDA' { $filter = 'Citrix Virtual Desktop Agent'; } 'SessionVDA' { $filter = 'Citrix Virtual Desktop Agent'; } } $result = $installedProducts -match $filter; if ([System.String]::IsNullOrEmpty($result)) { } elseif ($result) { $installedRoles += $r; } } return $installedRoles; } #end process } #end function GetXDInstalledProduct function ResolveXDSetupMedia { <# .SYNOPSIS Resolve the correct installation media source for the local architecture depending on the role. #> [CmdletBinding()] [OutputType([System.String])] param ( ## Citrix XenDesktop 7.x role to install/uninstall. [Parameter(Mandatory)] [ValidateSet('Controller','Studio','Storefront','Licensing','Director','DesktopVDA','SessionVDA')] [System.String[]] $Role, ## Citrix XenDesktop 7.x installation media path. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $SourcePath ) process { $architecture = 'x86'; if ([System.Environment]::Is64BitOperatingSystem) { $architecture = 'x64'; } if ($Role -contains 'DesktopVDA') { $installMedia = 'XenDesktopVdaSetup.exe'; } elseif ($Role -contains 'SessionVDA') { $installMedia = 'XenDesktopVdaSetup.exe'; } else { $installMedia = 'XenDesktopServerSetup.exe'; } $sourceArchitecturePath = Join-Path -Path $SourcePath -ChildPath $architecture; $installMediaPath = Get-ChildItem -Path $sourceArchitecturePath -Filter $installMedia -Recurse -File; if (-not $installMediaPath) { throw ($localized.NoValidSetupMediaError -f $installMedia, $sourceArchitecturePath); } return $installMediaPath.FullName; } #end process } #end function ResolveXDSetupMedia function ResolveXDServerSetupArguments { <# .SYNOPSIS Resolve the installation arguments for the associated XenDesktop role. #> [CmdletBinding()] [OutputType([System.String])] param ( ## Citrix XenDesktop 7.x role to install/uninstall. [Parameter(Mandatory)] [ValidateSet('Controller','Studio','Storefront','Licensing','Director')] [System.String[]] $Role, ## Citrix XenDesktop 7.x installation media path. [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $LogPath = (Join-Path -Path $env:TMP -ChildPath '\Citrix\XenDesktop Installer'), ## Uninstall Citrix XenDesktop 7.x product. [Parameter()] [System.Management.Automation.SwitchParameter] $Uninstall ) process { $arguments = New-Object -TypeName System.Collections.ArrayList -ArgumentList @(); $arguments.AddRange(@('/QUIET', '/LOGPATH', "`"$LogPath`"", '/NOREBOOT', '/COMPONENTS')); $components = @(); foreach ($r in $Role) { switch ($r) { ## Install/uninstall component names by role 'Controller' { $components += 'CONTROLLER'; } 'Studio' { $components += 'DESKTOPSTUDIO'; } 'Storefront' { $components += 'STOREFRONT'; } 'Licensing' { $components += 'LICENSESERVER'; } 'Director' { $components += 'DESKTOPDIRECTOR'; } } #end switch Role } $componentString = [System.String]::Join(',', $components); [ref] $null = $arguments.Add($componentString); if ($Uninstall) { [ref] $null = $arguments.Add('/REMOVE'); } else { ## Additional install parameters per role if ($Role -contains 'Controller') { [ref] $null = $arguments.Add('/NOSQL'); } [ref] $null = $arguments.Add('/CONFIGURE_FIREWALL'); } return [System.String]::Join(' ', $arguments.ToArray()); } #end process } #end function ResolveXDSetupArguments #endregion Private Functions # SIG # Begin signature block # MIIcawYJKoZIhvcNAQcCoIIcXDCCHFgCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUMaVZx2b7dU9/lEXdsJUikr/x # FbiggheaMIIFIzCCBAugAwIBAgIQAsbTxa4q6RSRmx0hkVyicTANBgkqhkiG9w0B # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz # c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTE5MDcwMzAwMDAwMFoXDTIxMTEw # MzEyMDAwMFowYDELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEfMB0GA1UE # ChMWVmlydHVhbCBFbmdpbmUgTGltaXRlZDEfMB0GA1UEAxMWVmlydHVhbCBFbmdp # bmUgTGltaXRlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/s4NME # JLA1Aji4EHJ85uwyEEAepndYn1X8pRnGkOlTzVHITAeH7BQnehjbNwCj7MHUPTSM # zSucXhyfaMZthCNYtugyZ2uU4uVjB1f3xdmXXFX+aukMYgCk1ZQFbQMBqbzRY4Cl # DwlLNGVEjDJeLBUL6ciIETqDc27YLg772WLpuvIXne13EYXN422Y83XRqEMf4v9S # 398S8MRk5qdasRtxYZY6GciZZQnAL/XObpXDM3tDFgcQuyGcZttRuXVZXEj+mlY8 # gUIzkSJ0aJn1pVVTsa+tCvAZuJMJwdPhyM7NUa7Ysm7n9qdF7BvcrWBmaYRfDyya # lLwRoOcI2HVodX8CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl6nED # wGD5LfZldQ5YMB0GA1UdDgQWBBSDFWXl70FjVfl8IBwATpE46qvGeDAOBgNVHQ8B # Af8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOgMYYv # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmww # NaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3Mt # ZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0 # dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcB # AQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggr # BgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hB # MkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI # hvcNAQELBQADggEBAJ1VZZNMAy8KyQLBqwRWAWDqcOxjVly6FTIeqO2/ul9rEYm8 # B9mNx60/AL+TbTbUwBzia2pwBuIin70eClZHFstvQcASBbB0k14R/rs+jestfFRm # rsEz272POc6vsKce3TOlqBc2rtvVyuUPRvI2yQm1WYTpOgQnnp3ix2LBd+fgRANs # P9yurvnGdEFFzToFDXFVkFHBQ9Pr5tAb4i7ZkSFC52BtB7NVuoiH83lx07SyjIxU # 11ELEDZBpO3HiTsTzbhPAEw4CP++ONK8fieWZevDK9DFEiNIC0gWL/DH1+c7eihO # oJdJqRAT9wkAMIjcskZ5LObGvMst/hqwBewpLzYwggUwMIIEGKADAgECAhAECRgb # X9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD # VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAi # BgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAw # MDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp # Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERp # Z2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqG # SIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE # 9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvsp # J8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWu # HEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel0 # 5iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4P # waLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHN # MIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUE # DDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6 # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0f # BHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNz # dXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29t # L0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG # /WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ # UzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYD # VR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEB # AD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh # 9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6R # Ffu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEM # j7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutm # Q9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUu # kpHqaGxEMrJmoecYpJpkUe8wggZqMIIFUqADAgECAhADAZoCOv9YsWvW1ermF/Bm # MA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0xNDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAw # MDBaMEcxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDElMCMGA1UEAxMc # RGlnaUNlcnQgVGltZXN0YW1wIFJlc3BvbmRlcjCCASIwDQYJKoZIhvcNAQEBBQAD # ggEPADCCAQoCggEBAKNkXfx8s+CCNeDg9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V # +JLjntVaY1sCSVDZg85vZu7dy4XpX6X51Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnM # Xij0ZLZQt/USs3OWCmejvmGfrvP9Enh1DqZbFP1FI46GRFV9GIYFjFWHeUhG98oO # jafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZ # h6h+z+yMDDZbesF6uHjHyQYuRhDIjegEYNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZ # FQlXmVYwk/PJYczQCMxr7GJCkawCwO+k8IkRj3cCAwEAAaOCAzUwggMxMA4GA1Ud # DwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI # MIIBvwYDVR0gBIIBtjCCAbIwggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIB # FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFW # HoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBm # AGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0 # AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAv # AEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0 # AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAg # AGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBw # AG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBu # AGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNVHSMEGDAWgBQVABIrE5iymQftHt+ivlcN # K2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1KKnkag0v0HonByn0wfQYDVR0fBHYwdDA4 # oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElE # Q0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dEFzc3VyZWRJRENBLTEuY3JsMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNydDANBgkq # hkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaBXJuGziMgD4CH5Yj//7HUaiwx7ToXGXEX # zakbvFoWOQCd42yE5FpA+94GAYw3+puxnSR+/iCkV61bt5qwYCbqaVchXTQvH3Gw # g5QZBWs1kBCge5fH9j/n4hFBpr1i2fAnPTgdKG86Ugnw7HBi02JLsOBzppLA044x # 2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoVXZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYo # FenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV2q7ELlmgYd3a822iYemKC23sEhi991VU # QAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3Kr2qNe9zYRDCCBs0wggW1oAMCAQICEAb9 # +QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEFBQAwZTELMAkGA1UEBhMCVVMxFTAT # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEk # MCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTA2MTExMDAw # MDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp # Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY # RGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyBn+0snPgW # Wd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHrzzpADEZNk+yLejYIA6sMNP4YSYL+ # x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZwiPkFl32j # x0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQrllmCsSN # vtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao8S+v7Iki8msYZbHBc63X8djPHgp0 # XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQIDAQABo4IDejCCA3YwDgYDVR0PAQH/ # BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMG # CCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1UdIASCAckwggHFMIIBtAYKYIZIAYb9 # bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3Nz # bC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5 # ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABl # ACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAg # AG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABh # AG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwBy # AGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBp # AGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABl # AGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJ # YIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKG # N2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv # b3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9j # cmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYD # VR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuC # MS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgdtlspr1LP # UukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXDUOSCuSPRujqGcq04eKx1XRcXNHJH # hZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFi # qKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX/l/hHrwC # Xaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7Pkdcntxbu # D8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAm # MYIEOzCCBDcCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 # IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNl # cnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQAsbTxa4q6RSRmx0h # kVyicTAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq # hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC # NwIBFTAjBgkqhkiG9w0BCQQxFgQUVOL18JLdatYzwt6bPm7aIBshYQ8wDQYJKoZI # hvcNAQEBBQAEggEAOoiSpCeWxeElTjsj/dzyi1xroJVihBl4vhSa7QUMbA9Anl2d # 7hPDI+ND9qwvrIeOmC0hg2UJmsV30fIuxgnDr304hDHf91h48ZbwW/UXayJV5rV7 # aa3d+37dN93KJ/Nf+7+IhjzXrneoLt8C8yFublw0yf8ZIr0Vux0ERoxGx7ICaHvf # wie/swh+WhSrrOMsoh6R4DcwG3228XHs2TGXhntwuTmpyc+gEiI7dXXDkeeDGPgV # C1yRCKStjrKXdDSYKwY+0vi43RK69za0xwYLYjUKxqbz18gN/yN7+JRdyhHvb9GZ # of8ba/tpg1owmnO+QDB3Tu30kQtsVoP474eePKGCAg8wggILBgkqhkiG9w0BCQYx # ggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr1tXq5hfwZjAJBgUrDgMCGgUAoF0w # GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjAwMzAy # MTU0NDQ5WjAjBgkqhkiG9w0BCQQxFgQUt3DGCiMxd0AYBXL1ctxSAFeGGOYwDQYJ # KoZIhvcNAQEBBQAEggEAVJQHE5A5BFZTnKJ0yy7prWAJhQfWLXV/PFnRL9tWhbcx # N7bba6uJXeUueukjouvVhxmLrBmGCGiLRzB/AHzVfeHq+AX5AKVFmZHO7NMNaBdP # 1IcECwKe5G1yGqtmkCPi1oSelFMcfHMKoojdYoNmgqZNi6z2atA0E3zLMMbjZaEy # pnffANZCzoR5K27VEntsscxEsfyXW6l33AaGVwsqRp09NaetIS4W8/YHCGiv3Vfq # W2bGF9h6KP8FxuUNbGvciph2xuVtLII4JpN24ab4uTQG/KaQSz9v9xpc0Comd88c # uJgRlGe6K0Y6ud/P87SQUJWruWZa3N5tm64iJKHWuw== # SIG # End signature block |