AzStackHciSBEHealth/AzStackHci.SBEHealth.Helpers.psm1
Import-LocalizedData -BindingVariable locSbeTxt -FileName AzStackHci.SBEHealth.Strings.psd1 function Get-ASArtifactPathLite { <# .SYNOPSIS Returns the nuget content path. Same as normal Get-ArtifactPath except it doesn't use Trace-Execution or try to locate on ProductVHD. .DESCRIPTION Calculates and returns the path to the nuget content folder for the specified nuget on the current infrastructure vm environment. All product artifacts are exposed to the infrastructure vms, however the location is not fixed, this method is used to find the desired content. .EXAMPLE $Path = Get-ASArtifactPathLite -NugetName "Microsoft.Diagnostics.Tracing.EventSource.Redist" .PARAMETER NugetName The full name of the nuget without version information. .PARAMETER Version The optional version number of the package. #> [CmdletBinding()] PARAM ( [Parameter(Position=0, Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $NugetName, [Parameter(Mandatory=$false)] [System.String] $Version = $null ) PROCESS { $VerbosePreference = [System.Management.Automation.ActionPreference]::Continue Import-Module PackageManagement -DisableNameChecking -Verbose:$false | Out-Null $nugetProvider = Get-PackageProvider | Where-Object { $_.Name -eq "Nuget" } if ($nugetProvider -eq $null) { Log-Info -Message "Attempting to install nuget package provider." Install-PackageProvider nuget -Force -ForceBootstrap } $drivePath = "$env:SystemDrive\NugetStore" if (Test-Path -Path $drivePath) { if ($Version) { $package = Get-Package -Name $NugetName -Destination $drivePath -ErrorAction Stop -RequiredVersion $Version -ProviderName Nuget } else { $package = Get-Package -Name $NugetName -Destination $drivePath -ErrorAction Stop -ProviderName Nuget } Log-Info -Message "Get-Package returned with Success:$($?)" } if ($package -eq $null) { throw "Could not find package $NugetName on source $drivePath." } Log-Info -Message "Found package $($package.Name) with version $($package.Version) at $($package.Source)." return [System.IO.Path]::GetDirectoryName($package.Source); } } function Copy-SBEContentToSession { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$PackagePath, [Parameter(Mandatory=$true)] [System.Management.Automation.Runspaces.PSSession]$Session, [Parameter(Mandatory=$true)] [string]$DestPath, [Parameter(Mandatory=$false)] [string[]]$ExcludeDirs, [Parameter(Mandatory=$false)] [string[]]$ExcludeFiles, [Parameter(Mandatory=$false)] [switch]$SkipNugetCopy ) $copyItems = @() if ($false -eq $SkipNugetCopy.IsPresent) { try { # Note - this function only works on the seed node as only it will have NugetStore bootstrapped. $sbeConfig = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.SBEConfiguration" $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" } catch { Log-Info -Message $locSbeTxt.SkipNoNugetPath } $sbeConfigDest = $sbeConfig $sbeRoleDest = $sbeRoleNuget } $targetIsCurrentNode = $false [array]$myIP = (Get-NetIPAddress -AddressFamily IPv4).IPAddress if (($Session.ComputerName -in $myIP) -or (($Session.ComputerName -eq $env:ComputerName))) { $targetIsCurrentNode = $true Log-Info -Message $locSbeTxt.TargetIsThisNode } if ($targetIsCurrentNode -and ($PackagePath.TrimEnd("\") -eq $DestPath.TrimEnd("\"))) { Log-Info -Message ($locSbeTxt.SkipSameCopy -f $PackagePath,$Session.ComputerName) } else { Log-Info -Message ($locSbeTxt.WillCopyToSession -f 'SBE package content',$PackagePath,$DestPath,$Session.ComputerName) $copyItems += @{Source=$PackagePath;Destination=$DestPath} } if (-not([string]::IsNullOrWhitespace($sbeConfigDest))) { if ($targetIsCurrentNode -and ($sbeConfig.TrimEnd("\") -eq $sbeConfigDest.TrimEnd("\"))) { Log-Info -Message ($locSbeTxt.SkipSameCopy -f $sbeConfig,$Session.ComputerName) } else { Log-Info -Message ($locSbeTxt.WillCopyToSession -f 'SBEConfiguration',$sbeConfig,$sbeConfigDest,$Session.ComputerName) $copyItems += @{Source=$sbeConfig;Destination=$sbeConfigDest} } } if (-not([string]::IsNullOrWhitespace($sbeRoleDest))) { if ($targetIsCurrentNode -and ($sbeRoleNuget.TrimEnd("\") -eq $sbeRoleDest.TrimEnd("\"))) { Log-Info -Message ($locSbeTxt.SkipSameCopy -f $sbeRoleNuget,$Session.ComputerName) } else { Log-Info -Message ($locSbeTxt.WillCopyToSession -f 'SBE.Role nuget',$sbeRoleNuget,$sbeRoleDest,$Session.ComputerName) $copyItems += @{Source=$sbeRoleNuget;Destination=$sbeRoleDest} } } [array]$exclude = @() if ($ExcludeFiles.Count -ne 0) { $exclude += $ExcludeFiles } if ($ExcludeDirs.Count -ne 0) { $exclude += $ExcludeDirs } $copySuccess = $true foreach ($item in $copyItems) { Log-Info -Message ($locSbeTxt.CopyToSession -f $item.Source,$item.Destination,$Session.ComputerName) -Type Info if ($exclude.Count -gt 0) { [array]$filesToCopy = (Get-Item -Path "$($item.Source)\*" -Exclude $exclude).FullName } else { [array]$filesToCopy = (Get-Item -Path "$($item.Source)\*").FullName } # Make sure target folder exists on remote session $destFolderScript = { if ($false -eq (Test-Path -Path $using:item.Destination)) { $null = New-Item -ItemType Directory -Force -Path $using:item.Destination } } $null = Invoke-Command -Session $Session -ScriptBlock $destFolderScript # Try to copy files over the PSSession, if this fails we will fallback to robocopy via PSDrive try { Copy-Item -Path $filesToCopy -Destination $item.Destination -Recurse -Force -ToSession $Session -ErrorAction Stop } catch { $copySuccess = $false Log-Info -Message ($locSbeTxt.CopyItemFailed -f $PSitem.Exception.Message) -Type Info } } return $copySuccess } function Copy-SBEContentLocalToNode { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$PackagePath, [Parameter(Mandatory=$true)] [string]$TargetNodeName, [Parameter(Mandatory=$true)] [string]$DestPath, [Parameter(Mandatory=$false)] [string[]]$ExcludeDirs, [Parameter(Mandatory=$false)] [string[]]$ExcludeFiles, [Parameter(Mandatory=$false)] [switch]$SkipNugetCopy, [PSCredential]$Credential ) $copyItems = @() # Note - this function only works on the seed node as only it will have NugetStore bootstrapped. $sbeConfig = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.SBEConfiguration" $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" if ($Credential) { Log-Info ("Username is '{0}'" -f $Credential.UserName) } else { # Credential is not needed if the target is the seed node (in this case it is copying to itself) Log-Info "Credential was not provided" } # Check if the copy destination is the current node $targetIsCurrentNode = $false if ($env:ComputerName -eq $TargetNodeName) { Log-Info "Current node ComputerName matched TargetNodeName" $targetIsCurrentNode = $true } else { $thisComputerName = $null $dnsName = ((Resolve-DnsName -Name $TargetNodeName -ErrorAction SilentlyContinue) | Select-Object -First 1) if ($dnsName.NameHost) { # Case when IP is resolved $thisComputerName = ($dnsName.NameHost).Split('.')[0] if ($env:ComputerName -eq $thisComputerName) { Log-Info "Current node ComputerName matched Resolve-DnsName by IP address" $targetIsCurrentNode = $true } } elseif ($dnsName.Name) { # Case when hostname is resolved $thisComputerName = ($dnsName.Name).Split('.')[0] if ($env:ComputerName -eq $thisComputerName) { Log-Info "Current node ComputerName matched Resolve-DnsName by hostname" $targetIsCurrentNode = $true } } else { # No DNS match so try IP address instead [array]$myIP = (Get-NetIPAddress).IPAddress if ($TargetNodeName -in $myIP) { Log-Info "TargetNodeName was matched in the current node myIP list" $targetIsCurrentNode = $true } } } if ($true -eq $targetIsCurrentNode) { $finalDestPath = $DestPath Log-Info "Target node is the current node, so use local path as destination: $finalDestPath" } else { Log-Info "Target node is a remote node, so need to map PSDrive(s)" Get-PSDrive -Name SBE -ErrorAction SilentlyContinue | Remove-PSDrive -Force if ($DestPath -match '^(\w):') { $destRoot = '\\' + $TargetNodeName + '\' + $Matches[1] + '$' } else { throw "Unable to determine proper path to copy SBE. Dest structure is unexpected '$DestPath'." } $systemDriveRoot = '\\' + $TargetNodeName + '\' + ($env:SystemDrive ).Replace(':','$') $retry = $true $maxRetry = 4 $attempt = 0 while ($true -eq $retry) { $attempt++ Log-Info "Map New-PSDrive to '$($destRoot)', attempt '$($attempt)/$($maxRetry)'" try { $destDrv = New-PSDrive -Credential $Credential -Name SBECACHE -PSProvider FileSystem -Root $destRoot -ErrorAction SilentlyContinue } catch { $errMessage = $PSItem.Exception.Message Log-Info "New-PSDrive failed with exception: $($errMessage)" } $found = Get-PSDrive -Name SBECACHE if ($found -and $found.Root -eq $destRoot) { $errMessage = '' $retry = $false } else { if ($attempt -ge $maxRetry) { throw "Failed to map New-PSDrive after '$($attempt)' attempts. Exception: '$($errMessage)'" $retry = $false } Start-Sleep -Seconds 15 } } # Change the destination path to use the mounted drive letter... $finalDestPath = $DestPath -replace '^\w:', $destDrv.Root Log-Info "Changing DestPath from $DestPath to $finalDestPath." if ($destRoot -ne $systemDriveRoot -and $false -eq $SkipNugetCopy.IsPresent) { $retry = $true $maxRetry = 4 $attempt = 0 while ($true -eq $retry) { $attempt++ Log-Info "Map New-PSDrive to '$($systemDriveRoot)', attempt '$($attempt)/$($maxRetry)'" try { $sysDrv = New-PSDrive -Credential $Credential -Name SBESYSROOT -PSProvider FileSystem -Root $systemDriveRoot -ErrorAction SilentlyContinue } catch { $errMessage = $PSItem.Exception.Message Log-Info "New-PSDrive failed with exception: $($errMessage)" } $found = Get-PSDrive -Name SBESYSROOT if ($found -and $found.Root -eq $systemDriveRoot) { $errMessage = '' $retry = $false } else { if ($attempt -ge $maxRetry) { throw "Failed to map New-PSDrive after '$($attempt)' attempts. Exception: '$($errMessage)'" $retry = $false } Start-Sleep -Seconds 15 } } $sbeConfigDest = $sbeConfig.Replace($env:SystemDrive,$sysDrv.Root) $sbeRoleDest = $sbeRoleNuget.Replace($env:SystemDrive,$sysDrv.Root) } elseif ($false -eq $SkipNugetCopy.IsPresent) { Log-Info "Using the destDrv mount to copy Config and Role Nugets." $sbeConfigDest = $sbeConfig.Replace($env:SystemDrive,$destDrv.Root) $sbeRoleDest = $sbeRoleNuget.Replace($env:SystemDrive,$destDrv.Root) } else { Log-Info "Skipping sysDrv mount - we don't need to copy Config or Role Nugets." # This is typical of post-deploy OperationType like "Update" where the SBE.Role nuget is already available on all nodes and the SBEConfiguration nuget is not needed. } } Log-Info -Message ($locSbeTxt.WillCopyToPSDrive -f 'SBE package contents',$finalDestPath,$TargetNodeName) $copyItems += @{Source=$PackagePath;Destination=$finalDestPath} if (-not([string]::IsNullOrWhitespace($sbeConfigDest))) { Log-Info -Message ($locSbeTxt.WillCopyToPSDrive -f 'SBEConfiguration',$sbeConfigDest,$TargetNodeName) $copyItems += @{Source=$sbeConfig;Destination=$sbeConfigDest} } if (-not([string]::IsNullOrWhitespace($sbeRoleDest))) { Log-Info -Message ($locSbeTxt.WillCopyToPSDrive -f 'SBE.Role nuget',$sbeRoleDest,$TargetNodeName) $copyItems += @{Source=$sbeRoleNuget;Destination=$sbeRoleDest} } [string]$exclude = "" if ($ExcludeFiles.Count -ne 0) { $exclude += " /XF $ExcludeFiles" } if ($ExcludeDirs.Count -ne 0) { $exclude += " /XD $ExcludeDirs" } foreach ($item in $copyItems) { Log-Info -Message ($locSbeTxt.CopySBEToNode -f $item.Source,$TargetNodeName,$item.Destination) -Type Info $copyCmd = "robocopy.exe $($item.Source) $($item.Destination) *.* /MIR /NP /R:2 /W:10$exclude" $output = Invoke-Command -ScriptBlock { cmd.exe /c $copyCmd } # Check for exit code. If exit code is greater than 7, an error occurred while peforming the copy operation. if ($LASTEXITCODE -ge 8) { Log-Info -Message ($locSbeTxt.RobocopyFailed -f $LASTEXITCODE) -ConsoleOut -Type Error Log-Info -Message ($output | Out-String).Trim() -ConsoleOut -Type Info if ($destDrv) { $destDrv | Remove-PSDrive -ErrorAction SilentlyContinue } if ($sysDrv) { $sysDrv | Remove-PSDrive -ErrorAction SilentlyContinue } return $false } } if ($destDrv) { $destDrv | Remove-PSDrive -ErrorAction SilentlyContinue } if ($sysDrv) { $sysDrv | Remove-PSDrive -ErrorAction SilentlyContinue } return $true } function Get-SBEHealthCheckParams { [CmdletBinding()] param ( [Parameter()] [CloudEngine.Configurations.EceInterfaceParameters] $ECEParameters, [String] $Tag ) $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" Import-Module "$($sbeRoleNuget)\content\Helpers\SBESolutionExtensionHelper.psm1" -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null $sbePartnerProps = Get-SBEPartnerProperties -SBERoleConfig $ECEParameters.Roles["SBE"].PublicConfiguration $sbeCredList = Get-SBECredentialList -Parameters $ECEParameters $sbeHostData = Get-AllNodesData -BareMetalConfig $ECEParameters.Roles["BareMetal"].PublicConfiguration $params = @{ CredentialList = $sbeCredList HostData = $sbeHostData PartnerProperties = $sbePartnerProps Tag = $Tag } return $params } function Test-SBEPropertiesValid { [CmdletBinding()] param ( [Parameter()] [CloudEngine.Configurations.EceInterfaceParameters] $ECEParameters ) $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" Import-Module "$($sbeRoleNuget)\content\Helpers\SBESolutionExtensionHelper.psm1" -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null $sbePartnerProps = Get-SBEPartnerProperties -SBERoleConfig $ECEParameters.Roles["SBE"].PublicConfiguration Log-Info -Message "Found '$($sbePartnerProps.Count)' PartnerProperties." -Type Info } function Test-SBECredentialsValid { [CmdletBinding()] param ( [Parameter()] [CloudEngine.Configurations.EceInterfaceParameters] $ECEParameters ) $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" Import-Module "$($sbeRoleNuget)\content\Helpers\SBESolutionExtensionHelper.psm1" -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null $sbeCredList = Get-SBECredentialList -Parameters $ECEParameters } function Test-SolutionExtensionModule { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $PackagePath, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $solExtModule = $null Log-Info -Message ($locSbeTxt.SBEPackagePath -f $PackagePath) -Type Info if ($PSSession) { $computername = $PsSession.ComputerName } else { $computername = $env:ComputerName } # Validate the SolutionExtension module using a function from the SBE Role Helper module $sbValidate = { param( [String] [parameter(Mandatory=$true)] $PackagePath, [String] [parameter(Mandatory=$true)] $SbeRoleNuget ) try { Import-Module "$SbeRoleNuget\content\Helpers\SBESolutionExtensionHelper.psm1" -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null $solExtModulePath = Join-Path -Path $PackagePath -ChildPath "Configuration\SolutionExtension" $solExtModule = Initialize-SolutionExtensionModule -SolExtFilePath $solExtModulePath -RequireTag "HealthServiceIntegration" -AssertCertificate return $solExtModule } catch { Write-Output "An exception occurred while validating the SolutionExtension module: " + ($PSItem | Format-List * | Out-String).Trim() } } $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" $solExtModule = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $sbValidate -ArgumentList @($PackagePath, $sbeRoleNuget) } else { Invoke-Command -ScriptBlock $sbValidate -ArgumentList @($PackagePath, $sbeRoleNuget) } if ($null -eq $solExtModule) { Log-Info -Message ($locSbeTxt.NoHeatlhChecks) -Type Info return $false } elseif ($solExtModule -match "An exception occurred") { throw $solExtModule } return $true } function Invoke-TestSBEContentIntegrity { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $SBEMetadataPath, [Parameter(Mandatory=$true)] [string] $SBEContentPath, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) $sbIntegrity = { param( [String] [parameter(Mandatory=$true)] $SBEMetadataPath, [String] [parameter(Mandatory=$true)] $SBEContentPath, [String] [parameter(Mandatory=$true)] $SbeRoleNuget ) try { if (-not(Get-Command -Name Test-SBEContentIntegrity -ErrorAction SilentlyContinue)) { Import-Module "$($SbeRoleNuget)\content\Helpers\SBESolutionExtensionHelper.psm1" -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null } $skipDir = @("IntegratedContent") Test-SBEContentIntegrity -SBEMetadataDirPath $SBEMetadataPath -SBEContentPath $SBEContentPath -IgnoreTopLevelFolder $skipDir } catch { throw $PSItem } } $sbeRoleNuget = Get-ASArtifactPathLite -NugetName "Microsoft.AzureStack.Role.SBE" $result = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $sbIntegrity -ArgumentList @($SBEMetadataPath, $SBEContentPath, $sbeRoleNuget) } else { Invoke-Command -ScriptBlock $sbIntegrity -ArgumentList @($SBEMetadataPath, $SBEContentPath, $sbeRoleNuget) } return $result } function Import-SolutionExtensionModule { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $PackagePath, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Import the SolutionExtension module $solExtModule = (Join-Path -Path $PackagePath -ChildPath "Configuration\SolutionExtension\SolutionExtension.psd1") Log-Info -Message ($locSbeTxt.ModuleToImport -f $solExtModule) -Type Info $sbImport = { param( [String] [parameter(Mandatory=$true)] $SolExtModule ) try { Import-Module $SolExtModule -Force -ErrorAction Stop -Verbose:$false -DisableNameChecking -Global | Out-Null } catch { Write-Output "An error occurred while importing the SolutionExtension module: " + ($PSItem | Format-List * | Out-String).Trim() } } $result = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $sbImport -ArgumentList @($solExtModule) } else { Invoke-Command -ScriptBlock $sbImport -ArgumentList @($solExtModule) } if ($result -match "An exception occurred") { throw $solExtModule } return $true } function New-SBEHealthResultObject { param ( [Parameter(Mandatory=$true)] [string]$TargetName, [Parameter()] [string]$TestName, [Parameter()] [ValidateSet('CRITICAL','WARNING','INFORMATIONAL')] [string]$Severity = 'INFORMATIONAL', [Parameter()] [ValidateSet('SUCCESS', 'FAILURE', 'ERROR')] [string]$Status, [Parameter()] [string]$Description, [Parameter()] [string]$Detail, [Parameter()] [bool]$PopulateAdditionalData = $true ) $name = 'AzStackHci_SBEHealth' $title = 'SBE' if (-not([string]::IsNullOrWhiteSpace($TestName))) { $name += "_$TestName" $title += (" " + $TestName.Replace("-", " ")) } $name += "_$TargetName" if (-not($title.EndsWith(" Health Check"))) { $title += " Health Check" } $params = @{ Name = $name Title = $title DisplayName = $title Severity = $Severity Description = $Description Tags = @{} Remediation = '' TargetResourceID = $TargetName TargetResourceName = $TargetName TargetResourceType = 'SBEHealth' Timestamp = "$([datetime]::UtcNow)" Status = $Status AdditionalData = @{ Source = $TargetName Resource = 'SBEHealth' Detail = $Detail Status = $Status Timestamp = "$([datetime]::UtcNow)" } HealthCheckSource = $ENV:EnvChkrId } $resultObj = New-AzStackHciResultObject @params return $resultObj } function Get-ResultObject { $resultObject = @{ "Name" = "" "DisplayName" = "" "Title"= "" "Description" = "" "Status" = "" "Severity" = "" "Timestamp" = "" "TargetResourceID" = "" "TargetResourceName" = "" "TargetResourceType" = "" "Tags" = @{} "AdditionalData" = @{} "HealthCheckSource" = "" "Remediation" = "" } return $resultObject } function Assert-ResponseSchemaValid { [CmdletBinding()] param ( [PSObject[]]$ResultObject ) $expectedSchema = Get-ResultObject foreach ($item in $ResultObject) { # Assert Name or Title must contain information if ([string]::IsNullOrWhiteSpace($item.Name) -and [string]::IsNullOrWhiteSpace($item.Title)) { $msg = "Both Name and Title properties of this result object are empty" Log-Info -Message $msg -Type Error $item.AdditionalData.NameTitleEmpty = $msg $item.Severity = 'CRITICAL' $item.Status = "Error" } elseif ([string]::IsNullOrWhiteSpace($item.Name)) { $item.Name = $item.Title } elseif ([string]::IsNullOrWhiteSpace($item.Title)) { $item.Title = $item.Name } # Assert response contains expected schema properties foreach ($expectedKey in $expectedSchema.Keys) { if (-not($item.ContainsKey($expectedKey))) { # TODO : Temporary special case to add DisplayName if missing due to this being added after partner communication if ($key -eq "DisplayName") { $item.DisplayName = $item.Title } else { Log-Info -Message "Expected result property '$($expectedKey)' was not found" -Type Warning $item.$expectedKey = "" # TODO : In the future, we should decide how to better handle these cases of missing properties } } } # Assert Status values if ($item.Status -notin @("Success", "Failure", "Error")) { $msg = "Unexpected Status: '$($item.Status)'" Log-Info -Message $msg $item.AdditionalData.StatusDiscrepancy = $msg $item.Status = "Error" } # Assert Severity values if ($item.Severity -notin @('CRITICAL', 'WARNING', 'INFORMATIONAL')) { $msg = "Unexpected Severity: '$($item.Severity)'" $item.AdditionalData.SeverityDiscrepancy = $msg if ($item.Status -eq "Success") { $item.Severity = 'WARNING' Log-Info -Message $msg -Type Warning } else { $item.Severity = 'CRITICAL' Log-Info -Message $msg -Type Error } } # Assert Timestamp is valid if (-not [string]::IsNullOrWhiteSpace($item.Timestamp)) { try { $null = [DateTime]$item.Timestamp } catch { Log-Info -Message "Invalid Timestamp: '$($item.Timestamp)'" -Type Warning if (-not [string]::IsNullOrWhiteSpace($item.AdditionalData.Timestamp)) { try { $null = [DateTime]$item.AdditionalData.Timestamp # AdditionalData.Timestamp is valid, so use it $item.Timestamp = $item.AdditionalData.Timestamp } catch { # Use current time $item.Timestamp = "$([datetime]::UtcNow)" } } else { # Use current time $item.Timestamp = "$([datetime]::UtcNow)" } } } if (-not [string]::IsNullOrWhiteSpace($item.AdditionalData.Timestamp)) { try { $null = [DateTime]$item.AdditionalData.Timestamp } catch { Log-Info -Message "Invalid Timestamp: '$($item.AdditionalData.Timestamp)'" -Type Warning # Timestamp must be valid now, so use it $item.AdditionalData.Timestamp = $item.Timestamp } } } return $ResultObject } Export-ModuleMember -Function Test-* Export-ModuleMember -Function New-SBEHealthResultObject Export-ModuleMember -Function Get-SBEHealthCheckParams Export-ModuleMember -Function Copy-SBEContentLocalToNode Export-ModuleMember -Function Copy-SBEContentToSession Export-ModuleMember -Function Import-SolutionExtensionModule Export-ModuleMember -Function Assert-ResponseSchemaValid Export-ModuleMember -Function Invoke-TestSBEContentIntegrity # SIG # Begin signature block # MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB4bEelPvI0MR0f # Vdg3s9G3J+QpLXPltULxS+a9dQk9rqCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIB97B1ADFg+UlmeGCkl9Lf1v # t2ExsUSkr8hlkTz6hwRpMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAnE9URHVio9V3MvNdSooy5SYgjmVfSPo1GdqYMRu0cg4m7qZgINiXOOSy # 1oKsZdhQPyyjdVCrkPx3fF/CXJJn9WPJ0gqdVjy+gL8W3Z/wYZT6cpv763SQQJBq # ENcRL34pkvNluSxY1A8xZiQwSzXxj2emSjsrlh81V/IYiHZHYmHKw7aKMxvzrY68 # QNwipPUcNZylhX5fzoLqxQSwVUTHa8D6WKnv9PleZYYdSOgV931utb3DwUboVDeU # JxTRR+tA+D6K4O2LYCGLyrrrMoxa5w74MLGkLZ9v+/HRoUaEKNiMZw9IFxSO2rHm # EaNuli4NXItHf+lujCIaGU8uxxqQhKGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC # F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq # hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCKC6TvM1DnsFDrodfWxr0b3Jf7kbzSfUpSAjLCNBkMggIGZ0nWNWzc # GBMyMDI0MTIwNDE1MDQwNC4yNDdaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # TjozNjA1LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB91ggdQTK+8L0AAEAAAH3MA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0 # MDcyNTE4MzEwNloXDTI1MTAyMjE4MzEwNlowgdMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w # ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjM2MDUt # MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0OdHTBNom6/uXKaEKP9r # PITkT6QxF11tjzB0Nk1byDpPrFTHha3hxwSdTcr8Y0a3k6EQlwqy6ROz42e0R5eD # W+dCoQapipDIFUOYp3oNuqwX/xepATEkY17MyXFx6rQW2NcWUJW3Qo2AuJ0HOtbl # SpItQZPGmHnGqkt/DB45Fwxk6VoSvxNcQKhKETkuzrt8U6DRccQm1FdhmPKgDzgc # fDPM5o+GnzbiMu6y069A4EHmLMmkecSkVvBmcZ8VnzFHTDkGLdpnDV5FXjVObAgb # SM0cnqYSGfRp7VGHBRqyoscvR4bcQ+CV9pDjbJ6S5rZn1uA8hRhj09Hs33HRevt4 # oWAVYGItgEsG+BrCYbpgWMDEIVnAgPZEiPAaI8wBGemE4feEkuz7TAwgkRBcUzLg # Q4uvPqRD1A+Jkt26+pDqWYSn0MA8j0zacQk9q/AvciPXD9It2ez+mqEzgFRRsJGL # tcf9HksvK8Jsd6I5zFShlqi5bpzf1Y4NOiNOh5QwW1pIvA5irlal7qFhkAeeeZqm # op8+uNxZXxFCQG3R3s5pXW89FiCh9rmXrVqOCwgcXFIJQAQkllKsI+UJqGq9rmRA # BJz5lHKTFYmFwcM52KWWjNx3z6odwz2h+sxaxewToe9GqtDx3/aU+yqNRcB8w0tS # XUf+ylN4uk5xHEpLpx+ZNNsCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBTfRqQzP3m9 # PZWuLf1p8/meFfkmmDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAN0ajafILeL6S # QIMIMAXM1Qd6xaoci2mOrpR8vKWyyTsL3b83A7XGLiAbQxTrqnXvVWWeNst5YQD8 # saO+UTgOLJdTdfUADhLXoK+RlwjfndimIJT9MH9tUYXLzJXKhZM09ouPwNsrn8YO # LIpdAi5TPyN8Cl11OGZSlP9r8JnvomW00AoJ4Pl9rlg0G5lcQknAXqHa9nQdWp1Z # xXqNd+0JsKmlR8tcANX33ClM9NnaClJExLQHiKeHUUWtqyLMl65TW6wRM7XlF7Y+ # PTnC8duNWn4uLng+ON/Z39GO6qBj7IEZxoq4o3avEh9ba43UU6TgzVZaBm8VaA0w # SwUe/pqpTOYFWN62XL3gl/JC2pzfIPxP66XfRLIxafjBVXm8KVDn2cML9IvRK02s # 941Y5+RR4gSAOhLiQQ6A03VNRup+spMa0k+XTPAi+2aMH5xa1Zjb/K8u9f9M05U0 # /bUMJXJDP++ysWpJbVRDiHG7szaca+r3HiUPjQJyQl2NiOcYTGV/DcLrLCBK2zG5 # 03FGb04N5Kf10XgAwFaXlod5B9eKh95PnXKx2LNBgLwG85anlhhGxxBQ5mFsJGkB # n0PZPtAzZyfr96qxzpp2pH9DJJcjKCDrMmZziXazpa5VVN36CO1kDU4ABkSYTXOM # 8RmJXuQm7mUF3bWmj+hjAJb4pz6hT5UwggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # TjozNjA1LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAb28KDG/xXbNBjmM7/nqw3bgrEOaggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF # AAIFAOr67AMwIhgPMjAyNDEyMDQxNDU2MDNaGA8yMDI0MTIwNTE0NTYwM1owdzA9 # BgorBgEEAYRZCgQBMS8wLTAKAgUA6vrsAwIBADAKAgEAAgIL7AIB/zAHAgEAAgIS # bjAKAgUA6vw9gwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow # CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQA8u4NRbeCo # xhCkd0pG5OpIpdaSE8PwtSLa/X1PHoB+Lo4A+BNUx5VI5H1+0AGMn61ho9i+uHJg # w9iiXBsx9XEjAEC9Yk36p/HN/k6hiWYdhK0rHyaYIc7EYFz34RvBUmL7c5M4vEE3 # agEMeASPRbFq/X0W4XGZB3T8kVyoHR0gBwCgLsrUfSyp86fHvQULcwgTsQjR+Fg1 # xe1tU5qAtPcKaTKY+/sDonajMMWDcFoplxCTC4unOnet23wygGB+zAQZLF0MogtF # bArhxOzXJpK0r9H/pKgG7PLIOTWBY5lMvSB9QguCdVCjhS4T9he/INO3SsyGefbT # wzFTs2sAVbV/MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAH3WCB1BMr7wvQAAQAAAfcwDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgoDZDkzhK # 6ELE30LlRV0PmklkaWsDKH0DziQp2GtcuRwwgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCAh2pjaa3ca0ecYuhu60uYHP/IKnPbedbVQJ5SoIH5Z4jCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB91ggdQTK+8L0AAEA # AAH3MCIEICHPFvVYs5oZoAOL51irkJfC3AbDaLOurx/mm0cPAqaxMA0GCSqGSIb3 # DQEBCwUABIICAMtyQgn6+EoLE+npN2MlLIhQr42mpym1w80lSzMNwvbzupiqZG01 # rVg5paOC5V4wEltX8VYkE2ktIBYkKEX+KJYwwG5wUFLKbslev0oPe8vwffu94b5j # ZnEya85mfnnqS6Ht7HwxdTZw3JnKwxy3N+GKkDu6VkgMOH8Xvb6G/N42Gvqc08ST # cdK4z8/4QRCSmkI1gBvHguOm81GBz44K5v+2senMRV1Cnq5geE61/Xe70p6Ipudy # jZAQGl10bngq0uWq6Un8PR8NtR+R9UTLdIsdSzgpB6SieNmlDTrOCRyHTyDHSOLh # 7PUv3+L4/dpz5/xMg92gFXstAwcKH+QGEtTew5He4xCq2IrF5sXvtMopsBD8KQOM # sW8hkA5kMgeU61monPV5Sv23K1YAT3V41Z0+UVymnW7nirbSCOYIPWmjYXsms1Ud # TBP2hl/3fjTVmhpsCOgt7QmoYygDGSI/hhgSrQ69WME1teHyIpr2fWtm8k9KcBwD # JpBimoPO3SI5XmvcEW1VQDJjKOOKGWWw+sCDS6wsO2w9keCZU2Ie9JqmSK855jE5 # abATt73SDu1M36gMy5dK8joobOKbMoPmTvQ8G8sLJZCsVbrY2ZZDztwCmyk9UJFk # v6CD4gpJkBkJnoI/cTjWgiCOo6oXSKC487uEVoXfQeDvS+vs3c26U4HB # SIG # End signature block |