Support.AksArc.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # Support.AksArc Module # ######################################################################################### <# .SYNOPSIS Imports the latest version of the Azs.Support module from the nuget directory. .PARAMETER AcceptEula If EULA is accepted, the disclaimer will not be shown. .PARAMETER SkipUpdate Skips the automatic update method from being executed. #> param( [CmdletBinding(DefaultParameterSetName = "AcceptEula")] [Parameter(Mandatory = $false , ParameterSetName = "AcceptEula", Position = 1)] [Bool]$AcceptEula = $false, [Parameter(Mandatory = $false, ParameterSetName = "SkipUpdate", Position = 2)] [Bool]$SkipUpdate = $false ) #requires -runasadministrator #requires -version 5.1 #requires -module Moc # Constants for reusability and maintainability $script:KnownIssuesPanicPattern = ".*\(*Client\)\.cleanupVirtualHardDiskNode" $script:CloudAgentServiceName = "wssdcloudagent" $script:NodeAgentServiceName = "wssdnodeagent" function Get-SupportAksArcKnownIssues { <# .SYNOPSIS Returns known issues in the MOC environment. .DESCRIPTION Returns a collection of known issues in the MOC environment, including their test and remediation functions. .OUTPUTS Array of PSObjects containing known issue definitions .EXAMPLE Get-SupportAksArcKnownIssues #> [CmdletBinding()] [OutputType([PSObject[]])] param() $knownIssues = @( @{ 'TestId' = "MOC-0012" 'TestName' = "MOC missing Cloud agent Issues" 'Description' = "Checks for any missing Cloud agent issues in the MOC environment." 'TestFunction' = "Test-SupportAksArcKnownIssues_CheckMissingCloudAgent" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_CheckingMissingCloudAgent" 'Critical' = $true }, @{ 'TestId' = "MOC-0003" 'TestName' = "MOC Cloud Agent Not Running" 'Description' = "Validates that the MOC Cloud Agent service is running on all nodes." 'TestFunction' = "Test-SupportAksArcKnownIssues_MocCloudAgentNotRunning" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_MocCloudAgentNotRunning" 'Critical' = $true }, @{ 'TestId' = "MOC-0011" 'TestName' = "MOC missing Node agent Issues" 'Description' = "Checks for any missing Node agent issues in the MOC environment." 'TestFunction' = "Test-SupportAksArcKnownIssues_CheckMissingNodeAgents" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_CheckMissingNodeAgents" 'Critical' = $true }, @{ 'TestId' = "MOC-0001" 'TestName' = "MOC Not on Latest Patch Version" 'Description' = "Checks if the current MOC version is the latest patch version." 'TestFunction' = "Test-SupportAksArcKnownIssues_MocNotOnLatestPatch" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_MocNotOnLatestPatch" 'Critical' = $false }, @{ 'TestId' = "MOC-0002" 'TestName' = "Null Pointer Dereference in VirtualHardDisk" 'Description' = "Validates MOC Cloud Agent Panics" 'TestFunction' = "Test-SupportAksArcKnownIssues_Panics" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_Panics" 'Critical' = $false }, @{ 'TestId' = "MOC-0013" 'TestName' = "MOC Expired Certificates" 'Description' = "Checks if any MOC certificates are expired." 'TestFunction' = "Test-SupportAksArcKnownIssues_CheckExpiredCertificate" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_CheckExpiredCertificate" 'Critical' = $false }, @{ 'TestId' = "MOC-0004" 'TestName' = "MOC Nodes Not Active" 'Description' = "Checks if any MOC nodes are not in the 'Active' state." 'TestFunction' = "Test-SupportAksArcKnownIssues_MocNodesNotActive" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_MocNodesNotActive" 'Critical' = $false }, @{ 'TestId' = "MOC-0005" 'TestName' = "MOC Nodes Out of Sync with Cluster Nodes" 'Description' = "Validates that all MOC nodes are in sync with the cluster nodes." 'TestFunction' = "Test-SupportAksArcKnownIssues_MocNodesOutOfSyncWithClusterNodes" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_MocNodesOutOfSyncWithClusterNodes" 'Critical' = $false }, @{ 'TestId' = "MOC-0006" 'TestName' = "Multiple MOC Cloud Agent Instances" 'Description' = "Validates that there is only one instance of the MOC Cloud Agent running." 'TestFunction' = "Test-SupportAksArcKnownIssues_MultipleCloudAgentInstances" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_MultipleCloudAgentInstances" 'Critical' = $true }, @{ 'TestId' = "MOC-0007" 'TestName' = "MOC Powershell Stuck in Updating State" 'Description' = "Validates that the MOC Powershell is not stuck in an updating state." 'TestFunction' = "Test-SupportAksArcKnownIssues_StuckInUpdating" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_StuckInUpdating" 'Critical' = $false }, @{ 'TestId' = "MOC-0008" 'TestName' = "Windows Event Log Not Running" 'Description' = "Validates that the Windows Event Log service is running on all nodes." 'TestFunction' = "Test-SupportAksArcKnownIssues_EventLogNotRunning" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_EventLogNotRunning" 'Critical' = $false }, @{ 'TestId' = "MOC-0009" 'TestName' = "Gallery Image Stuck in Deleting State" 'Description' = "Validates that no gallery images are stuck in deleting state." 'TestFunction' = "Test-SupportAksArcKnownIssues_GalleryImageStuckInDeleting" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_GalleryImageStuckInDeleting" 'Critical' = $false }, @{ 'TestId' = "MOC-0010" 'TestName' = "Virtual Machine Stuck in Pending State" 'Description' = "Validates if the Virtual Machine is stuck in a pending state." 'TestFunction' = "Test-SupportAksArcKnownIssues_VirtualMachineStuckInPending" 'RemediationFunction' = "Invoke-SupportAksArcRemediation_VirtualMachineStuckInPending" 'Critical' = $false } ) | ForEach-Object { New-Object -TypeName PSObject -Property $_ } return $knownIssues } function Test-SupportAksArcKnownIssues { <# .SYNOPSIS Tests for known issues in the MOC environment. .DESCRIPTION Executes tests for known issues in the MOC environment. If a TestId is provided, only that specific test will be run. Otherwise, all known issue tests are executed. .PARAMETER TestId Optional. The specific test ID to run. If not provided, all tests will be executed. .OUTPUTS Array of test results .EXAMPLE Test-SupportAksArcKnownIssues .EXAMPLE Test-SupportAksArcKnownIssues -TestId "MOC-0001" #> [CmdletBinding()] [OutputType([PSObject[]])] param( [ValidatePattern("^MOC-\d{4}$")] [string]$TestId ) try { $knownIssues = Get-SupportAksArcKnownIssues $results = @() foreach ($issue in $knownIssues) { if ([string]::IsNullOrWhiteSpace($TestId) -or ($issue.TestId -eq $TestId)) { Write-Host "Running test: $($issue.TestName) with ID: $($issue.TestId)" Write-Verbose "Invoking test function for $($issue.TestName)..." try { $result = & $issue.TestFunction Write-Verbose "Test result for $($issue.TestName): $($result.Status) - $($result.Message)" if ($result.Status -ne "Passed" -and $issue.Critical) { Write-Error "Critical Test $($issue.TestName) failed: $($result.Message)" return } $results += $result } catch { Write-Warning "Test $($issue.TestId) failed with error: $_" $results += [PSCustomObject]@{ TestName = $issue.TestName Status = "Error" Message = "Test execution failed: $_" } } } } return $results } catch { Write-Error "Failed to execute known issue tests: $_" throw } } function Invoke-SupportAksArcRemediation { <# .SYNOPSIS Remediates known issues in the MOC environment. .DESCRIPTION Executes remediation for known issues in the MOC environment. If a TestId is provided, only that specific issue will be remediated. Otherwise, all failed tests will be remediated. .PARAMETER TestId Optional. The specific issue ID to remediate. If not provided, all failed tests will be remediated. .OUTPUTS None .EXAMPLE Invoke-SupportAksArcRemediation .EXAMPLE Invoke-SupportAksArcRemediation -TestId "MOC-0001" #> [CmdletBinding()] param( [ValidatePattern("^MOC-\d{4}$")] [string]$TestId ) try { $knownIssues = Get-SupportAksArcKnownIssues foreach ($issue in $knownIssues) { if ([string]::IsNullOrWhiteSpace($TestId) -or ($issue.TestId -eq $TestId)) { Write-Verbose "Checking issue: $($issue.TestName) with ID: $($issue.TestId)" try { $testResult = & $issue.TestFunction Write-Verbose "Test result for $($issue.TestName): $($testResult.Status) - $($testResult.Message)" if ($testResult.Status -ne "Passed") { Write-Host "Remediation for $($issue.TestName) is required." Write-Verbose "Invoking remediation for $($issue.TestName)..." if (& $issue.RemediationFunction) { Write-Host "Remediation for $($issue.TestName) succeeded." } else { Write-Warning "Remediation for $($issue.TestName) failed." } } else { Write-Host "No remediation required for $($issue.TestName)." } } catch { Write-Error "Failed to remediate $($issue.TestName): $_" } } } } catch { Write-Error "Failed to execute remediation: $_" throw } } function Test-SupportAksArcKnownIssues_Panics { <# .SYNOPSIS Tests for known MOC Cloud Agent panics. .DESCRIPTION Validates if any known panics exist in the MOC Cloud Agent logs. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_Panics #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC Cloud Agent Panics" Status = "Passed" Message = "No panics found in the MOC Cloud Agent." } try { $panics = Get-MocCloudAgentPanic -ErrorAction Stop if ($panics.Count -gt 0) { $result.Status = "Failed" $result.Message = "Panics found in the MOC Cloud Agent: $($panics | ConvertTo-Json -Compress)" } } catch { Write-Error "Failed to check for panics: $_" throw } return $result } function Get-MocCloudAgentPanic { <# .SYNOPSIS Retrieves panic information from MOC Cloud Agent logs. .DESCRIPTION Searches through MOC Cloud Agent log files for known panic patterns and returns details about any panics found. .OUTPUTS Array of panic information objects .EXAMPLE Get-MocCloudAgentPanic #> [CmdletBinding()] [OutputType([PSObject[]])] param() try { $panicLocation = [IO.Path]::Combine((Get-MocConfig).cloudConfigLocation, "log") $matchedPanics = @() Get-ChildItem -Path $panicLocation -Filter "panic*" -ErrorAction Stop | Sort-Object LastWriteTime -Descending | Select-Object -First 10 | ForEach-Object { Write-Verbose "Analyzing panic file: $($_.FullName)" $panicDetails = Get-PanicDetails -FilePath $_.FullName if ($panicDetails.StackTrace -match $script:KnownIssuesPanicPattern) { Write-Verbose "Found known panic pattern in $($_.Name)" $matchedPanics += $panicDetails } } return $matchedPanics } catch { Write-Error "Failed to retrieve panic information: $_" throw } } function Get-PanicDetails { <# .SYNOPSIS Extracts detailed information from a panic log file. .DESCRIPTION Parses a panic log file to extract structured information about the panic, including message, timestamp, process ID, goroutine ID, and stack trace. .PARAMETER FilePath The path to the panic log file to analyze. .OUTPUTS PSObject containing structured panic information .EXAMPLE Get-PanicDetails -FilePath "path/to/panic.log" #> [CmdletBinding()] [OutputType([PSObject])] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.IO.FileInfo]$FilePath ) try { $panicInfo = @{ PanicMessage = "" Timestamp = "" PID = "" GoroutineID = "" StackTrace = @() } $lines = Get-Content -Path $FilePath -ErrorAction Stop foreach ($line in $lines) { switch -Regex ($line) { '^Panic:\s+(.*)' { $panicInfo.PanicMessage = $matches[1].Trim() } '^Time:\s+(.*)' { $panicInfo.Timestamp = $matches[1].Trim() } '^PID:\s+(\d+)' { $panicInfo.PID = $matches[1].Trim() } '^goroutine\s+(\d+)\s+\[.*\]:' { $panicInfo.GoroutineID = $matches[1].Trim() } default { $panicInfo.StackTrace += $line.Trim() } } } return [PSCustomObject]$panicInfo } catch { Write-Error "Failed to extract panic details from $FilePath : $_" throw } } function Invoke-SupportAksArcRemediation_Panics { <# .SYNOPSIS Remediates MOC Cloud Agent panics. .DESCRIPTION Updates the MOC version to address panic issues. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_Panics #> [CmdletBinding()] [OutputType([bool])] param() if (-not (Test-AzureLocal)) { Write-Verbose "Remediation for panics is only applicable to Azure Local Solution." return $true } try { Write-Verbose "Remediating MOC Cloud Agent Panics..." return Invoke-SupportAksArcRemediation_MocNotOnLatestPatch } catch { Write-Error "Failed to remediate panics: $_" return $false } } function Invoke-UpdateMocHotFix { <# .SYNOPSIS Updates MOC to the latest hotfix version. .DESCRIPTION Checks for the latest patch version of MOC and updates it. .OUTPUTS Boolean indicating update success .EXAMPLE Invoke-UpdateMocHotFix #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Updating MOC to the latest hotfix version..." $nextPatchVersion = Get-NextPatchVersionOfMoc Update-Moc -Version $nextPatchVersion -skipValidationCheck -ErrorAction Stop return $true } catch { Write-Error "Failed to update MOC hotfix: $_" return $false } } function Invoke-UpdateMocMinorRelease { <# .SYNOPSIS Updates MOC to the latest minor release. .DESCRIPTION Checks for the next minor version of MOC and updates it. .OUTPUTS Boolean indicating update success .EXAMPLE Invoke-UpdateMocMinorRelease #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Updating MOC to the next minor version..." $nextMinorVersion = Get-NextMinorVersionofMoc Update-Moc -Version $nextMinorVersion -skipValidationCheck -ErrorAction Stop return $true } catch { Write-Error "Failed to update MOC patch release: $_" return $false } } function Get-NextPatchVersionOfMoc { <# .SYNOPSIS Gets the next available patch version for the current MOC minor version. .DESCRIPTION Retrieves the latest available patch version for the current MOC minor version. .OUTPUTS String containing the version number .EXAMPLE Get-NextPatchVersionOfMoc #> [CmdletBinding()] [OutputType([string])] param() try { $version = Get-MocVersion return Get-LatestVersionOfMoc -version $version -ErrorAction Stop } catch { Write-Error "Failed to get next patch version: $_" throw } } function Get-NextMinorVersionofMoc { <# .SYNOPSIS Gets the next available minor version for MOC. .DESCRIPTION Increments the current minor version by 1 and returns the latest patch version for that minor version. .OUTPUTS String containing the version number .EXAMPLE Get-NextMinorVersionofMoc #> [CmdletBinding()] [OutputType([string])] param() $version = Get-MocVersion try { $versions = $version.Split('.') | Select-Object -First 3 $versions[1] = [int]$versions[1] + 1 $newVersion = $versions -join '.' return Get-LatestVersionOfMoc -version $newVersion -ErrorAction Stop } catch { # If unable to get the next minor version, fallback to the current version return $version } } function Get-LatestVersionOfMoc { <# .SYNOPSIS Gets the latest available version for a given MOC version prefix. .DESCRIPTION Searches the manifest cache for the latest version matching the specified version prefix. .PARAMETER version The version prefix to match against (e.g., "1.2" to find the latest 1.2.x version) .OUTPUTS String containing the complete version number .EXAMPLE Get-LatestVersionOfMoc -version "1.2" #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$version ) try { $versionSelectSubString = ($version.Split('.') | Select-Object -First 2) -join '.' $catalog = Get-Content -Path (Get-MocConfig).manifestCache -ErrorAction Stop | ConvertFrom-Json # Sorting is not required, as we expect the release to be ordered by version from latest to oldest $matchingVersion = $catalog.ProductStreamRefs[0].ProductReleases | Where-Object { $_.Version -match "^$([regex]::Escape($versionSelectSubString))\." } | Select-Object -Property Version | ForEach-Object { [version]$_.Version} | Sort-Object -Descending | Select-Object -First 1 if (-not $matchingVersion) { throw "No matching version found in manifest" } return $matchingVersion.ToString() } catch { Write-Error "Failed to get latest version: $_" throw } } function Test-SupportAksArcKnownIssues_MultipleCloudAgentInstances { <# .SYNOPSIS Tests for multiple running instances of the MOC Cloud Agent. .DESCRIPTION Checks cluster nodes for multiple running instances of the MOC Cloud Agent service. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_MultipleCloudAgentInstances #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Multiple MOC Cloud Agent Instances" Status = "Passed" Message = "No multiple instances of MOC Cloud Agent found." } try { if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } $instances = Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { Get-Service $using:script:CloudAgentServiceName } } | Where-Object { $_.Status -eq 'Running' } | Select-Object -Unique -Property PSComputerName if ($instances.Count -gt 1) { $result.Status = "Failed" $result.Message = "Multiple instances of MOC Cloud Agent found: $($instances.Count)" } } catch { Write-Error "Failed to check for multiple instances: $_" throw } return $result } function Invoke-SupportAksArcRemediation_MultipleCloudAgentInstances { <# .SYNOPSIS Remediates multiple MOC Cloud Agent instances. .DESCRIPTION Stops all running instances and ensures only one instance is running on the correct node. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_MultipleCloudAgentInstances #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating multiple MOC Cloud Agent instances..." if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, no remediation needed" return $true } $instances = Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { Get-Service $using:script:CloudAgentServiceName } } | Where-Object { $_.Status -eq 'Running' } | Select-Object -Unique if ($instances.Count -eq 1) { Write-Verbose "Only one instance running, no remediation needed" return $true } Write-Verbose "Stopping all MOC Cloud Agent instances..." Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { Stop-Service $using:script:CloudAgentServiceName -Force } } Write-Verbose "Starting cluster group to ensure MOC Cloud Agent runs on correct node..." Start-ClusterGroup (Get-MocConfig).clusterRoleName return $true } catch { Write-Error "Failed to remediate multiple instances: $_" return $false } } function Test-SupportAksArcKnownIssues_StuckInUpdating { <# .SYNOPSIS Tests if MOC is stuck in updating state. .DESCRIPTION Checks if the MOC install state is stuck in "Updating". .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_StuckInUpdating #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC Powershell Not Stuck in Updating" Status = "Passed" Message = "MOC Powershell is not stuck in updating state." } try { $agentStatus = (Get-MocConfig).InstallState if ($agentStatus -eq "Updating") { $result.Status = "Failed" $result.Message = "MOC Powershell is stuck in updating state." } } catch { Write-Error "Failed to check updating state: $_" throw } return $result } function Invoke-SupportAksArcRemediation_CleanupStaleDirectories { <# .SYNOPSIS Cleans up stale MOC entity directories. .DESCRIPTION Removes any unexpected or outdated directories from the MOC configuration location. This cleanup is needed when there are many files and directories that need to be purged as part of remediation. .OUTPUTS None .EXAMPLE Invoke-SupportAksArcRemediation_CleanupStaleDirectories #> [CmdletBinding()] param() Write-Verbose "Cleaning up MOC entity directories..." try { Set-Location (Get-MocConfig).cloudConfigLocation -ErrorAction Stop [string[]]$expectedNames = @( "certificate", "cluster", "container", "galleryimage", "group", "identity", "key", "keyvault", "loadbalancer", "location", "macpool", "networkinterface", "node", "role", "roleassignment", "secret", "virtualharddisk", "virtualmachine", "virtualmachineimage", "virtualnetwork" ) # Create a HashSet for efficient lookups $set = [System.Collections.Generic.HashSet[string]]::new($expectedNames) # Find all log and versioned directories $subDirs = @() $logDir = Get-ChildItem -Directory -Filter "log" -ErrorAction Stop if ($null -eq $logDir) { Write-Error "Log directory not found in expected location" return } $subDirs += $logDir.FullName # Get version-specific directories $versionDirs = Get-ChildItem -Directory -Filter "v0*" -ErrorAction Stop $subDirs += $versionDirs.FullName # Add log directories from version directories foreach($versionDir in $versionDirs) { $filterPath = Join-Path -Path $versionDir -ChildPath "log" $childDir = Get-ChildItem -Directory -Filter $filterPath -ErrorAction SilentlyContinue if ($null -ne $childDir) { $subDirs += $childDir.FullName } } # Clean up entity folders in each directory foreach($subDir in $subDirs) { Write-Verbose "Cleaning up entity directories in $subDir" Set-Location $subDir -ErrorAction Stop $entityDirs = Get-ChildItem -Directory -ErrorAction Stop foreach($dir in $entityDirs) { if (-not $set.Contains($dir.Name)) { Write-Verbose "Skipping non-entity directory: $($dir.FullName)" continue } try { Write-Verbose "Removing entity directory: $($dir.FullName)" Remove-Item -Path $dir.FullName -Recurse -Force -ErrorAction Stop } catch { Write-Warning "Failed to remove $($dir.FullName): $_" } } } } catch { Write-Error "Failed to cleanup stale directories: $_" } } function Invoke-SupportAksArcRemediation_StuckInUpdating { <# .SYNOPSIS Remediates MOC stuck in updating state. .DESCRIPTION Sets the MOC install state to UpdateFailed to allow future updates. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_StuckInUpdating #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating MOC Powershell stuck in updating state..." Invoke-SupportAksArcRemediation_CleanupStaleDirectories Write-Verbose "Cleaning up HostAgent on upgrade hang" # Stop all mochostagent processes to ensure we remove handles to the service Get-ClusterNode | % { Invoke-Command -ComputerName $_.Name -ScriptBlock { hostname Stop-Process -Name mochostagent -Force } } # Initiate deletion of MocHostAgent service from each node. Get-ClusterNode | % { Invoke-Command -ComputerName $_.Name -ScriptBlock { hostname $service = Get-WmiObject -Class Win32_Service -Filter "Name='mochostagent'" if ($null -ne $service) { $service.delete() | Out-Null } } } Write-Verbose "Updating MOC install state to UpdateFailed..." Import-Module Moc Set-MocConfigValue -name "installState" -value ([InstallState]::UpdateFailed) -ErrorAction Stop return $true } catch { Write-Error "Failed to remediate updating state: $_" return $false } } function Test-SupportAksArcKnownIssues_MocNodesOutOfSyncWithClusterNodes { <# .SYNOPSIS Tests if MOC nodes are out of sync with cluster nodes. .DESCRIPTION Validates that all MOC nodes are in sync with the cluster nodes. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_MocNodesOutOfSyncWithClusterNodes #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC Nodes Sync with Cluster Nodes" Status = "Passed" Message = "All MOC nodes are in sync with cluster nodes." } if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } try { Import-Module Moc $testResults = Test-MocNodesMatchFailoverClusterNodes -Cluster (Get-Cluster).Name $failedResult = $testResults | Where-Object { $_.Status -eq "FAILURE" } if ($failedResult) { $result.Status = "Failed" $result.Message = $failedResult.Description } } catch { Write-Error "Failed to check node sync: $_" throw } return $result } function Invoke-SupportAksArcRemediation_MocNodesOutOfSyncWithClusterNodes { <# .SYNOPSIS Remediates MOC nodes that are out of sync with cluster nodes. .DESCRIPTION Synchronizes MOC nodes with the cluster nodes to ensure consistency. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_MocNodesOutOfSyncWithClusterNodes #> [CmdletBinding()] [OutputType([bool])] param() return Invoke-SupportAksArcRemediation_MocNodesNotActive } function Test-SupportAksArcKnownIssues_MocCloudAgentNotRunning { <# .SYNOPSIS Tests if MOC Cloud Agent is running. .DESCRIPTION Validates that the MOC Cloud Agent service is running on all nodes. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_MocCloudAgentNotRunning #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC Cloud Agent Running" Status = "Passed" Message = "MOC Cloud Agent is running " } if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } if ((Get-ClusterResource -Name "MOC Cloud Agent Service").State -eq "Failed" -or (Get-ClusterResource -Name "MOC Cloud Agent Service").State -eq "Offline") { $result.Status = "Failed" $result.Message = "MOC Cloud Agent is not running." } return $result } function Invoke-SupportAksArcRemediation_MocCloudAgentNotRunning { <# .SYNOPSIS Remediates MOC Cloud Agent not running. .DESCRIPTION Starts the MOC Cloud Agent service on all nodes to ensure it is running. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_MocCloudAgentNotRunning #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Starting MOC Cloud Agent service on all nodes..." Start-ClusterResource -Name "MOC Cloud Agent Service" -ErrorAction Stop return $true } catch { # If Start-ClusterResource has failed, Its mostly the service is crashing return Invoke-SupportAksArcRemediation_Panics } } function Test-SupportAksArcKnownIssues_EventLogNotRunning { <# .SYNOPSIS Tests if MOC Event Log is running. .DESCRIPTION Validates that the Windows Event Log service is running on all nodes. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_EventLogNotRunning #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Windows Event Log Running" Status = "Passed" Message = "Windows Event Log is running " } if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { # Check if EventLog service is running $service = Get-Service -Name "EventLog" -ErrorAction SilentlyContinue if ($null -eq $service) { return $false # Service does not exist } return $service.Status -eq "Running" } | ForEach-Object { if (-not $_) { $result.Status = "Failed" $result.Message = "Windows Event Log Service is not running on node: $($_.Name)" } } } return $result } function Invoke-SupportAksArcRemediation_EventLogNotRunning { <# .SYNOPSIS Remediates Windows Event Log not running. .DESCRIPTION Starts the Windows Event Log service on all nodes to ensure it is running. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_EventLogNotRunning #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Starting Windows Event Log service on all nodes..." Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { Start-Service -Name "EventLog" -ErrorAction Stop } } return $true } catch { Write-Error "Failed to remediate Event Log not running: $_" return $false } } function Test-SupportAksArcKnownIssues_GalleryImageStuckInDeleting { <# .SYNOPSIS Tests if any gallery images are stuck in deleting state. .DESCRIPTION Validates that no gallery images are stuck in deleting state. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_GalleryImageStuckInDeleting #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Gallery Image Stuck In Deleting" Status = "Passed" Message = "No gallery images are stuck in deleting state." } # Check for any gallery images in deleting state $galImages = (Get-MocGalleryImage -location MocLocation) $stuckImages = $galImages | Where-Object { $_.properties.statuses.ProvisionState -match "DELETE_FAILED" } if ($stuckImages) { $result.Status = "Failed" $result.Message = "The gallery images are stuck in deleting state and need remediation: $($stuckImages.Name -Join ".")" } return $result } function Invoke-SupportAksArcRemediation_GalleryImageStuckInDeleting { <# .SYNOPSIS Remediates gallery images stuck in deleting state. .DESCRIPTION Deletes gallery images that are stuck in deleting state. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_GalleryImageStuckInDeleting #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating gallery images stuck in deleting state..." $galImages = (Get-MocGalleryImage -location MocLocation) $stuckImages = $galImages | Where-Object { $_.properties.statuses.ProvisionState -match "DELETE_FAILED" } foreach ($image in $stuckImages) { Write-Verbose "Deleting stuck gallery image: $($image.name)" try { # This shoud pass. Remove-MocGalleryImage -Name $image.name -Location MocLocation } catch { # If they are on 2411 or less, this would fail if ($_.Exception.Message -match "is missing from nodeEntityMap: Failed") { Write-Warning "Gallery image $($image.name) is still in deleting state, trying next remediation" Invoke-SupportAksArcRemediation_MocNotOnLatestPatch # Re-attempt the mitigation Remove-MocGalleryImage -Name $image.name -Location MocLocation } else { Write-Error "Failed to delete gallery image $($image.name): $_" return $false } <#Do this if a terminating exception happens#> } } return $true } catch { Write-Error "Failed to remediate gallery images stuck in deleting state: $_" return $false } } function Test-SupportAksArcKnownIssues_VirtualMachineStuckInPending { <# .SYNOPSIS Tests if any virtual machines are stuck in pending state. .DESCRIPTION Validates that no virtual machines are stuck in pending state. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_VirtualMachineStuckInPending #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Virtual Machine Stuck In Pending" Status = "Passed" Message = "No virtual machines are stuck in pending state." } # Check for any virtual machines in pending state $groups = Get-MocGroup -Location MocLocation $groups | ForEach-Object { $group = $_ # Check if any VMs are in Delete Failed State # Check if the VMs are stuck in Pending State $vms = (Get-MocVirtualMachine -group $group.Name) $stuckVMs = $vms | Where-Object { $_.properties.statuses.ProvisionState -match "DELETE_FAILED" -and $_.properties.statuses.Error -match "Pending State" } if ($stuckVMs) { $result.Status = "Failed" $result.Message = "The virtual machines are stuck in pending state and need remediation: $($stuckVMs.Name -Join ', ')" } } return $result } function Invoke-SupportAksArcRemediation_VirtualMachineStuckInPending { <# .SYNOPSIS Remediates virtual machines stuck in pending state. .DESCRIPTION Deletes virtual machines that are stuck in pending state. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_VirtualMachineStuckInPending #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating virtual machines stuck in pending state..." $currentVersion = Get-MocVersion if ($currentVersion -like "1.10.*" -or $currentVersion -like "1.9.*") { # Jump to 1.11.x $jumpToVersion = Get-LatestVersionOfMoc -version "1.11.2.10205" -ErrorAction Stop Update-Moc -Version $jumpToVersion -ErrorAction Stop } else { Write-Verbose "Simply apply hotfix version" Invoke-SupportAksArcRemediation_MocNotOnLatestPatch } return $true } catch { Write-Error "Failed to remediate virtual machines stuck in pending state: $_" return $false } } function Test-SupportAksArcKnownIssues_MocNotOnLatestPatch { <# .SYNOPSIS Checks if the current MOC version is the latest patch version. .DESCRIPTION Compares the current installed MOC version with the latest available patch version, ensuring the minor versions are the same and patch versions are different. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_MocNotOnLatestPatch #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC is on Latest Patch Version" Status = "Passed" Message = "MOC is on the latest patch version." } try { $currentVersion = Get-MocVersion $latestVersion = Get-NextPatchVersionOfMoc if ([Version]$currentVersion -lt [Version]$latestVersion) { $result.Status = "Failed" $result.Message = "MOC is not on the latest patch version. Current: $currentVersion, Latest: $latestVersion" } } catch { Write-Error "Failed to check latest patch version: $_" throw } return $result } function Invoke-SupportAksArcRemediation_MocNotOnLatestPatch { <# .SYNOPSIS Remediates when MOC is not on the latest patch version. .DESCRIPTION Updates MOC to the latest available patch version. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_MocNotOnLatestPatch #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating: Updating MOC to the latest patch version..." Invoke-SupportAksArcRemediation_CleanupStaleDirectories return Invoke-UpdateMocHotFix } catch { Write-Error "Failed to remediate MOC not on latest patch: $_" return $false } } function Test-SupportAksArcKnownIssues_MocNodesNotActive { <# .SYNOPSIS Checks if any MOC nodes are not in the 'Active' state. .DESCRIPTION Validates that all MOC nodes are in the 'Active' state. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_MocNodesNotActive #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate MOC Nodes Not Active" Status = "Passed" Message = "All MOC nodes are in the 'Active' state." } try { $nodes = Get-MocNode -Location MocLocation $inactiveNodes = $nodes | Where-Object { $_.properties.statuses.RunningState -ne "Active" } if ($inactiveNodes) { $result.Status = "Failed" $result.Message = "The following MOC nodes are not in 'Active' state: $($inactiveNodes.Name -Join ', ')" } } catch { Write-Error "Failed to check MOC node states: $_" throw } return $result } function Invoke-SupportAksArcRemediation_MocNodesNotActive { <# .SYNOPSIS Remediates MOC nodes that are not in the 'Active' state. .DESCRIPTION Attempts to bring MOC nodes that are not in the 'Active' state back to 'Active' by restarting the node service. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_MocNodesNotActive #> [CmdletBinding()] [OutputType([bool])] param() try { $nodes = Get-MocNode -Location MocLocation $inactiveNodes = $nodes | Where-Object { $_.properties.statuses.RunningState -ne "Active" } if (-not $inactiveNodes) { Write-Verbose "All MOC nodes are already in 'Active' state." return $true } Repair-Moc return $true } catch { Write-Error "Failed to remediate MOC nodes not active: $_" return $false } } function Test-SupportAksArcKnownIssues_CheckMissingNodeAgents { <# .SYNOPSIS Checks for any MOC nodes that have been removed from the cluster but still exist in MOC. .DESCRIPTION Validates that all MOC nodes correspond to current cluster nodes. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_CheckMissingNodeAgents #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Missing MOC Node Agents" Status = "Passed" Message = "No missing MOC node agents found." } if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } try { Import-Module Moc # Check if all the cluster nodes are added to MOC $tresult = Test-SupportAksArcKnownIssues_MocNodesOutOfSyncWithClusterNodes if ($tresult.Status -eq "Failed") { # If the test fails, check if all the nodes have node agent service installed Get-ClusterNode | ForEach-Object { $service = Get-Service -Name $script:NodeAgentServiceName -ComputerName $_.Name -ErrorAction SilentlyContinue if ($null -eq $service) { $result.Status = "Failed" $result.Message = "Node Agent service is missing on node: $($_.Name)" return } } } } catch { Write-Error "Failed to check missing node agents: $_" throw } return $result } function Invoke-SupportAksArcRemediation_CheckMissingNodeAgents { <# .SYNOPSIS Remediates any MOC nodes that are missing from the cluster. .DESCRIPTION Removes any MOC nodes that do not correspond to current cluster nodes. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_CheckMissingNodeAgents #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating missing MOC node agents..." if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, no remediation needed" return $true } Import-Module moc Import-Module Moc # If we come here, all Nodes are in MOC, but some nodes are missing the Node Agent service Get-ClusterNode | ForEach-Object { $nodeName = $_.Name.ToLowerInvariant() $service = Get-Service -Name $script:NodeAgentServiceName -ErrorAction SilentlyContinue -ComputerName $nodeName if (!$service) { Write-Verbose "Node Agent service is missing on node: $nodeName, Fixing the service" # If service is missing, try { Remove-MocIdentity -Name $nodeName } catch { # If the identity is already removed, we can ignore the error if ($_.Exception.Message -match "NotFound") { Write-Verbose "Node $nodeName is already removed from MOC, proceeding to re-install Node Agent" } else { throw $_ } } Install-NodeAgent -NodeName $nodeName } } return $true } catch { Write-Error "Failed to remediate missing node agents: $_" return $false } } function Test-SupportAksArcKnownIssues_CheckMissingCloudAgent { <# .SYNOPSIS Checks for any missing MOC cloud agents. .DESCRIPTION Validates that all MOC cloud agents are present and accounted for. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_CheckMissingCloudAgent #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Missing MOC Cloud Agents" Status = "Passed" Message = "No missing MOC cloud agents found." } if (-not (Test-FailoverCluster)) { Write-Verbose "Not a failover cluster environment, skipping test" return $result } try { Import-Module Moc # If the test fails, check if all the nodes have node agent service installed Get-ClusterNode | ForEach-Object { $service = Get-Service -Name $script:CloudAgentServiceName -ComputerName $_.Name -ErrorAction SilentlyContinue if ($null -eq $service) { $result.Status = "Failed" $result.Message = "Cloud Agent service is missing on node: $($_.Name)" return } } } catch { Write-Error "Failed to check missing cloud agents: $_" throw } return $result } function Invoke-SupportAksArcRemediation_CheckingMissingCloudAgent { <# .SYNOPSIS Checks for missing MOC cloud agent services. .DESCRIPTION Verifies that all MOC cloud agent services are installed and running. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_CheckingMissingCloudAgent #> [CmdletBinding()] [OutputType([bool])] param() if ((Test-AzureLocal) -eq $false) { Write-Verbose "Not an Azure Local environment, skipping cloud agent service check" return $true } Import-Module moc Import-Module Moc try { Write-Verbose "Checking for missing cloud agent services..." Get-ClusterNode | ForEach-Object { $nodeName = $_.Name.ToLowerInvariant() Write-Verbose "Checking for missing cloud agent services on node: $nodeName" $service = Get-Service -Name $script:CloudAgentServiceName -ErrorAction SilentlyContinue -ComputerName $_.Name if (!$service) { Write-Verbose "Cloud Agent service is missing on node:$nodeName, Fixing the service" Install-CloudAgentOnNode -nodeName $nodeName -startupType "Manual" } } return $true } catch { Write-Error "Failed to check missing cloud agent services: $_" return $false } } function Test-SupportAksArcKnownIssues_CheckExpiredCertificate { <# .SYNOPSIS Checks for expired certificates in the MOC environment. .DESCRIPTION Validates that all certificates in the MOC environment are valid and not expired. .OUTPUTS Test result object containing status and message .EXAMPLE Test-SupportAksArcKnownIssues_CheckExpiredCertificate #> [CmdletBinding()] [OutputType([PSObject])] param() $result = [PSCustomObject]@{ TestName = "Validate Expired Certificates" Status = "Passed" Message = "No expired certificates found." } try { Get-MocLocation | Out-Null } catch { if ($_.Exception.Message -match "authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown" -or $_.Exception.Message -match "tls: bad certificate") { $result.Status = "Failed" $result.Message = "One or more certificates are expired or invalid, causing authentication failures." } else { Write-Error "Failed to check expired certificates: $_" throw $_ } } return $result } function Invoke-SupportAksArcRemediation_CheckExpiredCertificate { <# .SYNOPSIS Remediates expired certificates in the MOC environment. .DESCRIPTION Renews or replaces any expired certificates in the MOC environment. .OUTPUTS Boolean indicating remediation success .EXAMPLE Invoke-SupportAksArcRemediation_CheckExpiredCertificate #> [CmdletBinding()] [OutputType([bool])] param() try { Write-Verbose "Remediating expired certificates..." Repair-Moc return $true } catch { Write-Error "Failed to remediate expired certificates: $_" return $false } } function Invoke-SupportAksArcRemediation_RotateResourceBridgeCredentials { <# .SYNOPSIS Rotates the Resource Bridge credentials. .DESCRIPTION Rotates the Resource Bridge credentials to ensure secure access. .OUTPUTS Boolean indicating rotation success .EXAMPLE Invoke-SupportAksArcRemediation_RotateResourceBridgeCredentials #> [CmdletBinding()] [OutputType([bool])] param() if ((Test-AzureLocal) -eq $false) { Write-Verbose "Not an Azure Local environment, skipping Resource Bridge credential rotation" return $true } try { Write-Verbose "Rotating Resource Bridge credentials..." $archciconfig = Get-ArcHciConfig $workingDir = $archciconfig.workingDir $arcConfigPath = "$($workingDir)\hci-appliance.yaml" $arcTokenFilePath = $archciconfig.KvaTokenLocation $arcKubeConfigFilePath = "$($workingDir)\kubeconfig" $applianceSshKeyFolder = "$($workingDir)\Kva" $clusterName = "Appliance" az arcappliance get-credentials --config-file $arcConfigPath --credentials-dir $applianceSshKeyFolder --y --overwrite-existing # Cleanup python cache to avoid issues with old tokens rmdir $workingDir\cloudCfg\python -Recurse -Force -ErrorAction SilentlyContinue Get-ClusterNode | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { # Cleanup python cache to avoid issues with old tokens rmdir $env:USERPROFILE\.wssd\python -Recurse -Force -ErrorAction SilentlyContinue } } $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($kvaIdentity)) $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding) # perform cred rotation az arcappliance update-infracredentials hci --cloudagent (get-mocconfig).cloudfqdn --loginconfigfile $arcTokenFilePath --kubeconfig $arcKubeConfigFilePath return $true } catch { Write-Error "Failed to rotate Resource Bridge credentials: $_" return $false } } function Invoke-SupportAksArcRemediation_CollectResourceBridgeLogs { <# .SYNOPSIS Collects Resource Bridge logs. .DESCRIPTION Gathers logs from the Resource Bridge for troubleshooting purposes. .OUTPUTS Boolean indicating log collection success .EXAMPLE Invoke-SupportAksArcRemediation_CollectResourceBridgeLogs #> [CmdletBinding()] [OutputType([bool])] param() if ((Test-AzureLocal) -eq $false) { Write-Verbose "Not an Azure Local environment, skipping Resource Bridge credential rotation" return $true } try { Write-Verbose "Collecting Resource Bridge logs..." $archciconfig = Get-ArcHciConfig $workingDir = $archciconfig.workingDir $arcConfigPath = "$($workingDir)\hci-appliance.yaml" $arcTokenFilePath = $archciconfig.KvaTokenLocation $arcKubeConfigFilePath = "$($workingDir)\kubeconfig" $applianceSshKeyFolder = "$($workingDir)\Kva" az arcappliance get-credentials --config-file $arcConfigPath --credentials-dir $applianceSshKeyFolder --y --overwrite-existing az arcappliance logs hci --loginconfigfile $arcTokenFilePath --cloudagent (get-mocconfig).cloudfqdn --credentials-dir $applianceSshKeyFolder --kubeconfig $arcKubeConfigFilePath return $true } catch { Write-Error "Failed to collect Resource Bridge logs: $_" return $false } } ############################################ function Test-AzureLocal { # Detecting Azure Local based on the moc catalog used return ((Get-MocConfig).catalog -eq "aks-hci-asz-stable-catalogs-int") } function Test-FailoverCluster { # Detecting Failover Cluster based on the presence of the Get-Cluster cmdlet return (Get-Command "Get-Cluster" -ErrorAction SilentlyContinue) -and (Get-Cluster -ErrorAction SilentlyContinue) } function Show-Disclaimer { @' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. '@ | Write-Warning } function Test-LatestVersion() { Write-Host "Checking if you are on the latest version" Update-Module -Name "Support.AksArc" } if ($PSBoundParameters['AcceptEula'] -ne $true){ Show-Disclaimer } # if $SkipUpdate or specific version is defined, then skip the auto-update method if ($PSBoundParameters['SkipUpdate'] -eq $true){ "SkipUpdate: {0}. Skipping auto-update" -f $SkipUpdate | Write-Host -ForegroundColor Yellow } else { Test-LatestVersion } # SIG # Begin signature block # MIIoQgYJKoZIhvcNAQcCoIIoMzCCKC8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDw/cWhzyMY/J3X # HwCuQyDmsf+kbsUjhzlIgp3MC/uRFaCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGiIwghoeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIF7bGLeouAjSNBlQZ08IxqXZ # 5QH3jzir6mG5rIewDTY6MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAB+mTWw9NVEtA0bZbHJQHzFRrQ9WqxhI+aHKygB9YQEysOJQxZXylXf+z # 4Y3Ut2pd6ZAf+YPCzxPPUTCLsa+K7XynqhVHDoW2cdLZtlAoL1hyNpneRIAkAit4 # NWIConDtKKDzumCO2uz5NyB4cRnbpWr8xLsCvBV+Vr9eHcaWbL8bXPMFr+5qHmpr # 7ILP6oUhBQE4IhWUnNce7ULXsCj6dxA8Bz98qZ7CsMH1fhjGhYoK+GQQcfcU7KDx # a9X3xS53QBeJ0MLcYbBzQEPc5C29CPX1mt2nibAdgZp1P63qrXr4O++gTzvbnp1a # KhDDWszHca6X9mz3ptNVCY/9U5Hcr6GCF6wwgheoBgorBgEEAYI3AwMBMYIXmDCC # F5QGCSqGSIb3DQEHAqCCF4UwgheBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq # hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCDbYTMkzY+ishM65GzkPeoig6Hp7FtNob/yKVGG66vOEwIGaC3+rVPU # GBIyMDI1MDYxOTA0MjgwNy40NFowBIACAfSggdmkgdYwgdMxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs # YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO # OjQwMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloIIR+zCCBygwggUQoAMCAQICEzMAAAH+0KjCezQhCwEAAQAAAf4wDQYJ # KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjQw # NzI1MTgzMTE4WhcNMjUxMDIyMTgzMTE4WjCB0zELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl # cmF0aW9ucyBMaW1pdGVkMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046NDAxQS0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC8vCEXFaWoDjeiWwTg8J6B # niZJ+wfZhNIoRi/wafffrYGZrJx1/lPe1DGk/c1abrZgdSJ4hBfD7S7iqVrLgA3c # iicj7js2mL1+jnbF0BcxfSkatzR6pbxFY3dt/Nc8q/Ts7XyLYeMPIu7LBjIoD0WZ # Zt4+NqF/0zB3xCDKCQ+3AOtVAYvI6TIzdVOIcqqEa70EIZVF0db2WY8yutSU9aJh # X0tUIHlVh34ARS11+oB2qXNXEDncSDFKqGnolt8QqdN1x8/pPwyKvQevBNO1XaHb # IMG2NdtAhqrJwo5vrfcZ9GSfbXos4MGDfs//HCGh1dPzVkLZoc3t7EQOaZuJayyM # a8UmSWLaDp23TV5KE6IaaFuievSpddwF6o1vpCgXyNf+4NW1j2m8viPxoRZLj2Ep # QfSbOwK5wivBRL7Hwy5PS5/tVcIU0VuIJQ1FOh/EncHjnh4YmEvR/BRNFuDIJuku # AowoOIJG5vrkOFp4O9QAAlP3cpIKh4UKiSU9q9uBDJqEZkMv+9YBWNflvwnOGXL2 # AYJ0r+qLqL5zFnRLzHoHbKM9tl90FV8f80Gn/UufvFt44RMA6fs5P0PdQa3Sr4qJ # aBjjYecuPKGXVsC7kd+CvIA7cMJoh1Xa2O+QlrLao6cXsOCPxrrQpBP1CB8l/Bee # vdkqJtgyNpRsI0gOfHPbKQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFG/Oe4n1JTaD # mX/n9v7kIGJtscXdMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G # A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs # BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH # AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCJPg1S87aXD7R0 # My2wG7GZfajeVVCqdCS4hMAYgYKAj6yXtmpk5MN3wurGwfgZYI9PO1ze2vwCAG+x # gjNaMXCKgKMJA4OrWgExY3MSwrNyQfEDNijlLN7s4+QwDcMDFWrhJJHzL5NELYZw # 53QlF5nWU+WGU+X1cj7Pw6C04+ZCcsuI/2rOlMfAXN76xupKfxx6R24xl0vIcmTc # 2LDcCeCVT9ZPMaxAB1yH1JVXgseJ9SebBN/SLTuIq1OU2SrdvHWLJaDs3uMZkAFF # ZPaZf5gBUeUrbu32f5a1hufpw4k1fouwfzE9UFFgAhFWRawzIQB2g/12p9pnPBca # aO5VD3fU2HMeOMb4R/DXXwNeOTdWrepQjWt7fjMwxNHNlkTDzYW6kXe+Jc1HcNU6 # VL0kfjHl6Z8g1rW65JpzoXgJ4kIPUZqR9LsPlrI2xpnZ76wFSHrYpVOWESxBEdlH # AJPFuLHVjiInD48M0tzQd/X2pfZeJfS7ZIz0JZNOOzP1K8KMgpLEJkUI2//OkoiW # wfHuFA1AdIxsqHT/DCfzq6IgAsSNrNSzMTT5fqtw5sN9TiH87/S+ZsXcExH7jmsB # kwARMmxEM/EckKj/lcaFZ2D8ugnldYGs4Mvjhg2s3sVGccQACvTqx+Wpnx55XcW4 # Mp0/mHX1ZScZbA7Uf9mNTM6hUaJXeDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb # SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj # YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy # NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI # yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo # YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y # aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v # 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG # ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS # kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr # bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM # jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL # W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF # emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu # rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE # FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn # G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW # M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5 # Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi # AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV # 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js # Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx # MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2 # LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv # 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn # OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1 # bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4 # rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU # 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF # NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/ # HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU # CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi # excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm # dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq # ELQdVTNYs6FwZvKhggNWMIICPgIBATCCAQGhgdmkgdYwgdMxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs # YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO # OjQwMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCEY0cP9rtDRtAtZUb0m4bGAtFex6CBgzCB # gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUA # AgUA6/4PADAiGA8yMDI1MDYxOTA0MjIyNFoYDzIwMjUwNjIwMDQyMjI0WjB0MDoG # CisGAQQBhFkKBAExLDAqMAoCBQDr/g8AAgEAMAcCAQACAgN8MAcCAQACAhI4MAoC # BQDr/2CAAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA # AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAHPQ6jZVMP1c48V5 # v9ooO24kpV3/2INWIJFCBqFxNobthhAutGcx0TyxCazWCmmCV/GESK4LZgO0duZu # nAECFMpP2yU628F/s+KlyZonCFOnxgCr2P3cS7SyDhxcHHO8NBV3BvtOsnFdjQ7R # 4nrTM6GgVLEM5ldOaEcBxjqsf0BVLFMNahiElShMvGMiTIkq+YhQ6UgDS6sXdNVs # oQMxnQOhZtt9YaUqaixlK5sobpTXU/EpuMasKh3zY1JQhAJFQEdIXFJuY5aSGb2T # blCesmVvZqvE+phVP387wvn/StSvh6nnY1frXPAT41DREUBIT+E3/ShKJKfew3pF # sOy2Q7oxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MAITMwAAAf7QqMJ7NCELAQABAAAB/jANBglghkgBZQMEAgEFAKCCAUowGgYJKoZI # hvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCJS3bw4vPOHBkP # 1c/OrTZu4Za+QLa99xRQoLrABZH6mjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQw # gb0EIBGFzN38U8ifGNH3abaE9apz68Y4bX78jRa2QKy3KHR5MIGYMIGApH4wfDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAH+0KjCezQhCwEAAQAAAf4w # IgQgLQU+vI+ape+RB/VkhbgrfOHzAZqL/yzjLlGTpydB364wDQYJKoZIhvcNAQEL # BQAEggIAdYp9mdxW2cTXS1dg+npYymSaf2NnLNiItIkSMPYURmVmRAkvUdjnsG87 # NYjMj12xYxDzhXOOXS+NmC3nNBlk0Yu+jp7a7rfoUXC0IMzScJk2kCH8Jda7twDj # BO3J4i0fSy1YJ4vlZwBXHPYiwxPNq1idz2Z0fjazdrocmHJVRXf74QiV18uGW80O # muNG8siSSE7NEHNHY/diqH51hqs3Qe1Co+Knofmzifk5732ZAFLUhtBmdPowPtgV # JpKSEQBWg8EKnPxsIE7SEksUFSW+WltLrj3mP1Mrmdj2Ih9dikE+FUuAhC+E74aj # bNfrkRG3zdAmwslLEN5Wn/CTiEF0ycQnpGaHpFmF3X0ujqgG2moa61ER7kDwGneb # FsjEWH13n2JO1JwGQYHahs+UnwIcDuFYekliUybBlBSOTCOFXLYHporMdfDBL5AS # DotHaQNflR5AoDzIFsHBY7OfGHpjG58xvSvFoROYyngt/ktR3ZbyP4QD2aM0tBCD # KAHIAXeWwi6l5aYTutDvuPDQpSqRroPHaslcUdnAnJAIUraGy7eRsMs7G9K8+e+c # I/z8f4RQNxzdYGrRATG0BvELqAsQ3HcwQvwKWFzS9oEoiO3qUjd/HPqjgaoWsQ4K # j1ADyTZtEhxWP+8tZUh+0Hp75QM+eAVG4JkqNuX2iGW6W632uyQ= # SIG # End signature block |