AzStackHci.EnvironmentChecker.Utilities.psm1
Import-Module $PSScriptRoot\AzStackHci.EnvironmentChecker.Reporting.psm1 -Force -DisableNameChecking -Global Import-LocalizedData -BindingVariable lTxt -FileName AzStackHci.EnvironmentChecker.Strings.psd1 class HealthModel { # Attributes for Azure Monitor schema [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. [string]$Title #User-facing name; one or more sentences indicating the direct issue. [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity. [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp. [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware" [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed. [string]$Remediation #Set of steps that can be taken to resolve the issue found. [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive). [string]$TargetResourceName #The name of the affected resource. [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring). [datetime]$Timestamp #The Time in which the HealthCheck was called. [psobject]$AdditionalData #Property bag of key value pairs for additional information. [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster). } function Test-ModuleUpdate { <# .SYNOPSIS Checks PSGallery for updated module. .DESCRIPTION Checks PSGallery for updated module and gives user 10 seconds to cancel cmdlet and prints update instructions to screen #> param([switch]$PassThru) try { if (-not $PassThru) { Log-Info "Looking for module updates for $($MyInvocation.MyCommand.Module.Name) greater than $($MyInvocation.MyCommand.Module.Version)" $ModuleOnline = Find-Module -Name $MyInvocation.MyCommand.Module.Name -Repository PSGallery -AllowPrerelease -ErrorAction SilentlyContinue if (([system.version]$ModuleOnline.Version -replace ('-preview', '')) -gt [system.version]$MyInvocation.MyCommand.Module.Version) { Log-Info ($lTxt.CurrentVersion -f $MyInvocation.MyCommand.Module.Name, $MyInvocation.MyCommand.Module.Version) -ConsoleOut Log-Info ($lTxt.UpdateToVersion -f $ModuleOnline.Version, $MyInvocation.MyCommand.Module.Name) -ConsoleOut Start-Sleep -Seconds 10 } else { Log-Info ($lTxt.CurrentVersion -f $MyInvocation.MyCommand.Module.Name, $MyInvocation.MyCommand.Module.Version) } } } catch { Log-Info ($lTxt.Exception -f $MyInvocation.MyCommand.Name, $_.exception.message) -Type Error } } function Test-IsClusterNode { <# .SYNOPSIS This is a function to test is the local machine is a cluster node. #> [CmdletBinding()] param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) $clusSvc = Get-Service -Name ClusSvc -ErrorAction SilentlyContinue if ($clusSvc.Status -eq 'Running') { Log-Info -Message "Cluster service is running on $ENV:ComputerName" return $true } else { Log-Info -Message "Cluster service is not running on $ENV:ComputerName" return $false } } function Get-ClusterNodeName { <# .SYNOPSIS Get all Cluster Node names. #> [CmdletBinding()] param ( [Parameter()] [string] $Cluster, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) $sb = { if ([string]::IsNullOrEmpty($Cluster)) { Get-ClusterNode } else { Get-ClusterNode -Cluster $Cluster } } if ([string]::IsNullOrEmpty($PsSession)) { Log-Info -Message "Get cluster node locally on $ENV:ComputerName" $nodes = Invoke-Command -ScriptBlock $sb } else { Log-Info -Message "Get cluster node from PsSession on $($PsSession.ComputerName)" $nodes = Invoke-Command -ScriptBlock $sb -Session $PsSession } foreach ($node in $nodes) { if ($node.State -ne 'Up') { Log-Info -Message "$node state is $($node.state)" } } Log-Info -Message ("Return node names: {0}" -f ($nodes.name -join ',')) return $nodes.Name } function Open-PsSession { <# .SYNOPSIS Create PsSession to intended targets .DESCRIPTION Creates PsSession to single computer, cluster and related nodes. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, HelpMessage = "Specify the Computer Name(s) to run validation from. If null the local machine will be used.")] [string[]] $ComputerName, [Parameter(Mandatory = $false, HelpMessage = "Specify the Cluster Name to run validation from. If null the local machine will be used.")] [string] $Cluster, [Parameter(Mandatory = $false, HelpMessage = "Specify the credential used to connect to remote computer.")] [pscredential] $Credential, [Parameter(Mandatory = $false, HelpMessage = "Specify the authentication used to connect to remote computer.")] $Authentication ) try { # Build PsSession Params $sessionAuthParams = @{} if (-not [string]::IsNullOrEmpty($Authentication)) { if ($Authentication -eq 'CredSSP' -and [string]::IsNullOrEmpty($Credential)) { throw "A credential object must be provided with $Authentication Authentication" } $sessionAuthParams += @{ Authentication = $Authentication } } if (-not [string]::IsNullOrEmpty($Credential)) { $sessionAuthParams += @{ Credential = $Credential } } # Check if local machine is a cluster node and get its cluster nodes if (Test-IsClusterNode) { if ([string]::IsNullOrEmpty($Credential)) { Log-Info -Message "Credential not provided. Other Cluster nodes will not have connectivity validated. Only $ENV:ComputerName" } else { Log-Info -Message "Get computer names from local cluster/nodes" $ComputerName += @(Get-ClusterNodeName) } } # Create PsSession to cluster and get node names if (-not [string]::IsNullOrEmpty($Cluster)) { Log-Info -Message "Create session to cluster $cluster" $ClusterPsSession = New-PSSession -ComputerName $Cluster @sessionAuthParams Log-Info -Message "Get computer names from remote session to cluster $Cluster" $ComputerName = Get-ClusterNodeName -PsSession $ClusterPsSession -Cluster $Cluster Log-Info -Message ("ComputerNames found {0} from remote session to cluster $cluster. Removing session." -f ($ComputerName -join ',')) $ClusterPsSession | Remove-PSSession } if ([string]::IsNullOrEmpty($ComputerName)) { Log-Info -Message ("Running all tests locally.") } else { $ComputerName = $ComputerName | Sort-Object | Get-Unique Log-Info -Message ("Create a pssession to each ComputerName {0}." -f ($ComputerName -join ',')) $PsSession = @() $PsSession += foreach ($Computer in $ComputerName) { $Session = New-PSSession -ComputerName $Computer @sessionAuthParams -ErrorAction Stop Log-Info -Message ("Create a pssession created to {0}." -f $Session.ComputerName) } if ($null -eq $Session) { throw "User specified ComputerName but no remote sessions could be established to $($Computer -join ',')" } $Session } return $PsSession } catch { $exceptionMsg = $lTxt.Exception -f $MyInvocation.MyCommand.Name, $_.exception.message Log-Info $exceptionMsg -Type Error throw $exceptionMsg } } function Open-CimSession { <# .SYNOPSIS Create PsSession to intended targets .DESCRIPTION Creates PsSession to single computer, cluster and related nodes. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, HelpMessage = "Specify the Computer Name(s) to run validation from. If null the local machine will be used.")] [string[]] $ComputerName, [Parameter(Mandatory = $false, HelpMessage = "Specify the Cluster Name to run validation from. If null the local machine will be used.")] [string] $Cluster, [Parameter(Mandatory = $false, HelpMessage = "Specify the credential used to connect to remote computer.")] [pscredential] $Credential, [Parameter(Mandatory = $false, HelpMessage = "Specify the authentication used to connect to remote computer.")] $Authentication ) try { # Build PsSession Params $sessionAuthParams = @{} if (-not [string]::IsNullOrEmpty($Authentication)) { if ($Authentication -eq 'CredSSP' -and [string]::IsNullOrEmpty($Credential)) { throw "A credential object must be provided with $Authentication Authentication" } $sessionAuthParams += @{ Authentication = $Authentication } } if (-not [string]::IsNullOrEmpty($Credential)) { $sessionAuthParams += @{ Credential = $Credential } } # Check if local machine is a cluster node and get its cluster nodes if (Test-IsClusterNode) { if ([string]::IsNullOrEmpty($Credential)) { Log-Info -Message "Credential not provided. Other Cluster nodes will not have connectivity validated. Only $ENV:ComputerName" } else { Log-Info -Message "Get computer names from local cluster/nodes" $ComputerName += @(Get-ClusterNodeName) } } # Create PsSession to cluster and get node names if (-not [string]::IsNullOrEmpty($Cluster)) { Log-Info -Message "Create session to cluster $cluster" $ClusterPsSession = New-PSSession -ComputerName $Cluster @sessionAuthParams Log-Info -Message "Get computer names from remote session to cluster $Cluster" $ComputerName = Get-ClusterNodeName -PsSession $ClusterPsSession -Cluster $Cluster Log-Info -Message ("ComputerNames found {0} from remote session to cluster $cluster. Removing session." -f ($ComputerName -join ',')) $ClusterPsSession | Remove-PSSession } if ([string]::IsNullOrEmpty($ComputerName)) { Log-Info -Message ("Running all tests locally.") $CimSession = New-CimSession -ComputerName $ENV:COMPUTERNAME } else { $ComputerName = $ComputerName | Sort-Object | Get-Unique Log-Info -Message ("Create a CimSession to each ComputerName {0}." -f ($ComputerName -join ',')) $CimSession = @() $CimSession += foreach ($Computer in $ComputerName) { $Session = New-CimSession -ComputerName $Computer @sessionAuthParams -ErrorAction Stop Log-Info -Message ("Create a CimSession created to {0}." -f $Session.ComputerName) } if ($null -eq $Session) { throw "User specified ComputerName but no remote sessions could be established to $($Computer -join ',')" } $Session } return $CimSession } catch { $exceptionMsg = $lTxt.Exception -f $MyInvocation.MyCommand.Name, $_.exception.message Log-Info $exceptionMsg -Type Error throw $exceptionMsg } } function Test-Elevation { ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator') } function Test-Count { [CmdletBinding()] param ( [CimInstance[]] $CimData, [int] $minimum, [string] $ValidatorName, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) try { $passed = $false $className = $CimData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 $instanceId = $CimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique if ($CimData.Count -lt $minimum) { $passed = $false $detail = $lTxt.MinCount -f $ClassName, $CimData.count, $minimum } else { $passed = $true } $result = New-Object HealthModel $Result.Name = 'AzStackHci_{0}_Test_{1}_Count' -f $ValidatorName, $className $Result.Title = 'Test {0} Count' -f $className $Result.Severity = $Severity $Result.Description = 'Checking {0} count' -f $className $Result.Remediation = 'https://aka.ms/hci-connect-chk' $Result.TargetResourceID = $InstanceId $Result.TargetResourceName = $InstanceId $Result.TargetResourceType = $className $Result.Timestamp = [datetime]::UtcNow $Result.HealthCheckSource = 'Environment Checker' $Result.AdditionalData = New-Object -TypeName PSObject -Property @{ Source = "$ClassName Count" Resource = $CimData.count Detail = $detail Status = if ($passed) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow } $result.Status = if ($passed) { 'Succeeded' } else { 'Failed' } $result } catch { throw $_ } } function Test-InstanceCountByGroup { <# .SYNOPSIS Test if count matches across groups #> [CmdletBinding()] param ( [CimInstance[]] $CimData, [string[]] $GroupProperty, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) try { $GroupValues = $cimData | Group-Object -Property $groupProperty | Select-Object -ExpandProperty Name $nodeCount = ($cimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique).count foreach ($GroupValue in $GroupValues) { foreach ($group in $GroupProperty) { $gData = $CimData | Where-Object $Group -eq $GroupValue if ($gData.CimSystemProperties.SystemName.Count -eq 1) { $serverName = $gData.CimSystemProperties.SystemName } else { $serverName = 'AllServers' } $className = $gData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 $result = New-Object HealthModel $result.Name = 'AzStackHci_{0}_Test_{1}_Instance_Count_ByGroup' -f $ValidatorName, $className $result.Title = 'Test {0} Properties' -f $className $result.Severity = $Severity $result.Description = 'Checking all servers have same {0} instance count by group' -f $className $result.Remediation = 'https://aka.ms/hci-connect-chk' $result.TargetResourceID = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceName = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceType = $className $result.Timestamp = [datetime]::UtcNow $result.HealthCheckSource = 'Environment Checker' $groupData = $gData | Group-Object { $_.CimSystemProperties.ServerName } | Select-Object *, @{label = 'InstanceCount'; e = { $_.count } } $groupDataCount = $groupData.InstanceCount | Sort-Object | Get-Unique # The count of InstanceCounts must equal the number of servers to ensure each server has at least 1 instance # e.g. SVR1 has 6 disks of type A, SVR has 6 disks of type A, but SVR3 could have 5 disks of type A. # There should be only 1 unique InstanceCount from all values to ensure each server has the same instance count # e.g. SVR1 has 6 disks of type A, SVR has 6 disks of type A, SVR3 has 6 disks of type A. $Status = if ($groupData.InstanceCount.Count -ne $nodeCount -or $groupDataCount.Count -gt 1 ) { 'Failed' } else { 'Succeeded' } $result.AdditionalData = New-Object -TypeName PSObject -Property @{ Source = $serverName Resource = $ClassName Detail = if ($status -eq 'Succeeded') { $lTxt.CountByGroup -f $Group } else { $lTxt.CountByGroupFail -f $group, ` (($groupData | ForEach-Object { "{0}: {1}: $Group`: {2}" -f $_.Name, $_.InstanceCount, $GroupValue }) -join ',') } Status = $Status TimeStamp = [datetime]::UtcNow } $result.Status = if ($result.AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' } $result } } } catch { throw $_ } } function Test-GroupProperty { <# .SYNOPSIS Test if properties match across groups #> [CmdletBinding()] param ( [CimInstance[]] $CimData, [string[]] $GroupProperty, [string[]] $MatchProperty, [string] $ValidatorName, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) try { # Group by name and compare properties within each group $className = $CimData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 $ServerName = $CimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique $groupedData = @($CimData | Group-Object -Property $groupProperty) $returnResult = @() if ($serverName.Count -gt 1) { $serverName = 'AllServers' } $returnResult += foreach ($group in $groupedData) { $instanceId = "Machine: {0}, Class: {1} Group: {2}" -f $ServerName, $className, $group.Name $result = New-Object HealthModel $result.Name = 'AzStackHci_{0}_Test_{1}_Group_Consistency' -f $ValidatorName, $className $result.Title = 'Test {0} Groups' -f $className $result.Severity = $Severity $result.Description = 'Checking {0} Groups for consistent properties' -f $className $result.Remediation = 'https://aka.ms/hci-connect-chk' $result.TargetResourceID = $instanceId $result.TargetResourceName = $instanceId $result.TargetResourceType = $className $result.Timestamp = [datetime]::UtcNow $result.HealthCheckSource = 'Environment Checker' $AdditionalData = @() $detail = $null $passed = $false if ($group.Count -gt 1) { foreach ($propertyName in $matchProperty) { if (($group.Group.$propertyName | Sort-Object | Get-Unique).Count -gt 1) { $passed = $false $detail = $lTxt.MismatchProp -f $className, $propertyName, ($group.Group.$propertyName -join ', ') Log-Info -Message $detail -Type Warning } else { $passed = $true } $AdditionalData += New-Object -TypeName PSObject -Property @{ Source = "$($Group.Name) $propertyName" Resource = $instance.$propertyName Detail = $detail Status = if ($passed) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow } } } $result.AdditionalData = $AdditionalData $result.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' } $result } $returnResult } catch { throw $_ } } function Test-InstanceCount { <# .SYNOPSIS Test if instance count matches across instances #> [CmdletBinding()] param ( [Parameter()] [CimInstance[]] $CimData, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) if ($CimData.CimSystemProperties.SystemName.Count -eq 1) { $serverName = $CimData.CimSystemProperties.SystemName } else { $serverName = 'AllServers' } $className = $CimData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 $result = New-Object HealthModel $result.Name = 'AzStackHci_{0}_Test_{1}_Instance_Count' -f $ValidatorName, $className $result.Title = 'Test {0} Properties' -f $className $result.Severity = $Severity $result.Description = 'Checking all servers have same {0} instance count' -f $className $result.Remediation = 'https://aka.ms/hci-connect-chk' $result.TargetResourceID = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceName = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceType = $className $result.Timestamp = [datetime]::UtcNow $result.HealthCheckSource = 'Environment Checker' $groupData = $cimData | Group-Object { $_.CimSystemProperties.ServerName } | Select-Object *, @{label = 'InstanceCount'; e = { $_.count } } $groupDataCount = $groupData.InstanceCount | Sort-Object | Get-Unique $status = if ($groupDataCount.Count -gt 1) { 'Failed' } else { 'Succeeded' } $result.AdditionalData = New-Object -TypeName PSObject -Property @{ Source = $serverName Resource = $ClassName Detail = if ($status -eq 'Succeeded') { $lTxt.InstanceCount } else { $lTxt.InstanceCountFail -f ` (($groupData | ForEach-Object { "{0}: {1}" -f $_.Name, $_.InstanceCount }) -join ',') } Status = $status TimeStamp = [datetime]::UtcNow } $result.Status = if ($result.AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' } $result } function Test-PropertySync { <# .SYNOPSIS Test if properties match across instances #> [CmdletBinding()] param ( [Parameter()] [CimInstance[]] $CimData, [Parameter()] [string[]] $MatchProperty, [string] $ValidatorName, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) try { $returnResult = @() $className = $CimData.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 $serverName = $CimData.CimSystemProperties.ServerName | Sort-Object | Get-Unique if ($serverName.Count -gt 1) { $serverName = 'AllServers' $returnResult += Test-InstanceCount -CimData $CimData } $returnResult += if ($CimData.Count -gt 1) { $result = New-Object HealthModel $result.Name = 'AzStackHci_{0}_Test_{1}_Consistency' -f $ValidatorName, $className $result.Title = 'Test {0} Properties' -f $className $result.Severity = $Severity $result.Description = 'Checking all {0} for consistent properties' -f $className $result.Remediation = 'https://aka.ms/hci-connect-chk' $result.TargetResourceID = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceName = "{0}.All{1}" -f $ServerName, $className $result.TargetResourceType = $className $result.Timestamp = [datetime]::UtcNow $result.HealthCheckSource = 'Environment Checker' $AdditionalData = @() foreach ($propertyName in $matchProperty) { $detail = $null $passed = $false if (($CimData.$propertyName | Get-Unique).Count -gt 1) { $passed = $false $detail = $lTxt.MismatchProp -f $className, $propertyName, ($CimData.$propertyName -join ',') Log-Info -Message $detail -Type Warning } else { $passed = $true } $AdditionalData += New-Object -TypeName PSObject -Property @{ Source = "$className`: $propertyName" Resource = $instance.$propertyName Detail = $detail Status = if ($passed) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow } } $result.AdditionalData = $AdditionalData $result.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' } $result } return $returnResult } catch { throw $_ } } function Test-DesiredProperty { <# .SYNOPSIS Test if properties have required value #> [cmdletbinding()] param ( [CimInstance[]] $cimData, [hashtable] $desiredPropertyValue, [string] $InstanceIdStr, [string] $ValidatorName, [validateset('Critical','Warning','Informational','Hidden')] [string] $Severity ) try { # Test properties $returnResult = @() $returnResult += foreach ($instance in $cimData) { $serverName = $instance.CimSystemProperties.ServerName | Sort-Object | Get-Unique $sb = ([scriptblock]::Create($InstanceIdStr)) $instanceId = Invoke-Command -ScriptBlock $sb $className = $instance.CimSystemProperties.ClassName -split '_' | Select-Object -Last 1 Log-Info -Message ($lTxt.Test -f $className, $instanceId) $result = New-Object HealthModel $result.Name = 'AzStackHci_{0}_Test_{1}_Instance_Properties' -f $ValidatorName, $className $result.Title = 'Test {0} Properties' -f $className $result.Severity = $Severity $result.Description = 'Checking {0} for desired properties' -f $className $result.Remediation = 'https://aka.ms/hci-connect-chk' $result.TargetResourceID = $instanceId $result.TargetResourceName = $instanceId $result.TargetResourceType = $className $result.Timestamp = [datetime]::UtcNow $result.HealthCheckSource = 'Environment Checker' $AdditionalData = @() foreach ($propertyName in $desiredPropertyValue.Keys) { $detail = $null $passed = $false $hint = $null $desiredPropertyValueCheck = $null $desiredPropertyValueCheck = if ($desiredPropertyValue.$propertyName -is [hashtable]) { $desiredPropertyValue.$propertyName.Value } else { $desiredPropertyValue.$propertyName } if (($instance.$propertyName | Select-Object -First 1) -notin $desiredPropertyValueCheck) { $passed = $false $hint = if ($desiredPropertyValue.$propertyName.hint) { ' ({0})' -f $desiredPropertyValue.$propertyName.hint } $detail = $lTxt.UnexProp -f $className, $propertyName, $instance.$propertyName, ($desiredPropertyValueCheck -join ','), $hint Log-Info -Message $detail -Type Warning } else { $passed = $true } $AdditionalData += New-Object -TypeName PSObject -Property @{ Source = "$className`: $propertyName" Resource = if ($hint) { "$($instance.$propertyName)$hint" } else { $instance.$propertyName } Detail = $detail Status = if ($passed) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow } } $result.AdditionalData = $AdditionalData $result.Status = if ($AdditionalData.Status -contains 'Failed') { 'Failed' } else { 'Succeeded' } $result } return $returnResult } catch { throw $_ } } function Confirm-RemoteConnectionMatch { <# .SYNOPSIS Ensure multiple remote connection methods match all remote systems #> [CmdletBinding()] param ( [Parameter()] [System.Management.Automation.Runspaces.PSSession[]] $PsSession, [CimSession[]] $CimSession ) if ($PsSession -or $CimSession) { Log-Info ($lTxt.RemoteSyncStart -f ($PsSession.ComputerName -join ','), ($CimSession.ComputerName -join ',')) if (Compare-Object -ReferenceObject $PsSession -DifferenceObject $CimSession -Property ComputerName) { $errorMsg = $lTxt.RemoteSyncFail -f ($PsSession.ComputerName -join ','), ($CimSession.ComputerName -join ',') Log-Info $errorMsg -Type Error throw $errorMsg } else { Log-Info ($lTxt.RemoteSyncPass -f ($PsSession.ComputerName -join ','), ($CimSession.ComputerName -join ',')) } } else { Log-Info $lTxt.RemoteSyncSkip } } function Get-TestCount { param ( [Parameter()] [string] $ModuleName, [Parameter()] [string] $CommandPrefix ) try { $command = Get-Command -Name $CommandPrefix* -Module $ModuleName if ($command) { return $command.Count } else { return 1 } } catch { return 1 } } function Get-DeploymentData { [cmdletbinding()] param ($Path) try { $Json = Get-Content -Path $Path | ConvertFrom-Json $DeploymentData = $json.ScaleUnits[0].DeploymentData if ([string]::IsNullOrEmpty($DeploymentData)) { Log-Info $lTxt.InvalidDeploymentData return $null } return $DeploymentData } catch { throw $_ } } # SIG # Begin signature block # MIInrQYJKoZIhvcNAQcCoIInnjCCJ5oCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCv/YLMbRRxYXxM # zP7o/mYGXZCFDXPII1e6Ip8HRso07qCCDYUwggYDMIID66ADAgECAhMzAAACzfNk # v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz # HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ # aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO # nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1 # uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+ # oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r # GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS # MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl # 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/ # LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL # tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB # VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4 # BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go # a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME # iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM # 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGX4wghl6AgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA # As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBQh # kCCISxXoE+weOApu/6CFTfq4WWtI0I1u2fzSEOegMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAesvWkAsIzcTk1da0V1zUnuKSjW10Yc3TiWj3 # 82PIH+cIbqfcE0VY19T1rkXJNcMAPZF/mYwDZJ2DNOIdLon9bWNojfr0E/a9WdRK # saWauKrODmyvW8wViC7/+7YSvyx4dUPaNjdbTjbEXuGVDxvJV0T8PFprkxYMCkHW # +Y8LGTAuZPhQDVIpX4/iTSZMPux1lbspU7X5VH5dcOSWB8cOR3JXq35hYIGWJo0j # za5D1PMW8p55WLs69s3sAey6pzE1xC3SaMxoqD5ox6DjQn8O1L/c8sP1RBhK6LWv # p6Jl07DNBoUkZiRzVwHani8I/QYh+0Wsk43dt4p6zrFvULBrcKGCFwgwghcEBgor # BgEEAYI3AwMBMYIW9DCCFvAGCSqGSIb3DQEHAqCCFuEwghbdAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFUBgsqhkiG9w0BCRABBKCCAUMEggE/MIIBOwIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCCjB3CNPcTW6Ye4HPRGHBJantBnYpoLTmce # rUqSJ1/4AwIGYv660LFhGBIyMDIyMDgzMTExMDAzMy42NFowBIACAfSggdSkgdEw # gc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsT # IE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFs # ZXMgVFNTIEVTTjo0RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRp # bWUtU3RhbXAgU2VydmljZaCCEVwwggcQMIIE+KADAgECAhMzAAABsKHjgzLojTvA # AAEAAAGwMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy # MDEwMB4XDTIyMDMwMjE4NTE0MloXDTIzMDUxMTE4NTE0Mlowgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 # RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJzGbTsM19KCnQc5 # RC7VoglySXMKLut/yWWPQWD6VAlJgBexVKx2n1zgX3o/xA2ZgZ/NFGcgNDRCJ7mJ # iOeW7xeHnoNXPlg7EjYWulfk3oOAj6a7O15GvckpYsvLcx+o8Se8CrfIb40EJ8W0 # Qx4TIXf0yDwAJ4/qO94dJ/hGabeJYg4Gp0G0uQmhwFovAWTHlD1ci+sp36AxT9wI # hHqw/70tzMvrnDF7jmQjaVUPnjOgPOyFWZiVr7e6rkSl4anT1tLv23SWhXqMs14w # olv4ZeQcWP84rV2Frr1KbwkIa0vlHjlv4xG9a6nlTRfo0CYUQDfrZOMXCI5KcAN2 # BZ6fVb09qtCdsWdNNxB0y4lwMjnuNmx85FNfzPcMZjmwAF9aRUUMLHv626I67t1+ # dZoVPpKqfSNmGtVt9DETWkmDipnGg4+BdTplvgGVq9F3KZPDFHabxbLpSWfXW90M # ZXOuFH8yCMzDJNUzeyAqytFFyLZir3j4T1Gx7lReCOUPw1puVzbWKspV7ModZjtN # /IUWdVIdk3HPp4QN1wwdVvdXOsYdhG8kgjGyAZID5or7C/75hyKQb5F0Z+Ee04uY # 9K+sDZ3l3z8TQZWAfYurbZCMWWnmJVsu5V4PR5PO+U6D7tAtMvMULNYibT9+sxVZ # K/WQer2JJ9q3Z7ljFs4lgpmfc6AVAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUOt8B # JDcBJm4dy6ASZHrXIEfWNj8wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ # 6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSku # Y3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNy # b3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIw # UENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEF # BQcDCDANBgkqhkiG9w0BAQsFAAOCAgEA3XPih5sNtUfAyLnlXq6MZSpCh0TF+uG+ # nhIJ44//cMcQGEViZ2N263NwvrQjCFOni/+oxf76jcmUhcKWLXk9hhd7vfFBhZZz # cF5aNs07Uligs24pveasFuhmJ4y82OYm1G1ORYsFndZdvF//NrYGxaXqUNlRHQls # kV/pmccqO3Oi6wLHcPB1/WRTLJtYbIiiwE/uTFEFEL45wWD/1mTCPEkFX3hliXEy # pxXzdZ1k6XqGTysGAtLXUB7IC6CH26YygKQuXG8QjcJBAUG/9F3yNZOdbFvn7Fin # ZyNcIVLxld7h0bELfQzhIjelj+5sBKhLcaFU0vbjbmf0WENgFmnyJNiMrL7/2FYO # LsgiQDbJx6Dpy1EfvuRGsdL5f+jVVds5oMaKrhxgV7oEobrA6Z56nnWYN47swwou # ucHf0ym1DQWHy2DHOFRRN7yv++zes0GSCOjRRYPK7rr1Qc+O3nsd604Ogm5nR9Qq # hOOc2OQTrvtSgXBStu5vF6W8DPcsns53cQ4gdcR1Y9Ng5IYEwxCZzzYsq9oalxlH # +ZH/A6J7ZMeSNKNkrXPx6ppFXUxHuC3k4mzVyZNGWP/ZgcUOi2qV03m6Imytvi1k # fGe6YdCh32POgWeNH9lfKt+d1M+q4IhJLmX0E2ZZICYEb9Q0romeMX8GZ+cbhuNs # FimJga/fjjswggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqG # SIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg # MjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC # AgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4X # YDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTz # xXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7 # uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlw # aQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedG # bsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXN # xF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03 # dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9 # ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5 # UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReT # wDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZ # MBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8 # RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAE # VTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAww # CgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQD # AgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb # 186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29t # L3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoG # CCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZI # hvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9 # MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2Lpyp # glYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OO # PcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8 # DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA # 0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1Rt # nWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjc # ZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq7 # 7EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJ # C4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328 # y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC # zzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBS # aWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0RDJGLUUzREQtQkVFRjElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa # AxUAAp4vkN3fD5FNBVYZklZeS/JFPBiggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOa5s78wIhgPMjAyMjA4MzEx # NDE3MzVaGA8yMDIyMDkwMTE0MTczNVowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA # 5rmzvwIBADAHAgEAAgIbJTAHAgEAAgIRUzAKAgUA5rsFPwIBADA2BgorBgEEAYRZ # CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G # CSqGSIb3DQEBBQUAA4GBADa3ghMFw4uA09fER6Bt9CGCSCHtCdNyXTzWjVCP0sn/ # qVqaWwgFFsi6pQ9g89g2NlOoR8iUbHOCybkso2oDHey2gZqb8HH4mSavDsCifOdL # th2cXCs/+zXbOEbUlo6b+nUxkR+CjibVp4mxZLYsCCZJ1goTKSyKrxd8/ItkOx7Z # MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA # AAGwoeODMuiNO8AAAQAAAbAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ # AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgjf6INj7+gCO15XR+62PE # o+tBArHSjs5GpQCpDCQeCIswgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDN # BgtDd8uf9KTjGf1G67IfKmcNFJmeWTd6ilAy5xWEoDCBmDCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsKHjgzLojTvAAAEAAAGwMCIEIF0M # ww/ihD7mTLUeHvTfYxTrkUEOkXHeNyy3G3eQZtFWMA0GCSqGSIb3DQEBCwUABIIC # AIs03DXMq6iiXnbOQ9Eorjtig89TLSIbS56xQtAKFr0qAWimKH0+Rk2oVHWDO1el # YlayNpDOEPpv0zaBrwY7evTSGt8sGy/cgFAKVRUqNS4dT7A+NYdJ9j4WbfivLe6H # Yy6VtpZLrwkCmu2cMTC5kMmFJn1PNA3KWDdtvjewsQ/lSvQz+1JIIG+zqFF+i/Yn # UsAp0pixstND9PhGatxkOepWJ6VaxxORN9DPvPpUXP9vKMx9zoxAd3tSunnkDuSt # fitNF+obsdVwiVYZl+sNEWV1ec1ZzcaqH4G7RlzkXGDkk/lapaxIfrBEQkoj4bb7 # odP2T58TDWuN8DTPO0ePoNGef5hrGLSWmxC4uPCQ5thCqAWBJD02UOALFIrsUh+x # 48VRqm6tVDaRVscq0Rh4EonFzDkluCLQ6u7fx6FsCQkqFcnOm+FIpx5+/Ds7zGOs # 8EtHchk6aTPkQZhIuGpGnIrW+BmjxT1BwLAdWOrMFcra1Pbt6VNH6ulaGQ+sozTc # 6oSavdPId44vpEOO0vwcg+gvIXpg2zo2KfM7gUa41rTnD/Y/sBF6a9D28fQ/6Fbh # 4C4fYb1qJK4PdErxhDsM78Tttojxd5POJD0letGplrbmUq8S6EThmJ1v7eZgUz4N # /CsMZ/wRP3GCA/RTp3GD3hGi4VQSUbfhoy2/xvnSe168 # SIG # End signature block |