Azs.TeamsIntegration.psm1
#Requires -Modules Azs.Update.Admin, Azs.Operator Import-Module -Name Azs.Operator -Force -Verbose:$false Import-Module -Name Azs.Update.Admin -Force -Verbose:$false # Import modules $Location = $PSScriptRoot [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\Antlr4.Runtime.dll") [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\Newtonsoft.Json.dll") [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\Jurassic.dll") [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\AdaptiveCards.dll") [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\AdaptiveExpressions.dll") [System.Reflection.Assembly]::LoadFrom("$Location\Dependencies\AdaptiveCards.Templating.dll") Import-Module -Name $Location\Azs.TeamsIntegration.Update.psm1 -force -Global -Verbose:$false Import-Module -Name $Location\Azs.TeamsIntegration.Utilities.psm1 -force -Global -DisableNameChecking -Verbose:$false Import-LocalizedData -BindingVariable LocalizedText -Filename Azs.TeamsIntegration.Update.Strings.psd1 -ErrorAction SilentlyContinue function Send-AzsUpdate { <# .SYNOPSIS Send Update status to operators and audiences. .DESCRIPTION Retrieves update status and post a webrequest to operator and audience URIs, optionally as an adaptive card for teams. .EXAMPLE PS C:\> $OperatorUri = "https://outlook.office.com/webhook/<etc>" PS C:\> $AudienceUri = "https://outlook.office.com/webhook/<etc>" PS C:\> $Bridge = "https://teams.microsoft.com/l/meetup-join/<etc>" PS C:\> $stamp = "Prod" PS C:\> Send-AzsUpdate -AudienceUri $AudienceUri -OperatorUri $OperatorUri -Stamp $stamp -BridgeInformation $Bridge Updates audience every 10 minutes and operators (default) every 5 minutes for stamp "Prod" .NOTES Operators are assumed to have access to the audience channel and thus do not need rich data on the update. Operators recieve update status and duration and messages about the execution of retrieving and building status updates for all. OperatorFrequency must be less than or equal AudienceFrequency, it is assumes operators need update status quicker than audiences. .PARAMETER OperatorFrequency How often (approximately) the monitor the update for Operators. Must be 5, 10, 15, 20, 30 or 60 .PARAMETER AudienceFrequency How often (approximately) the monitor the update for Audiences. Must be 5, 10, 15, 20, 30 or 60 .PARAMETER AudienceUri Uri(s) to send Audience status updates .PARAMETER OperatorUri Uri(s) to send Operator status updates .PARAMETER Stamp An Azs.Operator construct that is passed to PEP and ARM connections. Stamps must be onboarded in Azs.Management using Add-Stamp. .PARAMETER PepCredential An Azs.Operator construct that is passed to PEP connections. Cloud Admin credential for PEP connections. .PARAMETER UpdateStatus NOT YET IMPLEMENT. FOR OFFLINE UPDATES .PARAMETER StampInformation NOT YET IMPLEMENT. FOR OFFLINE UPDATES .PARAMETER DisposePEP Close the PEP Session after every iteration. .PARAMETER Brief Messages will not be delivered if the previous message content was the same. .PARAMETER LogAction Logs write to console and disk by default, valid values 'File','Console','WebHook' where Webhook is the operator webhook by default .PARAMETER LogPath Saves zip of diagnostic data for troubleshooting in $home\.Azs.TeamsIntegration by default. .PARAMETER RetrySeconds Wait in seconds for retries connecting to ARM and PEP. Default is 30 .PARAMETER HideEnvironmentDetail Optionally hide environment on teams cards .NOTES v0.1.1 - Send-AzsUpdate errors when Get-AzsUpdate returns nothing v0.1.1 Not using -log can lead to errors #> [CmdletBinding()] [Alias("Send-AzsUpdateStatus")] param ( [Parameter(Mandatory = $false, HelpMessage = 'Will override connectionUri in StampDefinition')] [System.Uri[]] $AudienceUri, [Parameter(Mandatory = $false, HelpMessage = 'Send issues to a seperate Operator URI')] [System.Uri[]] $OperatorUri, [Parameter(Mandatory = $true, ParameterSetName = 'Online')] [ArgumentCompleter( { (Get-Stamp).Name | Sort-Object })] [string] $Stamp, [Parameter(Mandatory = $false, ParameterSetName = 'Online')] [pscredential] $PepCredential, [Parameter(Mandatory = $true, ParameterSetName = 'Offline', HelpMessage = 'Use offline XML status of update')] [xml] $UpdateStatus, [Parameter(Mandatory = $false, HelpMessage = 'Messages will not be delivered if the previous message content was the same.')] [switch]$Brief, [Parameter(Mandatory = $true, ParameterSetName = 'Offline', HelpMessage = 'Use offline Stamp Information JSON status of update')] [psobject] $StampInformation, [Parameter(Mandatory = $false, ParameterSetName = 'Online', HelpMessage = 'Force PEP disconnect after every run.')] [switch]$DisposePEP, [Parameter(Mandatory = $false, HelpMessage = "Logs write to console and disk by default, valid values 'File','Console','WebHook' where Webhook is the operator webhook by default")] [ValidateSet('Console','File','WebHook')] [string[]]$LogAction = @('File','Console'), [Parameter(Mandatory = $false, HelpMessage = 'Customize log path')] [string]$logPath = (Join-Path "$HOME" (".AzsTeamsIntegration\{0}" -f (Get-Date).ToString('yyyyMMddHHmmss'))), [Parameter(Mandatory = $false, ParameterSetName = 'Online', HelpMessage = 'Wait in seconds for retries connecting to ARM and PEP. Default is 30')] [int]$retrySeconds = 30, [Parameter(Mandatory = $false, HelpMessage = 'Optionally include bridge information on the card')] $BridgeInformation, [Parameter(Mandatory = $false, HelpMessage = 'Optionally hide environment on teams cards')] [switch]$HideEnvironmentDetail ) try { $GLOBAL:logPath = Initialize-LogPath -LogPath $logPath $GLOBAL:LogAction = $LogAction $GLOBAL:Stamp = $Stamp #region online/live & offline read for update info if ($PSCmdlet.ParameterSetName -eq 'Online') { Write-CustomLog "[$Stamp] Connecting PEP" $pepsession = Get-PepSessionWithRetries -stamp $stamp -retrySeconds $retrySeconds -PepCredential $PepCredential ###### ONLINE if (-not $pepSession) { throw ($localizedText.NoPepSession -f $stamp) } if (-not $StampInformation) { Write-CustomLog "[$Stamp] Getting Stamp Information" $StampInformation = Invoke-PepCommand -PepSession $pepSession -scriptBlock { Get-AzureStackStampInformation } ###### ONLINE } # get update status from URP and determine latest update state $AllUpdates = Get-AzsUpdateWithRetries -stamp $stamp -retrySeconds $retrySeconds ###### ONLINE $UpdateInFlight = ($AllUpdates | Where-Object State -in 'Installing', 'Preparing') if ($UpdateInFlight) { $TrackingUpdateRun = Get-AzsUpdateRunWithRetries -Stamp $Stamp -retrySeconds $retrySeconds -UpdateName $UpdateInFlight.Name ####### ONLINE # if the update is installing and the audience needs an update we can get the detail from the PEP # if the audience doesn't need an update, just the operators, then ARM calls will be enough. if ($UpdateInFlight.state -eq 'Installing' -and $AudienceUri) { Write-CustomLog "[$Stamp] Running Get-AzureStackUpdateStatus" [xml]$updateStatus = Invoke-PepCommand -PepSession $pepSession -ScriptBlock { Get-AzureStackUpdateStatus } } } else { Write-CustomLog "[$Stamp] No active update run" } } elseif ($PSCmdlet.ParameterSetName -eq 'Offline') { $OfflineLogPath = Join-Path $logPath 'offline' Expand-Archive -Path $zipFile -DestinationPath $OfflineLogPath # Stamp Info $StampInformation = Get-Content (Join-Path $OfflineLogPath\StampInformation.json) -raw -ErrorAction Continue | ConvertFrom-Json # All updates $AllUpdates = Get-Content (Join-Path $OfflineLogPath\AllUpdates.json) -raw -ErrorAction Continue | ConvertFrom-Json $UpdateInFlight = ($AllUpdates | Where-Object State -in 'Installing', 'Preparing') # Current Run $TrackingUpdateRun = Get-Content (Join-Path $OfflineLogPath\UpdateRun.json) -raw -ErrorAction Continue | ConvertFrom-Json # Current Update status [xml]$updateStatus = Get-Content (Join-Path $OfflineLogPath\UpdateStatus.xml) -raw -ErrorAction Continue Remove-Item -path $OfflineLogPath -Recurse -force } else { throw "Unexpected Error. Unknown parameterset" } #endregion # if there's an update running, get duration, and save name for later. if ($UpdateInFlight) { Write-CustomLog "[$Stamp] Update $($UpdateInFlight.DisplayName) is $($UpdateInFlight.state)" $Banner = "$($UpdateInFlight.DisplayName) - $($UpdateInFlight.State.ToString())" $GLOBAL:TrackingUpdateRunUniqueName = $TrackingUpdateRun.Name $UpdateCardTemplate = $GLOBAL:UpdateInProgressTemplate $totalDuration = Format-TimeSpan $TrackingUpdateRun.duration } else { $latestUpdate = $AllUpdates | Select-Object -first 1 if ($latestUpdate) { $Banner = "$($latestUpdate.DisplayName) - $($latestUpdate.State.ToString())" } else { $Banner = 'Azure Stack Hub Update Status' } Write-CustomLog "[$Stamp] No Updates in progress according URP. (Can be transient)" $UpdateCardTemplate = $GLOBAL:UpdateNotRunningTemplate } # if the update is preparing, we can get the preparation steps in progress if ($UpdateInFlight.State -eq 'Preparing') { $PrepProgress = $TrackingUpdateRun | Select-Object -expand ProgressStep | Select-Object Name, Status Write-CustomLog "[$Stamp] Steps in progress:" $PrepProgress | ForEach-Object { Write-CustomLog (("[$Stamp] {0}" -f $_.Name).PadRight(75) + $_.Status) } Write-CustomLog "[$Stamp] Update Duration: $totalDuration" } # the previous run errored, we want logs but we only want to once them once. # TO DO, make these logs targetted if ($latestUpdate.State -in 'PreparationFailed', 'InstallationFailed') { $collectAzsLogs = $true } else { $GLOBAL:AzsLogsSent = $false } # Get previous duration if the update is installed if ($latestUpdate.State -eq 'Installed') { $latestUpdateRun = Get-AzsUpdateRunWithRetries -Stamp $Stamp -retrySeconds $retrySeconds -UpdateName $latestUpdate.Name $totalDurationFinal = Format-TimeSpan $latestUpdateRun.duration } #endregion # formatting last few bits $updateHistory = $AllUpdates | Select-Object Location, DisplayName, @{label = 'State'; Expression = { $_.State.tostring() } }, VersionNumber $UpdateSummary = Get-UpdateSummary -UpdateStatus $updateStatus # Gather cmdlet used and version $moduleVersion = $MyInvocation.MyCommand.Module.Version $moduleName = $MyInvocation.MyCommand.Module.Name $commandletName = (Get-PSCallStack)[-2].Command # Create PSObject of progress data in "i hate nulls and ints" mode $UpdateData = New-Object -TypeName PSObject -Property @{ ShowScopedRepair = $null -ne $UpdateProgress.ShowScopedRepair showPrepProgress = $null -ne $PrepProgress ShowStampInfo = $null -ne $StampInformation showDuration = $null -ne $totalDuration ShowHistory = $null -ne $updateHistory ShowProgress = $null -ne $UpdateSummary.updateProgress ShowBridge = $null -ne $BridgeInformation Banner = if ($Banner) { $Banner } else { "Azure Stack Update" } totalDuration = if ($totalDurationFinal) {$totalDurationFinal} else {$totalDuration} Stamp = New-Object -TypeName PSObject -Property @{ AdminPortal = $StampInformation.AdminExternalEndpoints.AdminPortal TenantPortal = $StampInformation.TenantExternalEndpoints.TenantPortal Prefix = $StampInformation.Prefix Hardware = $StampInformation.HardwareOEM NumberOfNodes = "$($StampInformation.NumberOfNodes)" Version = $StampInformation.StampVersion CloudId = $StampInformation.CloudId Name = $Stamp } Update = New-Object -TypeName PSObject -Property @{ Duration = if ($totalDuration) { $totalDuration } else { '' } Status = if ($UpdateInFlight.State) { $UpdateInFlight.State.ToString() } else { '' } Prep = if ($PrepProgress) {$PrepProgress} else {''} Summary = if ($UpdateSummary.summary) { $UpdateSummary.summary } else { '' } UpdateProgress = if ($UpdateSummary.updateProgress) { $UpdateSummary.updateProgress } else { '' } UpdateHistory = if ($updateHistory) { $UpdateHistory } else { '' } UpdateName = if ($TrackingUpdateRun.Name) { $TrackingUpdateRun.Name } else { '' } } BridgeInformation = $BridgeInformation commandletName = if ($commandletName) { $commandletName } else { '' } moduleVersion = if ($moduleVersion) { "$moduleVersion" } else { '' } moduleName = if ($moduleName) { $moduleName } else { '' } ShowEnvDetail = if ($HideEnvironmentDetail) { 'false' } else { 'true' } ShowNoEnvDetail = if ($HideEnvironmentDetail) { 'true' } else { 'false' } Brief = [bool]$Brief } # Get Update Detail Adaptive Card Template if there's an audience. if ($AudienceUri) { $TeamsMessageContent = Format-TeamsMessage -JsonTemplate $UpdateCardTemplate -AdaptiveCardData $UpdateData if ($TeamsMessageContent -ne $GLOBAL:previousAudienceMessage -or -not $Brief) { Send-MessageContent -uri $AudienceUri -MessageContent $TeamsMessageContent $GLOBAL:previousAudienceMessage = $TeamsMessageContent } else { Write-CustomLog ($localizedText.SameAsPreviousMessage -f 'Audience', $Brief) } } else { Write-CustomLog "[$Stamp] Skipping Adaptive card. No Audience." } if ($OperatorUri) { $OperatorText = Get-OperatorUpdate -updateData $UpdateData $OperatorUpdateMessage = Format-OperatorMessage -Message $OperatorText if ($GLOBAL:PreviousOperatorMessage -ne $OperatorUpdateMessage -or -not $Brief) { foreach ($Uri in $OperatorUri) { Invoke-RestMethod -Uri $Uri -Method Post -Body $OperatorUpdateMessage -ContentType 'application/json' | out-Null } } else { Write-CustomLog ($localizedText.SameAsPreviousMessage -f 'Operator', $Brief) } $GLOBAL:PreviousOperatorMessage = $OperatorUpdateMessage } } catch { $OperatorCatchMessage = "We hit a problem sending data from the stamp to teams: `nError: `n{0}. `nUpdate Data: `n {1}" -f $_.exception.message, $OperatorUpdateMessage Write-CustomLog "[$Stamp] $OperatorCatchMessage" Send-TextMessage -uri $OperatorUri -message $OperatorCatchMessage } finally { if ($pepSession -and $disposePEP) { Write-CustomLog "[$Stamp] Closing Pepsession" Close-AzsPepSession -Stamp $Stamp } else { Write-CustomLog "[$Stamp] Leaving Pepsession open." } if ($collectAzsLogs -and -not $GLOBAL:azsLogsSent) { Write-CustomLog "[$Stamp] Sending Azure Stack Logs." $pepsession = Get-PepSessionWithRetries -stamp $stamp -retrySeconds $retrySeconds -PepCredential $PepCredential Invoke-PepCommand -PepSession $pepSession -ScriptBlock { Send-AzureStackDiagnosticLog -FilterByRole SeedRingServices } -AsJob $GLOBAL:azsLogsSent = $true Send-TextMessage -uri $OperatorUri -message "[$Stamp] Sending Azure Stack Logs." } if ($updatesFromURP) { $updatesFromURP | ConvertTo-Json -depth 15 -WarningAction SilentlyContinue | Out-File $logPath\AllUpdates.json } if ($StampInformation) { $StampInformation | ConvertTo-Json -depth 15 -WarningAction SilentlyContinue | Out-File $logPath\StampInformation.json } if ($UpdateStatus) { $UpdateStatus | Out-File $logPath\UpdateStatus.xml } if ($UpdateData) { $UpdateData | ConvertTo-Json -Depth 20 -WarningAction SilentlyContinue | Out-File $logPath\UpdateData.json } if ($latestUpdateRun) { $latestUpdateRun | ConvertTo-Json -depth 15 -WarningAction SilentlyContinue | Out-File $logPath\UpdateRun.json } if ($TeamsMessageContent) { $TeamsMessageContent | Out-File $logPath\TeamsAudienceMessageContent.json } if ($OperatorUpdateMessage) { $OperatorUpdateMessage | Out-File $logPath\TeamsOperatorContent.json} if ($OperatorCatchMessage) { $GLOBAL:previousAudienceMessage = 'ERROR'; Write-CustomLog "Forcing card next time" } if ('Webhook' -in $GLOBAL:LogActionlog) { # send operator the log if logging on, highlight important messaging $TeamsMessageContent = Format-OperatorMessage -message (Get-Content (Join-Path $logPath AzsMgmtTeams.log)) foreach ($connector in $OperatorUri) { Invoke-RestMethod -Uri $connector -Method Post -Body $TeamsMessageContent -ContentType 'application/json' } } $zipFile = "{0}\AzsTeamsIntegrationLogs_{1}.zip" -f ((Split-Path $logPath -Parent), (Split-Path $logPath -Leaf)) Write-CustomLog "[$Stamp] Zipping logs. $zipFile" Compress-Archive -Path $logPath -DestinationPath $zipFile Write-CustomLog "[$Stamp] Removing $logPath" Remove-Item -Path $logPath -Recurse -Force -ErrorAction SilentlyContinue } } Export-ModuleMember -Function Send-AzsUpdate function Watch-AzsUpdate { <# .SYNOPSIS Continually monitor Updates for changes and invoke updates to operators and audiences. .DESCRIPTION Runs in loop every minute for (configurable) totalMinutes, default 7200. Each iteration checks if the operators or the audiences should updated as per OperatorFreqency and AudienceFrequency respectively. If Operators should be updated, the status of the most recent update is retrieved and changes (name, duration, status) are determined. If Audiences should be updated and changes are detected, both audiences and operators receive an update. If Audiences are not to be updated, i.e. update has changed but AudienceFrequency (a update) isn't due, the operators get a short status update. .EXAMPLE PS C:\> $OperatorUri = "https://outlook.office.com/webhook/<etc>" PS C:\> $AudienceUri = "https://outlook.office.com/webhook/<etc>" PS C:\> $Bridge = "https://teams.microsoft.com/l/meetup-join/<etc>" PS C:\> $stamp = "Prod" PS C:\> Watch-AzsUpdate -AudienceUri $AudienceUri -OperatorUri $OperatorUri -Stamp $stamp -BridgeInformation $Bridge -AudienceFrequency 30 Updates audience every 30 minutes and operators (default) every 15 minutes for stamp "Prod" .PARAMETER OperatorFrequency How often (approximately) the monitor the update for Operators. Must be 5, 10, 15 (default) or 30. .PARAMETER AudienceFrequency How often (approximately) the monitor the update for Audiences. Must be 30 or 60. .PARAMETER AudienceUri Uri(s) to send Audience status updates .PARAMETER OperatorUri Uri(s) to send Operator status updates .PARAMETER Stamp An Azs.Operator construct that is passed to PEP and ARM connections. Stamps must be onboarded in Azs.Management using Add-AzsStamp. .PARAMETER PepCredential An Azs.Operator construct that is passed to PEP connections. Cloud Admin credential for PEP connections. .PARAMETER UpdateStatus NOT YET IMPLEMENT. FOR OFFLINE UPDATES .PARAMETER StampInformation NOT YET IMPLEMENT. FOR OFFLINE UPDATES .PARAMETER DisposePEP Close the PEP Session after every iteration. .PARAMETER Brief Messages will not be delivered if the previous message content was the same. .PARAMETER Log Saves zip of diagnostic data for troubleshooting .PARAMETER LogPath Saves zip of diagnostic data for troubleshooting .PARAMETER RetrySeconds Wait in seconds for retries connecting to ARM and PEP. Default is 30 .PARAMETER TotalMinutes Total runtime in minutes. Defaults to 7200 minutes (5 days) .PARAMETER BridgeInformation Optionally include bridge information on the card .PARAMETER HideEnvironmentDetail Optionally hide environment on teams cards. .NOTES To be run on a workstation, server or jumpbox with PowerShell 7 and has connectivity WINRM to PriviledgedEndpoint, and HTTPS connectivity Azure Resource Manager on Azure Stack Stack Hub and HTTPS connectivity the target webhook. Operators are assumed to have access to the audience channel and thus do not need rich data on the update. Operators recieve update status and duration and messages about the execution of retrieving and building status updates for all. OperatorFrequency must be less than or equal AudienceFrequency, it is assumes operators need update status quicker than audiences. #> param ( [Parameter(Mandatory = $true, ParameterSetName = 'PEP')] [ArgumentCompleter( { (Get-Stamp).Name | Sort-Object })] [string] $Stamp, [Parameter(Mandatory = $false, ParameterSetName = 'PEP')] [pscredential] $PepCredential, [Parameter(Mandatory = $false, HelpMessage = 'Uri(s) to send Audience status updates')] [System.Uri[]] $AudienceUri, [Parameter(Mandatory = $false, HelpMessage = 'Uri(s) to send Audience status updates')] [System.Uri[]] $OperatorUri, [Parameter(Mandatory = $false, HelpMessage = 'How often (approximately) the monitor the update for Operators. Must be 5, 10 or 15')] [ValidateSet(10, 15, 30)] $OperatorFrequency = 15, [Parameter(Mandatory = $false, HelpMessage = 'How often (approximately) to send the update to the audiences. Must be 30 or 60')] [ValidateSet(10, 30, 60)] $AudienceFrequency = 30, [Parameter(Mandatory = $false, HelpMessage = 'Optionally include bridge information on the card')] $BridgeInformation, [Parameter(Mandatory = $false, HelpMessage = 'Messages will not be delivered if the previous message content was the same.')] [switch]$Brief, [Parameter(Mandatory = $true, ParameterSetName = 'File', HelpMessage = 'Use offline Stamp Information JSON status of update')] [psobject] $StampInformation, [Parameter(Mandatory = $false, ParameterSetName = 'PEP', HelpMessage = 'Force PEP disconnect after every run.')] [switch]$DisposePEP, [Parameter(Mandatory = $false, HelpMessage = "Logs write to console and disk by default, valid values 'File','Console','WebHook' where Webhook is the operator webhook by default")] [ValidateSet('Console','File','WebHook')] [string[]]$LogAction = @('File','Console'), [Parameter(Mandatory = $false, HelpMessage = 'Customize log path')] [string]$LogPath = (Join-Path "$HOME" ".AzsMgmtTeams"), [Parameter(Mandatory = $false, HelpMessage = 'Wait in seconds for retries connecting to ARM and PEP. Default is 30')] [int]$RetrySeconds = 30, [Parameter(Mandatory = $false, HelpMessage = 'Total runtime in minutes. Defaults to 7200 minutes (5 days)')] $totalMinutes = 7200, [Parameter(Mandatory = $false, HelpMessage = 'Optionally hide environment on teams cards')] [switch]$HideEnvironmentDetail ) $GLOBAL:previousAudienceMessage = 'PLACEHOLDER' $GLOBAL:previousOperatorMessage = 'PLACEHOLDER' $i = 0 $opCount = 0 $audCount = 0 try { # Check for module update Test-ModuleUpdate if (-not $PepCredential -and -not (Get-AzsStamp -Stamp $Stamp).CloudAdminUser.VaultName) { Write-Warning $localizedText.NoPepCredentialsWarning } if (-not $AudienceUri -and -not $OperatorUri) { throw $localizedText.NoUriWarning } # Validate PEP connectivity try { $pepSession = Connect-AzsPepSession -Stamp $Stamp -PepCredential $PepCredential -errorAction Stop } catch { $pepError = $_.exception.message } if ($pepSession.State -ne 'Opened') { throw $localizedText.NoPepConnection -f $pepError } # check ARM connectivity try { Connect-AzsArmEndpoint -Stamp $Stamp $validateUpdates = Get-AzsUpdate } catch { throw $localizedText.NoARMConnection -f $_.exception.message } while ($i -le $totalMinutes) { try { # determine if operators and audiences should get an update $totalPercentage = (($totalMinutes - $i) / $totalMinutes) * 100 $showOperator = if (($i % $OperatorFrequency) -eq 0) { $opMinus = 0 $opMinusPercent = 0 $opCount = 0 $OperatorUri } else { $opCount++; $opMinus = $OperatorFrequency - $opCount $opMinusPercent = ($opMinus / $OperatorFrequency) * 100 $null } $showAudience = if (($i % $AudienceFrequency) -eq 0) { $audMinus = 0 $audMinusPercent = 0 $audCount = 0 $AudienceUri } else { $audCount++ $audMinus = $AudienceFrequency - $audCount $audMinusPercent = ($audMinus / $AudienceFrequency) * 100 $null } Write-Progress -id 1 -activity "Forwarding Azure Stack Update Status" -status "Total Monitoring Runtime" -percentComplete $totalPercentage Write-Progress -id 2 -parentId 1 -activity "Audience Forwarding" -status "Audience Update in $audMinus minutes" -percentComplete $audMinusPercent Write-Progress -id 3 -parentId 1 -activity "Operator Forwarding" -status "Operator Update in $opMinus minutes" -percentComplete $opMinusPercent if ($showAudience -or $showOperator) { $PSBoundParameters['AudienceUri'] = $showAudience $PSBoundParameters['OperatorUri'] = $showOperator $PSBoundParameters.Remove('OperatorFrequency') | Out-Null $PSBoundParameters.Remove('AudienceFrequency') | Out-Null Send-AzsUpdate @PSBoundParameters } Start-Sleep -Seconds 60 $i++ } catch { Write-Warning ($localizedText.Exception -f $($_.exception.message)) } } } catch { throw ("Watch-AzsUpdate had a problem: Error: {0}" -f $_.exception.message) } finally { Close-AzsPepSession -Stamp $Stamp -ErrorAction SilentlyContinue } } function Test-ModuleUpdate { param() try { Write-CustomLog "Looking for module updates" $ModuleOnline = Find-Module -Name $MyInvocation.MyCommand.Module.Name -Repository PSGallery -AllowPrerelease -ErrorAction SilentlyContinue if ([system.version]$ModuleOnline.Version.replace('-preview','') -gt $MyInvocation.MyCommand.Module.Version) { Write-Warning ($localizedText.CurrentVersion -f $MyInvocation.MyCommand.Module.Name, $MyInvocation.MyCommand.Module.Version) Write-Warning ($localizedText.UpdateToVersion -f $ModuleOnline.Version) Start-Sleep -seconds 10 } else { Write-Verbose ($localizedText.CurrentVersion -f $MyInvocation.MyCommand.Module.Name, $MyInvocation.MyCommand.Module.Version) } } catch { Write-Verbose ($localizedText.Exception -f $_.exception.message) } } Export-ModuleMember -Function Watch-AzsUpdate # SIG # Begin signature block # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDu8B0BFUqSyz6H # eE3z8ZjvZPaUspdA+l+nZwk7V3nCJ6CCDYEwggX/MIID56ADAgECAhMzAAAB32vw # LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn # s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw # PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS # yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG # 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh # EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH # tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS # 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp # TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok # t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 # b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao # mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD # Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt # VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G # CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ # Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 # oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgOZ7Wkfu+ # QT/B9eCBSQ7vOCTQd8sHezwO87S2UQE9YMIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQCbrbD7ts0Ysk4dMpflYkXv1D7Bbamhz4TZAiFVnKb2 # IsYLuBrhlm++5EW4nZCXmQBqOEtBN240un0zoY1yA/QAHtfoFrr8f1AMgdm+aClu # isk8B7oDHVRqU8rQsGHGIIxZ5mVhGyEHuEVfiBl/BZZGpXv8QfyVlZDiuA8/IWLw # L/Ay7YTBOSm8QceaSwCmMfL7E7qmoj5tq2lsNH3FmKGq144er71PIC+8j8ZRXR0f # WfPrzgDyguBlmxCyB+CQhFGR45niCXgl+ac6e2Bkq6jwGbNxVDhRNQNXtnKWwkIC # acTOkvk0LH9HGEhVj6fbtDJYqvKkrMnogyIiCsgLyg54oYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIL0iVE/pacd7SyO6f083xQ3qGWWFOLOsDGOqy31Y # RhDLAgZgPPIkFm8YEzIwMjEwMzExMTUyNTAwLjIxNlowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYtD+AvMB5c1JAAAA # AAFiMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIxMDExNDE5MDIyMloXDTIyMDQxMTE5MDIyMlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJE # LUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO+GodT2ucL3Mr2DQsv2 # ELNbSvKyBpYdUKtUBWiZmFVy18pG/pucgkrc5i9tu8CY7GpWV/CQNmHG2mVeSHMJ # vbwCc/AAv7JP3bFCt6Zg75IbVSNOGA1eqLbmQiC6UAfSKXLN3dHtQ5diihb3Ymzp # NP9K0cVPZfv2MXm+ZVU0RES8cyPkXel7+UEGE+kqdiBNDdb8yBXd8sju+90+V4nz # YC+ZWW7SFJ2FFZlASpVaHpjv+eGohXlQaSBvmM4Q0xe3LhzQM8ViGz9cLeFSKgFf # SY7qizL7wUg+eqYvDUyjPX8axEQHmk0th23wWH5p0Wduws43qNIo0OQ0mRotBK71 # nykCAwEAAaOCARswggEXMB0GA1UdDgQWBBTLxEoRYEpDtzp84B5WlZN2kP4qazAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAtQa3DoXYbW/cXACbcVSFGe4gC8GXs # FxSHT3JgwFU/NdJOcbkcFTVvTp6vlmTvHm6sIjknRBB0Xi1NBTqPw20u6u/T7Cnc # /z0gT6mf9crI0VR9C+R1CtjezYKZEdZZ7fuNQWjsyftNDhQy+Rqnqryt0VoezLal # heiinHzZD/4Y4hZYPf0u8TSv1ZfKtdBweWG3QU0Lp/I9SbIoemDG97RULMcPvq2u # fhUp3OMiYQGL1WqkykSnqRJsM2IcA4l4dmoPNP6dLg5Dr7NVoYKIMInaQVZjSwDM # ZhWryvfizX0SrzyLgkMPhLMVkfLxQQSQ37NeFk7F1RfeAkNWAh6mCORBMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz # MkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAmrP6Chrbz0ax7s57n5Pop3VC8gyggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOP0n5EwIhgPMjAyMTAzMTExNzU0MjVaGA8yMDIxMDMxMjE3NTQyNVowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA4/SfkQIBADAKAgEAAgIr8QIB/zAHAgEAAgIRVzAK # AgUA4/XxEQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAAIDMp0N05uhZQRe # 6PS1v/iJoUlZCpQ804lWd+lATCq/HO5jwF6vo7D+ka2jwrYZkNAGakI6eoHRajdc # WdI1XEfMdukNpH1aTHuk6Ss6s7GZi2zgmBNVYRyH6/uo/r2VAmhN58g9QcBGBNAq # dP4aOpyoA854av9ZSzXjxpT4E5PEMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAFi0P4C8wHlzUkAAAAAAWIwDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgEMFlqBbgn/fqY6aomyLQi8kNgYIJ3SQIgNHYPLEw+0kwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCCKqhiV+zwNDrpU7DRB7Mi57xi6GBNYsGjgZqq2 # qVMKMjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # YtD+AvMB5c1JAAAAAAFiMCIEIFIoFNvxLtJimrsLTxAvErioEIQbE+YKajRWVcZo # m5SyMA0GCSqGSIb3DQEBCwUABIIBAM3B26Ti984LR59/vDwukOlZ3LHs9Q+nGMxt # 2GgvqSLQmu+sKar96YO/pC1lVAUOwpOiAQ4QUO9PDJ2sRHAS0gA6GWQzUjhzCZ35 # kjVIDzk5PKIEWspjcF4SJtuneQIUFUcDzSECk7jAyNtIdRH3F1JuwzT6utd1XcLl # hzyUtClMziqFYdoA+ESWvXz2tcJ0f35R2mHea7wRqBJrl9Eu2PEy4ZEcyMtkJblJ # nttUr8M/Yv1QBzahl6tJ2yemID1nW7542shSYQ9Gc60ptCtcY+KSe6AXId9+lpqH # NzhVevIJGrDbaazdestxS5tZHAKLe0Rz3W8boigUzphSEgAzMkU= # SIG # End signature block |