SdnDiagnostics.psm1
# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. Import-Module $PSScriptRoot\SdnDiagnostics.Helper.psm1 New-Variable -Name 'SdnDiagnostics' -Scope 'Global' -Force -Value @{ Cache = @{} EnvironmentInfo = @{ # defines the cluster configuration type, supported values are 'ServiceFabric', 'FailoverCluster' # will default to 'ServiceFabric' on module import and updated once environment details have been retrieved ClusterConfigType = 'ServiceFabric' FailoverClusterConfig = @{ Name = $null } RestApiVersion = 'V1' # defaults to v1 on module load, and updated once environment details have been retrieved NcUrl = $null Gateway = @() NetworkController = @() LoadBalancerMux = @() Server = @() FabricNodes = @() } Config = @{ # when creating remote sessions, the module will be imported automatically ImportModuleOnRemoteSession = $false # determines from a global perspective if we should be disabling automatic seeding of module to remote nodes DisableModuleSeeding = $false # by default will just leverage the name of the module, however if using custom path not under default module directory # can update this to be the full path name to module, which will be used on PSRemoteSessions ModuleName = 'SdnDiagnostics' # defines if this module is running on Windows Server, Azure Stack HCI or Azure Stack Hub # supported values are 'WindowsServer', 'AzureStackHCI', 'AzureStackHub' Mode = "WindowsServer" } } # in some instances where powershell has been left open for a long time, we can leave behind sessions that are no longer valid # so we will want to clean up any SDN related sessions on module import Remove-PSRemotingSession function Get-SdnConfigState { <# .SYNOPSIS Gets the configuration state of the computer. .PARAMETER Role The SDN role of the computer. .PARAMETER OutputDirectory The directory to output the configuration state to. .EXAMPLE PS> Get-SdnConfigState -Role Server -OutputDirectory C:\Temp #> [cmdletbinding()] param( [parameter(Mandatory = $true)] [ValidateSet('Common', 'Gateway', 'NetworkController', 'Server', 'LoadBalancerMux')] [String]$Role, [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory ) switch ($Role) { 'Common' { Get-CommonConfigState -OutputDirectory $OutputDirectory } 'Gateway' { Get-GatewayConfigState -OutputDirectory $OutputDirectory } 'NetworkController' { Get-NetworkControllerConfigState -OutputDirectory $OutputDirectory } 'Server' { Get-ServerConfigState -OutputDirectory $OutputDirectory } 'LoadBalancerMux' { Get-SlbMuxConfigState -OutputDirectory $OutputDirectory } } } function Start-SdnCertificateRotation { <# .SYNOPSIS Performs a controller certificate rotate operation for Network Controller Northbound API, Southbound communications and Network Controller nodes. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER NcRestCertificate Specifies the client certificate that is used for a secure web request to Network Controller REST API. Enter a variable that contains a certificate or a command or expression that gets the certificate. .PARAMETER NcRestCredential Specifies a user account that has permission to perform this action against the Network Controller REST API. The default is the current user. .PARAMETER CertPath Path directory where certificate(s) .pfx files are located for use with certificate rotation. .PARAMETER GenerateCertificate Switch to determine if certificate rotate function should generate self-signed certificates. .PARAMETER CertPassword SecureString password for accessing the .pfx files, or if using -GenerateCertificate, what the .pfx files will be encrypted with. .PARAMETER NotAfter Expiration date when using -GenerateCertificate. If ommited, defaults to 3 years. .PARAMETER CertRotateConfig The Config generated by New-SdnCertificateRotationConfig to include NC REST certificate thumbprint and node certificate thumbprint. .PARAMETER Force Switch to force the rotation without being prompted, when Service Fabric is unhealthy. #> [CmdletBinding(DefaultParameterSetName = 'GenerateCertificate')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'CertConfig')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(Mandatory = $false, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $false, ParameterSetName = 'CertConfig')] [X509Certificate]$NcRestCertificate, [Parameter(Mandatory = $false, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $false, ParameterSetName = 'CertConfig')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $NcRestCredential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] [System.String]$CertPath, [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [Switch]$GenerateCertificate, [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [System.Security.SecureString]$CertPassword, [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [datetime]$NotAfter = (Get-Date).AddYears(3), [Parameter(Mandatory = $true, ParameterSetName = 'CertConfig')] [hashtable]$CertRotateConfig, [Parameter(Mandatory = $false, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $false, ParameterSetName = 'CertConfig')] [switch]$Force ) $ncRestParams = @{ NcUri = $null } if ($PSBoundParameters.ContainsKey('NcRestCertificate')) { $restCredParam = @{ NcRestCertificate = $NcRestCertificate } $ncRestParams.Add('NcRestCertificate', $NcRestCertificate) } else { $restCredParam = @{ NcRestCredential = $NcRestCredential } $ncRestParams.Add('NcRestCredential', $NcRestCredential) } # ensure that the module is running as local administrator $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-NOT $elevated) { throw New-Object System.Exception("This function requires elevated permissions. Run PowerShell as an Administrator and import the module again.") } if ($Global:SdnDiagnostics.EnvironmentInfo.ClusterConfigType -ine 'ServiceFabric') { throw New-Object System.NotSupportedException("This function is only supported on Service Fabric clusters.") } $config = Get-SdnModuleConfiguration -Role 'NetworkController_SF' $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature if (-NOT ($confirmFeatures)) { throw New-Object System.NotSupportedException("The current machine is not a NetworkController, run this on NetworkController.") } # add disclaimer that this feature is currently under preview if (!$Force) { "This feature is currently under preview. Please report any issues to https://github.com/microsoft/SdnDiagnostics/issues so we can accurately track any issues and help unblock your cert rotation." | Trace-Output -Level:Warning $confirm = Confirm-UserInput -Message "Do you want to proceed with certificate rotation? [Y/N]:" if (-NOT $confirm) { "User has opted to abort the operation. Terminating operation" | Trace-Output -Level:Warning return } } try { "Starting certificate rotation" | Trace-Output # purge any existing remote sessions to prevent situation where # we leverage a session without credentials Remove-PSRemotingSession "Retrieving current SDN environment details" | Trace-Output if ([String]::IsNullOrEmpty($CertPath)) { [System.String]$CertPath = "$(Get-WorkingDirectory)\Cert_{0}" -f (Get-FormattedDateTimeUTC) if (-NOT (Test-Path -Path $CertPath -PathType Container)) { $null = New-Item -Path $CertPath -ItemType Directory -Force } } [System.IO.FileSystemInfo]$CertPath = Get-Item -Path $CertPath -ErrorAction Stop # Get the Network Controller Info Offline (NC Cluster Down case) $NcInfraInfo = Get-SdnNetworkControllerInfoOffline -Credential $Credential $ncRestParams.NcUri = "https://$($NcInfraInfo.NcRestName)" if ($NcInfraInfo.ClusterCredentialType -ieq 'X509') { $rotateNCNodeCerts = $true } else { $rotateNCNodeCerts = $false } # Get the current rest certificate to determine if it is expired scenario or not. $currentRestCert = Get-SdnNetworkControllerRestCertificate $restCertExpired = (Get-Date) -gt $($currentRestCert.NotAfter) if ($restCertExpired) { "Network Controller Rest Certificate {0} expired at {1}" -f $currentRestCert.Thumbprint, $currentRestCert.NotAfter | Trace-Output -Level:Warning $isNetworkControllerHealthy = $false } else { $isNetworkControllerHealthy = Test-NetworkControllerIsHealthy } if ($restCertExpired -or !$isNetworkControllerHealthy) { $postRotateSBRestCert = $true $sdnFabricDetails = [SdnFabricInfrastructure]@{ NetworkController = $NcInfraInfo.NodeList.IpAddressOrFQDN } Install-SdnDiagnostics -ComputerName $sdnFabricDetails.NetworkController -Credential $Credential -ErrorAction Stop } else { # determine fabric information and current version settings for network controller $sdnFabricDetails = Get-SdnInfrastructureInfo -NetworkController $env:COMPUTERNAME -Credential $Credential @restCredParam $ncClusterSettings = Get-NetworkControllerCluster $ncSettings = @{ NetworkControllerVersion = (Get-NetworkController).Version NetworkControllerClusterVersion = $ncClusterSettings.Version ClusterAuthentication = $ncClusterSettings.ClusterAuthentication } # before we proceed with anything else, we want to make sure that all the Network Controllers within the SDN fabric are running the current version Install-SdnDiagnostics -ComputerName $sdnFabricDetails.NetworkController -Credential $Credential -ErrorAction Stop "Network Controller version: {0}" -f $ncSettings.NetworkControllerVersion | Trace-Output "Network Controller cluster version: {0}" -f $ncSettings.NetworkControllerClusterVersion | Trace-Output $healthState = Get-SdnServiceFabricClusterHealth -NetworkController $env:COMPUTERNAME -Credential $Credential if ($healthState.AggregatedHealthState -ine 'Ok') { "Service Fabric AggregatedHealthState is currently reporting {0}. Please address underlying health before proceeding with certificate rotation" ` -f $healthState.AggregatedHealthState | Trace-Output -Level:Error if (!$Force) { $confirm = Confirm-UserInput -Message "Do you want to proceed with certificate rotation? Enter N to abort and address the underlying health. Enter Y to force continue:" if (-NOT $confirm) { "User has opted to abort the operation. Terminating operation" | Trace-Output -Level:Warning return } } } } ##################################### # # Create Certificate (Optional) # ##################################### if ($PSCmdlet.ParameterSetName -ieq 'GenerateCertificate') { "== STAGE: CREATE SELF SIGNED CERTIFICATES ==" | Trace-Output $newSelfSignedCert = New-SdnNetworkControllerRestCertificate -RestName $NcInfraInfo.NcRestName.ToString() -NotAfter $NotAfter -Path $CertPath.FullName ` -CertPassword $CertPassword -Credential $Credential -FabricDetails $sdnFabricDetails $selfSignedRestCertFile = $newSelfSignedCert.FileInfo if ($rotateNCNodeCerts) { $null = Invoke-PSRemoteCommand -ComputerName $sdnFabricDetails.NetworkController -Credential $Credential -ScriptBlock { param( [Parameter(Position = 0)][DateTime]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][PSCredential]$param3, [Parameter(Position = 3)][String]$param4, [Parameter(Position = 4)][System.Object]$param5 ) New-SdnNetworkControllerNodeCertificate -NotAfter $param1 -CertPassword $param2 -Credential $param3 -Path $param4 -FabricDetails $param5 } -ArgumentList @($NotAfter, $CertPassword, $Credential, $CertPath.FullName, $sdnFabricDetails) } $CertRotateConfig = New-SdnCertificateRotationConfig -Credential $Credential } ##################################### # # PFX Certificates (Optional) # ##################################### if ($PSCmdlet.ParameterSetName -ieq 'Pfx') { "== STAGE: Install PFX Certificates to Fabric ==" | Trace-Output $pfxCertificates = Copy-UserProvidedCertificateToFabric -CertPath $CertPath -CertPassword $CertPassword -FabricDetails $sdnFabricDetails ` -NetworkControllerHealthy $isNetworkControllerHealthy -Credential $Credential -RotateNodeCerts $rotateNCNodeCerts $pfxCertificates | ForEach-Object { if ($_.CertificateType -ieq 'NetworkControllerRest' ) { if ($_.SelfSigned -ieq $true) { $selfSignedRestCertFile = $_.FileInfo } } } $CertRotateConfig = New-SdnCertificateRotationConfig -Credential $Credential } ##################################### # # Certificate Configuration # ##################################### "== STAGE: DETERMINE CERTIFICATE CONFIG ==" | Trace-Output "Validating Certificate Configuration" | Trace-Output $certValidated = Test-SdnCertificateRotationConfig -NcNodeList $NcInfraInfo.NodeList -CertRotateConfig $CertRotateConfig -Credential $Credential if ($certValidated -ne $true) { throw New-Object System.NotSupportedException("Unable to validate certificate configuration") } $updatedRestCertificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -ieq $currentRestCert.Subject } ` | Sort-Object -Property NotBefore -Descending | Select-Object -First 1 "Network Controller Rest Certificate {0} will be updated from [Thumbprint:{1} NotAfter:{2}] to [Thumbprint:{3} NotAfter:{4}]" ` -f $currentRestCert.Subject, $currentRestCert.Thumbprint, $currentRestCert.NotAfter, $CertRotateConfig["NcRestCert"], $updatedRestCertificate.NotAfter ` | Trace-Output -Level:Warning if ($rotateNCNodeCerts) { foreach ($node in $NcInfraInfo.NodeList) { $nodeCertThumbprint = $certRotateConfig[$node.NodeName.ToLower()] $currentNodeCert = Invoke-PSRemoteCommand -ComputerName $node.IpAddressOrFQDN -Credential $Credential -ScriptBlock { Get-SdnNetworkControllerNodeCertificate } $newNodeCert = Invoke-PSRemoteCommand -ComputerName $node.IpAddressOrFQDN -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) Get-SdnCertificate -Path $param1 -Thumbprint $param2 } -ArgumentList @('Cert:\LocalMachine\My', $nodeCertThumbprint) "Network Controller Node Certificate {0} will be updated from [Thumbprint:{1} NotAfter:{2}] to [Thumbprint:{3} NotAfter:{4}]" ` -f $currentNodeCert.Subject, $currentNodeCert.Thumbprint, $currentNodeCert.NotAfter, ` $newNodeCert.Thumbprint, $newNodeCert.NotAfter | Trace-Output -Level:Warning } } if (!$Force) { $confirm = Confirm-UserInput if (-NOT $confirm) { "User has opted to abort the operation. Terminating operation" | Trace-Output -Level:Warning return } } ##################################### # # Rotate NC Certificate Expired # ##################################### if ($restCertExpired -or !$isNetworkControllerHealthy) { # Use this for certificate if either rest cert expired or nc unhealthy, get-networkcontroller failed Start-SdnExpiredCertificateRotation -CertRotateConfig $CertRotateConfig -Credential $Credential } ##################################### # # Rotate NC Northbound Certificate (REST) # ##################################### "== STAGE: ROTATE NC REST CERTIFICATE ==" | Trace-Output $null = Invoke-CertRotateCommand -Command 'Set-NetworkController' -Credential $Credential -Thumbprint $CertRotateConfig["NcRestCert"] "Waiting for 5 minutes before proceeding to the next step. Script will resume at {0}" -f (Get-Date).AddMinutes(5).ToUniversalTime().ToString() | Trace-Output Start-Sleep -Seconds 300 ##################################### # # Rotate Cluster Certificate # ##################################### "== STAGE: ROTATE NC CLUSTER CERTIFICATE ==" | Trace-Output $null = Invoke-CertRotateCommand -Command 'Set-NetworkControllerCluster' -Credential $Credential -Thumbprint $CertRotateConfig["NcRestCert"] "Waiting for 5 minutes before proceeding to the next step. Script will resume at {0}" -f (Get-Date).AddMinutes(5).ToUniversalTime().ToString() | Trace-Output Start-Sleep -Seconds 300 ##################################### # # Rotate NC Node Certificates # ##################################### if ($rotateNCNodeCerts) { "== STAGE: ROTATE NC NODE CERTIFICATE ==" | Trace-Output foreach ($node in $NcInfraInfo.NodeList) { $nodeCertThumbprint = $certRotateConfig[$node.NodeName.ToLower()] $null = Invoke-CertRotateCommand -Command 'Set-NetworkControllerNode' -NetworkController $node.IpAddressOrFQDN -Name $node.NodeName -Credential $Credential -Thumbprint $nodeCertThumbprint "Waiting for 2 minutes before proceeding to the next step. Script will resume at {0}" -f (Get-Date).AddMinutes(5).ToUniversalTime().ToString() | Trace-Output Start-Sleep -Seconds 120 } } ##################################### # # Rotate NC Southbound Certificates # ##################################### "== STAGE: ROTATE SOUTHBOUND CERTIFICATE CREDENTIALS ==" | Trace-Output $null = Update-NetworkControllerCredentialResource @ncRestParams -NewRestCertThumbprint $CertRotateConfig["NcRestCert"] -ErrorAction Stop "Southbound certificate rotation completed" | Trace-Output ##################################### # # Certificate Seeding (Southbound Nodes) # ##################################### # if nc was unhealthy and unable to determine southbound devices in the dataplane earlier # we now want to check to see if nc is healthy and if we need to install the rest cert (for self-signed) to southbound devices if ($postRotateSBRestCert) { if ($selfSignedRestCertFile) { $sdnFabricDetails = Get-SdnInfrastructureInfo -Credential $Credential @restCredParam -Force $southBoundNodes = @() if ($null -ne $sdnFabricDetails.LoadBalancerMux) { $southBoundNodes += $sdnFabricDetails.LoadBalancerMux } if ($null -ne $sdnFabricDetails.Server) { $southBoundNodes += $sdnFabricDetails.Server } if ($southBoundNodes) { "== STAGE: REST SELF-SIGNED CERTIFICATE SEEDING (Southbound Nodes) ==" | Trace-Output # ensure that we have the latest version of sdnDiagnostics module on the southbound devices Install-SdnDiagnostics -ComputerName $southBoundNodes -Credential $Credential -ErrorAction Stop "[REST CERT] Installing self-signed certificate to {0}" -f ($southBoundNodes -join ', ') | Trace-Output [System.String]$remoteFilePath = Join-Path -Path $CertPath.FullName -ChildPath $selfSignedRestCertFile.Name Invoke-PSRemoteCommand -ComputerName $southBoundNodes -Credential $Credential -ScriptBlock { param($arg0) if (-NOT (Test-Path -Path $arg0 -PathType Container)) { $null = New-Item -Path $arg0 -ItemType Directory -Force } } -ArgumentList @($CertPath.FullName) Copy-FileToRemoteComputer -ComputerName $southBoundNodes -Credential $Credential -Path $selfSignedRestCertFile.FullName -Destination $remoteFilePath $null = Invoke-PSRemoteCommand -ComputerName $southBoundNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) Import-SdnCertificate -FilePath $param1 -CertStore $param2 } -ArgumentList @($remoteFilePath, 'Cert:\LocalMachine\Root') -ErrorAction Stop } } } ##################################### # # Restart services # ##################################### "== STAGE: RESTART NETWORK CONTROLLER SERVICES ==" | Trace-Output # restart the network controller services # this will force new TLS connections to be established to southbound devices # ensuring that the new certificates are used and we are able to push policies successfully # check to determine if we have a multi-node NC cluster and if so, leverage the SF cmdlets to move the replicas # otherwise, we will just stop the processes and let SF restart them automatically if ($sdnFabricDetails.NetworkController.Count -gt 1) { Move-SdnServiceFabricReplica -ServiceTypeName 'SlbManagerService' Move-SdnServiceFabricReplica -ServiceTypeName 'VSwitchService' } else { Get-Process -Name 'SDNFW' | Stop-Process -Force -ErrorAction Continue Get-Process -Name 'SDNSLBM' | Stop-Process -Force -ErrorAction Continue } "Certificate rotation has completed" | Trace-Output } catch { $_ | Trace-Exception $_ | Write-Error } } function Start-SdnDataCollection { <# .SYNOPSIS Automated data collection script to pull the current configuration state in conjuction with diagnostic logs and other data points used for debugging. .PARAMETER NetworkController Specifies the name or IP address of the network controller node on which this cmdlet operates. The parameter is optional if running on network controller node. .PARAMETER NcUri Specifies the Uniform Resource Identifier (URI) of the network controller that all Representational State Transfer (REST) clients use to connect to that controller. .PARAMETER Role The specific SDN role(s) to collect configuration state and logs from. .PARAMETER ComputerName Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more remote computers. .PARAMETER OutputDirectory Directory the results will be saved to. If ommitted, will default to the current working directory. .PARAMETER IncludeNetView If enabled, will execute Get-NetView on the Role(s) or ComputerName(s) defined. .PARAMETER IncludeLogs If enabled, will collect the diagnostic logs from the Role(s) or ComputerName(s) defined. Works in conjunction with the FromDate parameter. .PARAMETER FromDate Determines the start time of what logs to collect. If omitted, defaults to the last 4 hours. .PARAMETER ToDate Determines the end time of what logs to collect. Optional parameter that if ommitted, defaults to current time. .PARAMETER Credential Specifies a user account that has permission to SDN Infrastructure Nodes. The default is the current user. .PARAMETER NcRestCertificate Specifies the client certificate that is used for a secure web request to Network Controller REST API. Enter a variable that contains a certificate or a command or expression that gets the certificate. .PARAMETER NcRestCredential Specifies a user account that has permission to perform this action against the Network Controller REST API. The default is the current user. .PARAMETER Limit Used in conjuction with the Role parameter to limit how many nodes per role operations are performed against. If ommitted, defaults to 16. .PARAMETER ConvertETW Optional parameter that allows you to specify if .etl trace should be converted. By default, set to $true .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,NetworkController,Server,LoadBalancerMux .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,NetworkController,Server,LoadBalancerMux -IncludeLogs .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,Server,LoadBalancerMux -IncludeLogs -FromDate (Get-Date).AddHours(-1) -Credential (Get-Credential) .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role LoadBalancerMux -IncludeLogs -IncludeNetView -FromDate '2023-08-11 10:00:00 AM' -ToDate '2023-08-11 11:30:00 AM' #> [CmdletBinding(DefaultParameterSetName = 'Role')] param ( [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.String]$NetworkController = $env:COMPUTERNAME, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [ValidateScript({ if ($_.Scheme -ne "http" -and $_.Scheme -ne "https") { throw New-Object System.FormatException("Parameter is expected to be in http:// or https:// format.") } return $true })] [Uri]$NcUri, [Parameter(Mandatory = $true, ParameterSetName = 'Role')] [ValidateSet('Gateway', 'NetworkController', 'Server', 'LoadBalancerMux')] [String[]]$Role, [Parameter(Mandatory = $true, ParameterSetName = 'Computer')] [System.String[]]$ComputerName, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.IO.FileInfo]$OutputDirectory = (Get-WorkingDirectory), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [Switch]$IncludeNetView, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [Switch]$IncludeLogs, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [DateTime]$FromDate = (Get-Date).AddHours(-4), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [DateTime]$ToDate = (Get-Date), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [X509Certificate]$NcRestCertificate, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $NcRestCredential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Int]$Limit = 16, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [bool]$ConvertETW = $true ) $ErrorActionPreference = 'Continue' $dataCollectionNodes = [System.Collections.ArrayList]::new() # need an arrayList so we can remove objects from this list $filteredDataCollectionNodes = @() $ncRestParams = @{} if ($PSBoundParameters.ContainsKey('NcUri')) { $ncRestParams.Add('NcUri', $NcUri) } if ($PSBoundParameters.ContainsKey('NcRestCertificate')) { $restCredParam = @{ NcRestCertificate = $NcRestCertificate } $ncRestParams.Add('NcRestCertificate', $NcRestCertificate) } else { $restCredParam = @{ NcRestCredential = $NcRestCredential } $ncRestParams.Add('NcRestCredential', $NcRestCredential) } $stopWatch = [System.Diagnostics.Stopwatch]::StartNew() $dataCollectionObject = [PSCustomObject]@{ DurationInMinutes = $null TotalSize = $null OutputDirectory = $null Role = $null IncludeNetView = $IncludeNetView IncludeLogs = $IncludeLogs FromDate = $FromDate.ToString() FromDateUTC = $FromDate.ToUniversalTime().ToString() ToDate = $ToDate.ToString() ToDateUTC = $ToDate.ToUniversalTime().ToString() Result = $null } $collectLogSB = { param([string[]]$arg0,[String]$arg1,[DateTime]$arg2,[DateTime]$arg3,[Boolean]$arg4,[Boolean]$arg5,[string[]]$arg6) Get-SdnDiagnosticLogFile -LogDir $arg0 -OutputDirectory $arg1 -FromDate $arg2 -ToDate $arg3 -ConvertETW $arg4 -CleanUpFiles $arg5 -FolderNameFilter $arg6 } $collectConfigStateSB = { param([Parameter(Position = 0)][String]$Role, [Parameter(Position = 1)][String]$OutputDirectory) Get-SdnConfigState -Role $Role -OutputDirectory $OutputDirectory } $collectEventLogSB = { param([Parameter(Position = 0)][String]$OutputDirectory, [Parameter(Position =1)][String[]]$Role, [Parameter(Position =2)][DateTime]$FromDate, [Parameter(Position = 3)][DateTime]$ToDate) Get-SdnEventLog -OutputDirectory $OutputDirectory -Role $Role -FromDate $FromDate -ToDate $ToDate } $collectNetViewSB = { param([Parameter(Position = 0)][String]$OutputDirectory) Invoke-SdnGetNetView -OutputDirectory $OutputDirectory -SkipAdminCheck -SkipNetshTrace -SkipVM -SkipCounters } $collectClusterLogsSB = { param([Parameter(Position = 0)][String]$OutputDirectory) # The 3>$null 4>$null sends warning and error to null # typically Get-ClusterLog does not like remote powershell operations and generates warnings/errors $clusterLogFiles = Get-ClusterLog -Destination $OutputDirectory 2>$null 3>$null # if we have cluster log files, we will zip them up to preserve disk space if ($clusterLogFiles) { $clusterLogFiles | ForEach-Object { $zipFilePath = Join-Path -Path $OutputDirectory -ChildPath ($_.Name + ".zip") Compress-Archive -Path $_.FullName -DestinationPath $zipFilePath -Force -ErrorAction Stop # if the file was successfully zipped, we can remove the original file if (Get-Item -Path $zipFilePath -ErrorAction Ignore) { Remove-Item -Path $_.FullName -Force -ErrorAction Ignore } } } } if (Test-ComputerNameIsLocal -ComputerName $NetworkController) { Confirm-IsNetworkController } try { [System.String]$childPath = 'SdnDataCollection_{0}' -f (Get-FormattedDateTimeUTC) [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath $childPath [System.IO.FileInfo]$workingDirectory = (Get-WorkingDirectory) [System.IO.FileInfo]$tempDirectory = "$(Get-WorkingDirectory)\Temp" # setup the directory location where files will be saved to "Starting SDN Data Collection" | Trace-Output if ($IncludeLogs) { $minGB = 10 } else { $minGB = 5 } if (-NOT (Initialize-DataCollection -FilePath $OutputDirectory.FullName -MinimumGB $minGB)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Error return } "Results will be saved to {0}" -f $OutputDirectory.FullName | Trace-Output # generate a mapping of the environment $sdnFabricDetails = Get-SdnInfrastructureInfo -NetworkController $NetworkController -Credential $Credential @ncRestParams $sdnFabricDetails | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnInfrastructureInfo' # determine if network controller is using default logging mechanism to local devices or network share if ($Global:SdnDiagnostics.EnvironmentInfo.ClusterConfigType -ieq 'ServiceFabric') { [xml]$clusterManifest = Get-SdnServiceFabricClusterManifest -NetworkController $NetworkController -Credential $Credential $fileShareWinFabEtw = $clusterManifest.ClusterManifest.FabricSettings.Section | Where-Object {$_.Name -ieq 'FileShareWinFabEtw'} $connectionString = $fileShareWinFabEtw.Parameter | Where-Object {$_.Name -ieq 'StoreConnectionString'} if ($connectionString.value) { # typically the network share will be in a format of file://share/path $diagLogNetShare = ($connectionString.value).Split(':')[1].Replace('/', '\').Trim() $ncNodeFolders = @() } } switch ($PSCmdlet.ParameterSetName) { 'Role' { foreach ($value in $Role) { foreach ($node in $sdnFabricDetails[$value.ToString()]) { $object = [PSCustomObject]@{ Role = $value Name = $node } "{0} with role {1} added for data collection" -f $object.Name, $object.Role | Trace-Output [void]$dataCollectionNodes.Add($object) } } } 'Computer' { foreach ($computer in $ComputerName) { $computerRole = Get-SdnRole -ComputerName $computer -EnvironmentInfo $sdnFabricDetails if ($computerRole) { $object = [PSCustomObject]@{ Role = $computerRole Name = $computer } "{0} with role {1} added for data collection" -f $object.Name, $object.Role | Trace-Output [void]$dataCollectionNodes.Add($object) } } } } if ($dataCollectionNodes.Count -eq 0) { throw New-Object System.NullReferenceException("No data nodes identified") } # once we have identified the nodes, we need to validate WinRM connectivity to the nodes # if we are running on PowerShell 7 or greater, we can leverage the -Parallel parameter # to speed up the process # if we are running on PowerShell 5.1, we will need to run the process in serial # if we have any nodes that fail the WinRM connectivity test, we will remove them from the data collection "Validating WinRM connectivity to {0}" -f ($dataCollectionNodes.Name -join ', ') | Trace-Output $Global:ProgressPreference = 'SilentlyContinue' $nodesToRemove = [System.Collections.ArrayList]::new() $tncScriptBlock = { $tncResult = Test-NetConnection -ComputerName $_.Name -Port 5985 -InformationLevel Quiet if (-NOT ($tncResult)) { [void]$nodesToRemove.Add($_) } } if ($PSVersionTable.PSVersion.Major -ge 7) { $dataCollectionNodes | Foreach-Object -ThrottleLimit 10 -Parallel $tncScriptBlock } else { $dataCollectionNodes | ForEach-Object $tncScriptBlock } if ($nodesToRemove.Count -gt 0) { $nodesToRemove | ForEach-Object { "Removing {0} from data collection due to WinRM connectivity issues" -f $_.Name | Trace-Output -Level:Warning [void]$dataCollectionNodes.Remove($_) } } $Global:ProgressPreference = 'Continue' $dataCollectionNodes = $dataCollectionNodes | Sort-Object -Property Name -Unique $groupedObjectsByRole = $dataCollectionNodes | Group-Object -Property Role # ensure SdnDiagnostics installed across the data nodes and versions are the same # depending on the state of the environment though, these may result in failure Install-SdnDiagnostics -ComputerName $NetworkController -ErrorAction Continue Install-SdnDiagnostics -ComputerName $dataCollectionNodes.Name -ErrorAction Continue # ensure that the NcUrl is populated before we start collecting data # in scenarios where certificate is not trusted or expired, we will not be able to collect data if (-NOT ([System.String]::IsNullOrEmpty($sdnFabricDetails.NcUrl))) { if (-NOT ($ncRestParams.ContainsKey('NcUri'))) { $ncRestParams.Add('NcUri', $sdnFabricDetails.NcUrl) } $slbStateInfo = Get-SdnSlbStateInformation @ncRestParams $slbStateInfo | ConvertTo-Json -Depth 100 | Out-File "$($OutputDirectory.FullName)\SlbState.Json" Invoke-SdnResourceDump @ncRestParams -OutputDirectory $OutputDirectory.FullName Get-SdnNetworkControllerState -NetworkController $NetworkController -OutputDirectory $OutputDirectory.FullName -Credential $Credential @restCredParam } Get-SdnNetworkControllerClusterInfo -NetworkController $NetworkController -OutputDirectory $OutputDirectory.FullName -Credential $Credential $debugInfraHealthResults = Get-SdnFabricInfrastructureResult if ($debugInfraHealthResults) { $debugInfraHealthResults | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnFabricInfrastructureResult_Summary' -FileType 'txt' -Format 'table' $debugInfraHealthResults | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnFabricInfrastructureResult' -FileType json -Depth 5 } # enumerate through each role and collect appropriate data foreach ($group in $groupedObjectsByRole | Sort-Object -Property Name) { if ($PSCmdlet.ParameterSetName -eq 'Role') { if ($group.Group.Name.Count -ge $Limit) { "Exceeded node limit for role {0}. Limiting nodes to the first {1} nodes" -f $group.Name, $Limit | Trace-Output -Level:Warning } $dataNodes = $group.Group.Name | Select-Object -First $Limit } else { $dataNodes = $group.Group.Name } "Performing cleanup of {0} directory across {1}" -f $tempDirectory.FullName, ($dataNodes -join ', ') | Trace-Output Clear-SdnWorkingDirectory -Path $tempDirectory.FullName -Recurse -ComputerName $dataNodes -Credential $Credential # add the data nodes to new variable, to ensure that we pick up the log files specifically from these nodes # to account for if filtering was applied $filteredDataCollectionNodes += $dataNodes "Collect configuration state details for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectConfigStateSB ArgumentList = @($group.Name, $tempDirectory.FullName) AsJob = $true PassThru = $true Activity = "Collect $($group.Name) Configuration State" } Invoke-PSRemoteCommand @splat # check to see if any network traces were captured on the data nodes previously "Checking for any previous network traces and moving them into {0}" -f $tempDirectory.FullName | Trace-Output $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectLogSB ArgumentList = @("$($workingDirectory.FullName)\NetworkTraces", $tempDirectory.FullName, $FromDate, $ToDate, $ConvertETW, $true) AsJob = $true PassThru = $true Activity = 'Collect Network Traces' } Invoke-PSRemoteCommand @splat # collect the sdndiagnostics etl files if IncludeLogs was provided if ($IncludeLogs) { $commonConfig = Get-SdnModuleConfiguration -Role:Common # check to see if we are using local or network share for the logs if (!$diagLogNetShare) { [String]$diagLogDir = $commonConfig.DefaultLogDirectory "Collect diagnostics logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output $outputDir = Join-Path -Path $tempDirectory.FullName -ChildPath 'SdnDiagnosticLogs' $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectLogSB ArgumentList = @($diagLogDir, $outputDir, $FromDate, $ToDate, $ConvertETW) AsJob = $true PassThru = $true Activity = 'Get Diagnostic Log Files' } Invoke-PSRemoteCommand @splat # collect the logs related to the network controller if ($group.Name -ieq 'NetworkController') { # switched based on the cluster configuration type to define the logs we need to collect switch ($Global:SdnDiagnostics.EnvironmentInfo.ClusterConfigType) { 'ServiceFabric' { $ncConfig = Get-SdnModuleConfiguration -Role 'NetworkController_SF' [string[]]$sfLogDir = $ncConfig.Properties.CommonPaths.serviceFabricLogDirectory "Collect service fabric logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output $outputDir = Join-Path -Path $tempDirectory.FullName -ChildPath 'ServiceFabricLogs' $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectLogSB ArgumentList = @($sfLogDir, $outputDir, $FromDate, $ToDate) AsJob = $true PassThru = $true Activity = 'Get Service Fabric Logs' } } 'FailoverCluster' { "Collect cluster logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output $outputDir = Join-Path -Path $tempDirectory.FullName -ChildPath 'ClusterLogs' $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectClusterLogsSB ArgumentList = @($outputDir) AsJob = $true PassThru = $true Activity = 'Get Cluster Logs' } } } Invoke-PSRemoteCommand @splat } # if the role is a server, collect the audit logs if they are available if ($group.Name -ieq 'Server') { $auditParams = $ncRestParams $auditParams.Add('OutputDirectory', "$($OutputDirectory.FullName)\AuditLogs") $auditParams.Add('ComputerName', $dataNodes) $auditParams.Add('Credential', $Credential) Get-SdnAuditLog @auditParams } } # if the role is network controller and we are using a network share # need to update variable to include the network controller nodes # so we can add these supplmental folders to the collection if ($group.Name -ieq 'NetworkController') { $ncNodeFolders += $dataNodes } # collect the event logs specific to the role "Collect event logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output # because we may have a 'Common' role that is being collected, we need to account for that # and ensure that we are collecting the appropriate event logs switch ( $group.Name ) { 'Common' { $roleArray = @(); $roleArray += $group.Name } default { $roleArray = @(); $roleArray += $group.Name; $roleArray += 'Common' } } $splat = @{ ComputerName = $dataNodes Credential = $Credential ScriptBlock = $collectEventLogSB ArgumentList = @($tempDirectory.FullName, $roleArray, $FromDate, $ToDate) AsJob = $true PassThru = $true Activity = "Get $($group.Name) Event Logs" } Invoke-PSRemoteCommand @splat } } if ($diagLogNetShare -and $IncludeLogs) { $isNetShareMapped = New-SdnDiagNetworkMappedShare -NetworkSharePath $diagLogNetShare -Credential $Credential if ($isNetShareMapped) { $outputDir = Join-Path -Path $OutputDirectory.FullName -ChildPath 'NetShare_SdnDiagnosticLogs' # create an array of names that we will use to filter the logs # this ensures that we will only pick up the logs from the nodes that we are collecting from $filterArray = @() $dataCollectionNodes.Name | ForEach-Object { $filterArray += (Get-ComputerNameFQDNandNetBIOS -ComputerName $_).ComputerNameNetBIOS } $filterArray = $filterArray | Sort-Object -Unique # create an array of folders to collect the logs from leveraging the common configuration $logDir = @() $commonConfig.DefaultLogFolders | ForEach-Object { $logDir += Join-Path -Path $diagLogNetShare -ChildPath $_ } $ncNodeFolders | ForEach-Object { $ncNetBiosName = (Get-ComputerNameFQDNandNetBIOS -ComputerName $_).ComputerNameNetBIOS $logDir += Join-Path -Path $diagLogNetShare -ChildPath $ncNetBiosName } $logDir = $logDir | Sort-Object -Unique # create parameters for the Get-SdnDiagnosticLogFile function $netDiagLogShareParams = @{ LogDir = $logDir OutputDirectory = $outputDir FromDate = $FromDate ToDate = $ToDate FolderNameFilter = $filterArray } Get-SdnDiagnosticLogFile @netDiagLogShareParams } } if ($IncludeNetView) { "Collect Get-NetView logs for {0}" -f ($filteredDataCollectionNodes -join ', ') | Trace-Output $splat = @{ ComputerName = $filteredDataCollectionNodes Credential = $Credential ScriptBlock = $collectNetViewSB ArgumentList = @($tempDirectory.FullName) AsJob = $true PassThru = $true Activity = 'Invoke Get-NetView' } $null = Invoke-PSRemoteCommand @splat } foreach ($node in $filteredDataCollectionNodes) { [System.IO.FileInfo]$formattedDirectoryName = Join-Path -Path $OutputDirectory.FullName -ChildPath $node.ToLower() Copy-FileFromRemoteComputer -Path $tempDirectory.FullName -Destination $formattedDirectoryName.FullName -ComputerName $node -Credential $Credential -Recurse -Force Copy-FileFromRemoteComputer -Path (Get-TraceOutputFile) -Destination $formattedDirectoryName.FullName -ComputerName $node -Credential $Credential -Force } $dataCollectionObject.TotalSize = (Get-FolderSize -Path $OutputDirectory.FullName -Total) $dataCollectionObject.OutputDirectory = $OutputDirectory.FullName $dataCollectionObject.Role = $groupedObjectsByRole.Name $dataCollectionObject.Result = 'Success' } catch { $_ | Trace-Exception $_ | Write-Error $dataCollectionObject.Result = 'Failed' } finally { $stopWatch.Stop() $dataCollectionObject.DurationInMinutes = $stopWatch.Elapsed.TotalMinutes try { "Performing post operations and cleanup of {0} across the SDN fabric" -f $tempDirectory.FullName | Trace-Output # check for any failed PS remoting jobs and copy them to data collection if (Test-Path -Path "$(Get-WorkingDirectory)\PSRemoteJob_Failures") { Copy-Item -Path "$(Get-WorkingDirectory)\PSRemoteJob_Failures" -Destination $formattedDirectoryName.FullName -Recurse } if ($filteredDataCollectionNodes) { Clear-SdnWorkingDirectory -Path $tempDirectory.FullName -Recurse -ComputerName $filteredDataCollectionNodes -Credential $Credential } # remove any completed or failed jobs Remove-SdnDiagnosticJob -State @('Completed', 'Failed') } catch { $_ | Trace-Exception Write-Error -Message "An error occurred during cleanup of the SDN fabric." -Exception $_.Exception $dataCollectionObject.Result = 'Failed' } } $dataCollectionObject | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'SdnDataCollection_Summary' -FileType json -Depth 4 -ErrorAction Continue Copy-Item -Path (Get-TraceOutputFile) -Destination $OutputDirectory.FullName -Force -ErrorAction Continue # we will return the object to the caller regardless if the data collection was successful or not $msg = "Sdn Data Collection completed with status of {0}" -f $dataCollectionObject.Result switch ($dataCollectionObject.Result) { 'Success' { $msg | Trace-Output } 'Failed' { $msg | Trace-Output -Level:Error } } return $dataCollectionObject } # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCArvRs7BXqOH0Cc # ZZdSOzrMzFRdTk2MWU4ka2hruebhnaCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEe86URSk8mzIcFyvOpCWaeL # HEuYuitzv+Gc+UqlewGeMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAjFrnRGZPFRWCzMLDKvAEES2Eey7WtrPGVLRyYdGZmPe2HPHGm4pZ9+DL # JVMVw5/80gZ+ZqtP3my7bBAJ/MGfZ+XmG8/7nxE2D4ZWxX2dl0GhEgsalwKAYjhx # tfBSIyhEH7LUG0D8Eh1GDGwHQee0pbIpB6ZcXd26LPDoLzTTPWdzI+tvawN8wMrm # FGLGom0zlnaS7zWDQCpQy0SzXiHu4l3dqPt2kp1I7JUOlCynHpWotSzuB0MmaDnJ # Go9E8WEZozNvPOjklLtfaHAkvLk6n0jIKOnEfU+oLXq6iVfzPLwMY4/hIZzsDtzk # Q6ajCUI06CTtLQpZIHcOr7bpHdoRtKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCBLYVYfOfYxkm0ul5Sfm8dA4ykQ3ikHRyG8Xb/4f4ICAIGZxp4WYJK # GBMyMDI0MTEwMTE1NTY0NC44NTZaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAecujy+TC08b6QABAAAB5zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MTlaFw0yNTAzMDUxODQ1MTlaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDCV58v4IuQ659XPM1DtaWMv9/HRUC5kdiEF89YBP6/ # Rn7kjqMkZ5ESemf5Eli4CLtQVSefRpF1j7S5LLKisMWOGRaLcaVbGTfcmI1vMRJ1 # tzMwCNIoCq/vy8WH8QdV1B/Ab5sK+Q9yIvzGw47TfXPE8RlrauwK/e+nWnwMt060 # akEZiJJz1Vh1LhSYKaiP9Z23EZmGETCWigkKbcuAnhvh3yrMa89uBfaeHQZEHGQq # dskM48EBcWSWdpiSSBiAxyhHUkbknl9PPztB/SUxzRZjUzWHg9bf1mqZ0cIiAWC0 # EjK7ONhlQfKSRHVLKLNPpl3/+UL4Xjc0Yvdqc88gOLUr/84T9/xK5r82ulvRp2A8 # /ar9cG4W7650uKaAxRAmgL4hKgIX5/0aIAsbyqJOa6OIGSF9a+DfXl1LpQPNKR79 # 2scF7tjD5WqwIuifS9YUiHMvRLjjKk0SSCV/mpXC0BoPkk5asfxrrJbCsJePHSOE # blpJzRmzaP6OMXwRcrb7TXFQOsTkKuqkWvvYIPvVzC68UM+MskLPld1eqdOOMK7S # bbf2tGSZf3+iOwWQMcWXB9gw5gK3AIYK08WkJJuyzPqfitgubdRCmYr9CVsNOuW+ # wHDYGhciJDF2LkrjkFUjUcXSIJd9f2ssYitZ9CurGV74BQcfrxjvk1L8jvtN7mul # IwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFM/+4JiAnzY4dpEf/Zlrh1K73o9YMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQB0ofDbk+llWi1cC6nsfie5Jtp09o6b6ARC # pvtDPq2KFP+hi+UNNP7LGciKuckqXCmBTFIhfBeGSxvk6ycokdQr3815pEOaYWTn # HvQ0+8hKy86r1F4rfBu4oHB5cTy08T4ohrG/OYG/B/gNnz0Ol6v7u/qEjz48zXZ6 # ZlxKGyZwKmKZWaBd2DYEwzKpdLkBxs6A6enWZR0jY+q5FdbV45ghGTKgSr5ECAOn # LD4njJwfjIq0mRZWwDZQoXtJSaVHSu2lHQL3YHEFikunbUTJfNfBDLL7Gv+sTmRi # DZky5OAxoLG2gaTfuiFbfpmSfPcgl5COUzfMQnzpKfX6+FkI0QQNvuPpWsDU8sR+ # uni2VmDo7rmqJrom4ihgVNdLaMfNUqvBL5ZiSK1zmaELBJ9a+YOjE5pmSarW5sGb # n7iVkF2W9JQIOH6tGWLFJS5Hs36zahkoHh8iD963LeGjZqkFusKaUW72yMj/yxTe # GEDOoIr35kwXxr1Uu+zkur2y+FuNY0oZjppzp95AW1lehP0xaO+oBV1XfvaCur/B # 5PVAp2xzrosMEUcAwpJpio+VYfIufGj7meXcGQYWA8Umr8K6Auo+Jlj8IeFS6lSv # KhqQpmdBzAMGqPOQKt1Ow3ZXxehK7vAiim3ZiALlM0K546k0sZrxdZPgpmz7O8w9 # gHLuyZAQezCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCz # cgTnGasSwe/dru+cPe1NF/vwQ6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6s7ZdjAiGA8yMDI0MTEwMTA0Mzcx # MFoYDzIwMjQxMTAyMDQzNzEwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqztl2 # AgEAMAoCAQACAi7PAgH/MAcCAQACAhLUMAoCBQDq0Cr2AgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAF7X7sPvK9UdRneOPcHtsqVIky8XblEjlJRoaqxv8xbM # RxlIpy+S2r1ZuaC9k3cy3ICkqUD5we4WnyA5+aKIQ+Qm9+HC4Kwq0KQlZdxuEMQR # Ng4yO2fqsz+iB1Dv0fvP70JNKHc9LYi+nhSSxSIVbWYfjInUpGUZRZIpC8G8Lw3m # ENOE/smne7KGam+8hf/nziURU6h7Hc4Ypx6lQOuz6WrhYtLimbLJSA9LSRZkOG70 # bnEyVl4LRvSWp/Cm0duiCUbbWJ71B+x25r4/hRk5i+uB4U5cbdjx7Q6XDxJa+UQv # TnIewOiizigo2RgDodoO46VIp5Pe9o6c4UGkluz+02gxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAecujy+TC08b6QABAAAB # 5zANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCA5gbcg+/04C7dMUfRGt3cLRShcjRJN0J0i9iebpmZo # XzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIOU2XQ12aob9DeDFXM9UFHeE # X74Fv0ABvQMG7qC51nOtMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHnLo8vkwtPG+kAAQAAAecwIgQgEL0Jw0CDm44ZeHtDtT6y/44l # fOnNwIQdUD3mjfhYoRowDQYJKoZIhvcNAQELBQAEggIAmHWiSLerjcqAHPPNQ+EQ # 2u4GwJRwmAYXnOeLfHzpsXSp5YhMmLoadI7G9G4gIe7byTLg+l59UsD/OavvKy+G # S5AJIaRQxzCSGrmNYYExbk0pWJFulqPaLulpM8VtISTp7QBIb7vSuSo+OdH452WJ # AXms+TaGq2x2RaA5HCfF0vS52nNzy2grJg5b5nxuDLe/CRXZ64INLIkWU4adnlsj # XlCRblkeL6s8sImOinnq9EvJZNP1N1LTDxL8XesBLnVVZGk4/7izc7TOadiam/CM # +cfgJkMdW8/Fr+4nwLP+yfb3U1IiOxbBGz9Ev2SKrRK+hxnIt8kLdzmuYXCL1AaT # 4uCFNDrCEA3Mgr0OC8wqJQaZCWbbdptcb+NtIyd5z0eqpe1efDawXtvFLvW1+FRB # WZMZjPxUG8zEXpAIdy1ipUkLuS3nVGiVs6nG0F7MneyT1f+AUBIUyUzroPnakSvu # SCkakGT+XniVh/XvnchD/d6OC6Cy3JR5a/a6H/8TbUSNK22gO+GOk1U8qDsyB1Rj # R1z04Dd1E40+TL2JivpbXaV0T/Bub/Fl+9iS50weg32huJXwlhN68c6kYAQbO4Ue # gE3AqRS67X6mzoAQayhNVyshr/qgHBjBvVrUNpqGWbkvT5gC76ugctgVpJ7Dsr1b # qvOlb10Z9yDO4A9diu5FRH4= # SIG # End signature block |