Updatomatic.psm1
function Get-WSUSReportSummary { [CmdletBinding()] param( [string] $FilePath, [switch] $ShowHTML, [switch] $Online, [switch] $PassThru ) Write-Verbose -Message "Getting WSUS configuration..." $Configuration = Get-MyWSUSConfiguration -Type 'Email', 'Subscription', 'ComputerTargetGroups', 'Configuration', 'DatabaseConfiguration' Write-Verbose -Message "Getting WSUS computer updates..." $Computers = Get-MyWSUScomputerUpdates -UpdateSummary -GroupByAllGroupNames $ComputersNotContacted = foreach ($Group in $Computers.Keys) { foreach ($Computer in $Computers[$Group]) { if ($Computer.LastSyncTimeDays -ge 3) { $Computer } } } Write-Verbose -Message "Getting WSUS computer summary..." $Status = Get-MyWSUSComputerSummary Write-Verbose -Message "Creating HTML report - $FilePath" New-HTML { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text "Updatomatic - $($Script:Updatomatic['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } New-HTMLSection { New-HTMLChart { New-ChartPie -Name 'Computers needing updates' -Value $Status.ComputerTargetsNeedingUpdatesCount New-ChartPie -Name 'Computers with errors' -Value $Status.ComputerTargetsWithUpdateErrorsCount New-ChartPie -Name 'Computers up to date' -Value $Status.ComputersUpToDateCount } } } New-HTMLTab -Name 'Configuration' -IconSolid toolbox { New-HTMLTabPanel { New-HTMLTab -Name 'Configuration' { New-HTMLTable -DataTable $Configuration.Configuration -Filtering -ScrollX -ExcludeProperty 'UpdateServer', 'ServerId' } New-HTMLTab -Name 'Email' { New-HTMLTable -DataTable $Configuration.Email -Filtering -ScrollX -ExcludeProperty 'UpdateServer' } New-HTMLTab -Name 'Subscription' { New-HTMLTable -DataTable $Configuration.Subscription -Filtering -ScrollX -ExcludeProperty 'UpdateServer' } New-HTMLTab -Name 'Computer Target Groups' { New-HTMLTable -DataTable $Configuration.ComputerTargetGroups -Filtering -ScrollX -ExcludeProperty 'UpdateServer' } New-HTMLTab -Name 'Database Configuration' { New-HTMLTable -DataTable $Configuration.DatabaseConfiguration -Filtering -ScrollX -ExcludeProperty 'UpdateServer' } } } New-HTMLTab -Name 'Computers' -IconSolid desktop { New-HTMLTabPanel { New-HTMLTab -Name 'Not reporting' { New-HTMLSection -Name 'Not reporting' { New-HTMLTable -DataTable $ComputersNotContacted -Filtering -ScrollX -ExcludeProperty 'Id', 'AllComputerUpdates' { New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator gt -Value 10 -BackgroundColor CoralRed New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator le -Value 10 -BackgroundColor Salmon New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator le -Value 3 -BackgroundColor SpringGreen New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator gt -Value 10 -BackgroundColor CoralRed New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator le -Value 10 -BackgroundColor Salmon New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator le -Value 3 -BackgroundColor SpringGreen New-HTMLTableCondition -Name 'PendingReboot' -BackgroundColor SpringGreen -ComparisonType string -Operator eq -Value $false -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateUnknownCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateNotInstalledCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateInstalledPendingRebootCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateFailedCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon } } } foreach ($Group in $Computers.Keys) { New-HTMLTab -Name $Group { New-HTMLSection -Name $Group { New-HTMLTable -DataTable $Computers[$Group] -Filtering -ScrollX -ExcludeProperty 'Id', 'AllComputerUpdates' { New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator gt -Value 10 -BackgroundColor CoralRed New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator le -Value 10 -BackgroundColor Salmon New-HTMLTableCondition -Name 'LastSyncTimeDays' -HighlightHeaders 'LastSyncTimeDays', 'LastSyncTime' -ComparisonType number -Operator le -Value 3 -BackgroundColor SpringGreen New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator gt -Value 10 -BackgroundColor CoralRed New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator le -Value 10 -BackgroundColor Salmon New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays', 'LastReportedTime' -ComparisonType number -Operator le -Value 3 -BackgroundColor SpringGreen New-HTMLTableCondition -Name 'PendingReboot' -BackgroundColor SpringGreen -ComparisonType string -Operator eq -Value $false -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateUnknownCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateNotInstalledCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateInstalledPendingRebootCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon New-HTMLTableCondition -Name 'UpdateFailedCount' -BackgroundColor SpringGreen -ComparisonType number -Operator eq -Value 0 -FailBackgroundColor Salmon } } } } } } New-HTMLTab -Name 'Computer Updates' -IconSolid cloud { New-HTMLTabPanel { foreach ($Group in $Computers.Keys) { New-HTMLTab -Name $Group { New-HTMLSection -Name $Group { New-HTMLTable -DataTable $Computers[$Group].AllComputerUpdates -Filtering -ScrollX -PriorityProperties ComputerName { New-HTMLTableCondition -Name 'ApprovalStatus' -BackgroundColor SpringGreen -ComparisonType string -Operator eq -Value "Install" -FailBackgroundColor LightYellow New-HTMLTableCondition -Name 'UpdateInstallationState' -BackgroundColor Salmon -ComparisonType string -Operator eq -Value 'Unknown' New-HTMLTableCondition -Name 'UpdateInstallationState' -BackgroundColor Crimson -ComparisonType string -Operator eq -Value 'NotInstalled' New-HTMLTableCondition -Name 'UpdateInstallationState' -BackgroundColor MoonYellow -ComparisonType string -Operator eq -Value 'InstalledPendingReboot' New-HTMLTableCondition -Name 'UpdateInstallationState' -BackgroundColor SpringGreen -ComparisonType string -Operator eq -Value 'Installed' New-HTMLTableCondition -Name 'UpdateInstallationState' -BackgroundColor CornflowerBlue -ComparisonType string -Operator eq -Value 'Downloaded' New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays' -ComparisonType number -Operator gt -Value 10 -BackgroundColor CoralRed New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays' -ComparisonType number -Operator le -Value 10 -BackgroundColor Salmon New-HTMLTableCondition -Name 'LastReportedTimeDays' -HighlightHeaders 'LastReportedTimeDays' -ComparisonType number -Operator le -Value 3 -BackgroundColor SpringGreen } } } } } } } -FilePath $FilePath -ShowHTML:$ShowHTML -Online:$Online if ($PassThru) { [ordered] @{ Configuration = $Configuration Computers = $Computers } } } function Get-WSUSUpdatesForComputer { [CmdletBinding()] param( [Microsoft.UpdateServices.Internal.BaseApi.ComputerTarget] $ComputerTarget, [int] $LastReportedTimeDays = -1, [DateTime] $Today = (Get-Date) ) if (-not $Script:Updatomatic.CachedUpdatesInformation) { Write-Verbose -Message "Getting WSUS available updates... (cache only), as no cached updates information is available." Get-MyWSUSUpdateState -CacheOnly } Write-Verbose -Message "Getting updates status/details for computer $($ComputerTarget.FullDomainName)" $AllComputerUpdates = $ComputerTarget.GetUpdateInstallationInfoPerUpdate() foreach ($Update in $AllComputerUpdates) { if ($Update.UpdateInstallationState -ne [Microsoft.UpdateServices.Administration.UpdateInstallationState]::NotApplicable) { $UpdateInformation = $Script:Updatomatic.CachedUpdatesInformation[$Update.UpdateId.Guid] [PSCustomObject]@{ KBNumber = $UpdateInformation.KnowledgebaseArticles -join ", " ApprovalStatus = $Update.UpdateApprovalAction UpdateInstallationState = $Update.UpdateInstallationState ArrivalDateDays = [math]::Round(($Today - $UpdateInformation.ArrivalDate).TotalDays, 0) LastReportedTimeDays = $LastReportedTimeDays UpdateType = $UpdateInformation.UpdateType ProductTitles = $UpdateInformation.ProductTitles UpdateTitle = $UpdateInformation.Title IsLatestRevision = $UpdateInformation.IsLatestRevision ArrivalDate = $UpdateInformation.ArrivalDate ComputerName = $ComputerTarget.FullDomainName } } } } function Connect-MyWSUS { [CmdletBinding()] param( [Parameter(Mandatory)][string] $ServerName, [int] $Port = 8530, [switch] $UseSsl, [switch] $DoNotSuppress ) try { $WSUSServer = Get-WsusServer -Name $ServerName -PortNumber $Port -UseSsl:$UseSsl -ErrorAction Stop } catch { if ($ErrorActionPreference -eq 'Stop') { throw } else { Write-Warning -Message "Connect-MyWSUS - Couldn't connect to WSUS server: $ServerName. Error: $($_.Exception.Message)" return } } $Script:Updatomatic = @{ Version = "0.2.0" WSUSServer = $WSUSServer } if ($DoNotSuppress) { $Script:Updatomatic } } function Get-MyWSUSComputerClient { [cmdletbinding(DefaultParameterSetName = 'AllComputers')] param( [Parameter(Position = 0, ParameterSetName = 'Computer')] [string[]]$ComputerName, [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Administration.UpdateInstallationStates]$IncludedInstallationState, [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Administration.UpdateInstallationStates]$ExcludedInstallState, [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Internal.BaseApi.ComputerTargetGroup[]]$ComputerTargetGroups, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$FromLastStatusTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$ToLastStatusTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$FromLastSyncTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$ToLastSyncTime, [Parameter(ParameterSetName = 'ComputerScope')] [string]$OSFamily, [Parameter(ParameterSetName = 'ComputerScope')] [switch]$IncludeSubGroups, [Parameter(ParameterSetName = 'ComputerScope')] [switch]$IncludeDownstreamComputerTargets ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSComputerClient - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $WSUSServer = $Script:Updatomatic.WSUSServer if ($PSCmdlet.ParameterSetName -eq 'ComputerScope') { $ComputerScope = [Microsoft.UpdateServices.Administration.ComputerTargetScope]::new() If ($PSBoundParameters['IncludedInstallationState']) { $ComputerScope.IncludedInstallationStates = $IncludedInstallationState } If ($PSBoundParameters['ExcludedInstallState']) { $ComputerScope.ExcludedInstallationStates = $ExcludedInstallState } If ($PSBoundParameters['FromLastStatusTime']) { $ComputerScope.FromLastReportedStatusTime = $FromLastStatusTime } If ($PSBoundParameters['ToLastStatusTime']) { $ComputerScope.ToLastReportedStatusTime = $ToLastStatusTime } If ($PSBoundParameters['FromLastSyncTime']) { $ComputerScope.FromLastSyncTime = $FromLastSyncTime } If ($PSBoundParameters['ToLastSyncTime']) { $ComputerScope.ToLastSyncTime = $ToLastSyncTime } If ($PSBoundParameters['IncludeSubGroups']) { $ComputerScope.IncludeSubgroups = $IncludeSubGroups } If ($PSBoundParameters['OSFamily']) { $ComputerScope.OSFamily = $OSFamily } If ($PSBoundParameters['IncludeDownstreamComputerTargets']) { $ComputerScope.IncludeDownstreamComputerTargets = $IncludeDownstreamComputerTargets } If ($PSBoundParameters['ComputerTargetGroups']) { $null = $ComputerScope.ComputerTargetGroups.AddRange($ComputerTargetGroups) } } if ($PSCmdlet.ParameterSetName -eq 'AllComputers') { Write-Verbose "Get-MyWSUSComputerClient - Gathering computers from all computer targets" $WSUSServer.GetComputerTargets() } elseif ($PSCmdlet.ParameterSetName -eq 'Computer') { $Count = 0 ForEach ($Computer in $ComputerName) { $Count++ Write-Verbose "Get-MyWSUSComputerClient - [$Count/$($ComputerName.Count)]Retrieve computer in WSUS" Try { $WSUSServer.SearchComputerTargets($Computer) } Catch { Write-Warning -Message "Get-MyWSUSComputerClient - Unable to retrieve $Computer from WSUS database." } } } elseif ($PSCmdlet.ParameterSetName -eq 'ComputerScope') { Write-Verbose -Message "Get-MyWSUSComputerClient - Gathering computers from computer target scope" $WSUSServer.GetComputerTargets($ComputerScope) } } function Get-MyWSUSComputerSummary { [CmdletBinding()] param ( [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Administration.UpdateInstallationStates]$IncludedInstallationState, [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Administration.UpdateInstallationStates]$ExcludedInstallState, [Parameter(ParameterSetName = 'ComputerScope')] [string[]]$ComputerTargetGroupsName, [Parameter(ParameterSetName = 'ComputerScope')] [string[]]$ComputerTargetGroupsId, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$FromLastStatusTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$ToLastStatusTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$FromLastSyncTime, [Parameter(ParameterSetName = 'ComputerScope')] [DateTime]$ToLastSyncTime, [Parameter(ParameterSetName = 'ComputerScope')] [string]$OSFamily, [Parameter(ParameterSetName = 'ComputerScope')] [switch]$IncludeSubGroups, [Parameter(ParameterSetName = 'ComputerScope')] [switch]$IncludeDownstreamComputerTargets, [Parameter(ParameterSetName = 'ComputerScope')] [Microsoft.UpdateServices.Administration.UpdateSources] $UpdateSources = [Microsoft.UpdateServices.Administration.UpdateSources]::All ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSComputerStatus - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $WSUSServer = $Script:Updatomatic.WSUSServer if ($ComputerTargetGroupsName -or $ComputerTargetGroupsId) { $ComputerTargetGroups = Get-MyWSUSComputerTargetGroups -ComputerTargetGroupsName $ComputerTargetGroupsName -ComputerTargetGroupsId $ComputerTargetGroupsId if ($ComputerTargetGroups.Count -ne ($ComputerTargetGroupsName.Count + $ComputerTargetGroupsId.Count)) { Write-Warning -Message "Get-MyWSUSComputerStatus - Requested amount of ComputerTargetGroups doesn't match. Terminating early." return } } else { $ComputerTargetGroups = $null } if ($PSCmdlet.ParameterSetName -eq 'ComputerScope') { $ComputerScope = [Microsoft.UpdateServices.Administration.ComputerTargetScope]::new() If ($PSBoundParameters['IncludedInstallationState']) { $ComputerScope.IncludedInstallationStates = $IncludedInstallationState } If ($PSBoundParameters['ExcludedInstallState']) { $ComputerScope.ExcludedInstallationStates = $ExcludedInstallState } If ($PSBoundParameters['FromLastStatusTime']) { $ComputerScope.FromLastReportedStatusTime = $FromLastStatusTime } If ($PSBoundParameters['ToLastStatusTime']) { $ComputerScope.ToLastReportedStatusTime = $ToLastStatusTime } If ($PSBoundParameters['FromLastSyncTime']) { $ComputerScope.FromLastSyncTime = $FromLastSyncTime } If ($PSBoundParameters['ToLastSyncTime']) { $ComputerScope.ToLastSyncTime = $ToLastSyncTime } If ($PSBoundParameters['IncludeSubGroups']) { $ComputerScope.IncludeSubgroups = $IncludeSubGroups } If ($PSBoundParameters['OSFamily']) { $ComputerScope.OSFamily = $OSFamily } If ($PSBoundParameters['IncludeDownstreamComputerTargets']) { $ComputerScope.IncludeDownstreamComputerTargets = $IncludeDownstreamComputerTargets } If ($ComputerTargetGroups) { $null = $ComputerScope.ComputerTargetGroups.AddRange($ComputerTargetGroups) } } $WSUSServer.GetComputerStatus($ComputerScope, $UpdateSources) } function Get-MyWSUSComputerTargetGroups { [CmdletBinding()] param( [alias('Name')][string[]]$ComputerTargetGroupsName, [alias('Id')][string[]]$ComputerTargetGroupsId ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSComputerTargetGroups - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $WSUSServer = $Script:Updatomatic.WSUSServer $Configuration = $WSUSServer.GetConfiguration() $AllComputerTargetGroups = [Microsoft.UpdateServices.Internal.BaseApi.ComputerTargetGroup]::GetAll($Configuration.UpdateServer) if ($ComputerTargetGroupsName -or $ComputerTargetGroupsId) { $Configuration = $WSUSServer.GetConfiguration() $AllComputerTargetGroups = [Microsoft.UpdateServices.Internal.BaseApi.ComputerTargetGroup]::GetAll($Configuration.UpdateServer) if ($ComputerTargetGroupsName) { $Names = foreach ($Name in $ComputerTargetGroupsName) { foreach ($TargetGroup in $AllComputerTargetGroups) { if ($TargetGroup.Name -eq $Name) { Write-Verbose -Message "Found ComputerTargetGroup: $($TargetGroup.Name)" $TargetGroup } } } if ($Names) { $Names } else { Write-Warning -Message "ComputerTargetGroup not found: $Name" } } if ($ComputerTargetGroupsId) { $Ids = foreach ($Id in $ComputerTargetGroupsId) { foreach ($TargetGroup in $AllComputerTargetGroups) { if ($TargetGroup.Id -eq $Id) { Write-Verbose -Message "Found ComputerTargetGroup: $($TargetGroup.Name)" $TargetGroup } } } if ($Ids) { $Ids } else { Write-Warning -Message "ComputerTargetGroup not found: $Id" } } } else { $AllComputerTargetGroups } } function Get-MyWSUScomputerUpdates { [CmdletBinding()] param( [Microsoft.UpdateServices.Administration.UpdateApprovalActions] $UpdateScope, [switch] $UpdateSummary, [switch] $GroupByGroupName, [switch] $GroupByAllGroupNames ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUScomputerUpdates - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $Today = [datetime]::Now $Grouping = [ordered] @{} Get-MyWSUSUpdateState -CacheOnly $WSUSServer = $Script:Updatomatic.WSUSServer $Scope = [Microsoft.UpdateServices.Administration.ComputerTargetScope]::new() $Count = 0 $Computers = $WSUSServer.GetComputerTargets($Scope) foreach ($Computer in $Computers) { $Count++ Write-Verbose -Message "Processing computer [$Count/$($Computers.Count)] $($Computer.FullDomainName)]" $LastReportedTimeDays = [math]::Round(($Today - $Computer.LastReportedStatusTime).TotalDays, 0) $AllComputerUpdates = Get-WsusUpdatesForComputer -ComputerTarget $Computer -LastReportedTimeDays $LastReportedTimeDays -Today $Today $AllTargetGroups = $Computer.GetComputerTargetGroups() | ForEach-Object { $_.Name } $FullDomainNameParts = $Computer.FullDomainName -split '\.' $HostName = $FullDomainNameParts[0] $Domain = ($FullDomainNameParts[1..($FullDomainNameParts.Length - 1)] -join '.') $Output = [ordered] @{ Id = $Computer.Id HostName = $HostName Domain = $Domain FullDomainName = $Computer.FullDomainName Groups = $AllTargetGroups PendingReboot = $null LastSyncTimeDays = [math]::Round(($Today - $Computer.LastSyncTime).TotalDays, 0) LastReportedTimeDays = $LastReportedTimeDays } $EndData = [ordered] @{ IPAddress = $Computer.IPAddress Make = $Computer.Make Model = $Computer.Model ClientVersion = $Computer.ClientVersion OS = $Computer.OSDescription ComputerRole = $Computer.ComputerRole LastSyncTime = $Computer.LastSyncTime LastSyncResult = $Computer.LastSyncResult LastReportedTime = $Computer.LastReportedStatusTime RequestedGroup = $Computer.RequestedTargetGroupName AllComputerUpdates = $AllComputerUpdates } if ($UpdateSummary) { $UpdateInstallationSummary = Get-MyWSUSSummaryUpdateInstallation -ComputerTarget $Computer -Today $Today if ($UpdateInstallationSummary.UpdateInstalledPendingRebootCount -gt 0) { $Output.PendingReboot = $true } else { $Output.PendingReboot = $false } $Object = [PSCustomObject] ($Output + $UpdateInstallationSummary + $EndData) } else { $Object = [PSCustomObject] ($Output + $EndData) } if ($GroupByAllGroupNames) { foreach ($Group in $Output.Groups) { if (-not $Grouping[$Group]) { $Grouping[$Group] = [System.Collections.Generic.List[PSCustomObject]]::new() } $Grouping[$Group].Add($Object) } } elseif ($GroupByGroupName) { if (-not $Grouping[$EndData.RequestedGroup]) { $Grouping[$EndData.RequestedGroup] = [System.Collections.Generic.List[PSCustomObject]]::new() } $Grouping[$EndData.RequestedGroup].Add($Object) } else { $Object } } if ($GroupByGroupName -or $GroupByAllGroupNames) { $Grouping } } function Get-MyWSUSConfiguration { [CmdletBinding()] param( [ValidateSet('Email', 'Subscription', 'ComputerTargetGroups', 'Configuration', 'DatabaseConfiguration')] [string[]] $Type = 'Configuration' ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSConfiguration - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $WSUSServer = $Script:Updatomatic.WSUSServer $OutputConfigurations = [ordered] @{} foreach ($T in $Type) { switch ($T) { 'Email' { $OutputConfigurations['Email'] = $WSUSServer.GetEmailNotificationConfiguration() } 'Subscription' { $OutputConfigurations['Subscription'] = $WSUSServer.GetSubscription() } 'ComputerTargetGroups' { $OutputConfigurations['ComputerTargetGroups'] = $WSUSServer.GetComputerTargetGroups() } 'Configuration' { $OutputConfigurations['Configuration'] = $WSUSServer.GetConfiguration() } 'DatabaseConfiguration' { $OutputConfigurations['DatabaseConfiguration'] = $WSUSServer.GetDatabaseConfiguration() } default { Write-Warning -Message "Get-MyWSUSConfiguration - Unknown type: $T" } } } if ($Type.Count -eq 1) { $OutputConfigurations[$Type[0]] } else { $OutputConfigurations } } function Get-MyWSUSStatus { [CmdletBinding()] param( ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSStatus - Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } $WSUSServer = $Script:Updatomatic.WSUSServer $WSUSServer.GetStatus() } function Get-MyWSUSSummaryUpdateInstallation { [CmdletBinding()] param ( [Parameter(Mandatory)][Microsoft.UpdateServices.Internal.BaseApi.ComputerTarget] $ComputerTarget, [DateTime] $Today = (Get-Date) ) $Data = $ComputerTarget.GetUpdateInstallationSummary() [ordered] @{ UpdateUnknownCount = $Data.UnknownCount UpdateNotInstalledCount = $Data.NotInstalledCount UpdateDownloadedCount = $Data.DownloadedCount UpdateInstalledCount = $Data.InstalledCount UpdateInstalledPendingRebootCount = $Data.InstalledPendingRebootCount UpdateFailedCount = $Data.FailedCount UpdateNotApplicableCount = $Data.NotApplicableCount UpdateLastUpdated = $Data.LastUpdated UpdateLastUpdatedDays = [math]::Round(($Today - $Data.LastUpdated).TotalDays, 0) } } function Get-MyWSUSUpdateState { [CmdletBinding()] param( [Microsoft.UpdateServices.Administration.UpdateApprovalActions] $UpdateScope, [switch] $CacheOnly ) if (-not $Script:Updatomatic) { Write-Warning -Message "Get-MyWSUSUpdateState: Updatomatic is not connected to a WSUS server. Use Connect-MyWSUS to connect." return } if ($CacheOnly) { Write-Verbose -Message "Getting WSUS available updates... (cache only)" } else { Write-Verbose -Message "Getting WSUS available updates..." } $WSUSServer = $Script:Updatomatic.WSUSServer $CachedUpdatesInformation = [ordered] @{} if ($UpdateScope) { $UpdateScope = [Microsoft.UpdateServices.Administration.UpdateScope]::new() $UpdateScope.UpdateApprovalActions = $UpdateScope $ListUpdates = $WSUSServer.GetUpdates($UpdateScope) } else { $ListUpdates = $WSUSServer.GetUpdates() } foreach ($Update in $ListUpdates) { $CachedUpdatesInformation[$update.Id.UpdateId.Guid] = $Update if (-not $CacheOnly) { $Update } } $Script:Updatomatic.CachedUpdatesInformation = $CachedUpdatesInformation } function Show-MyWSUS { [CmdletBinding()] param( [string] $FilePath, [switch] $ShowHTML, [switch] $Online, [Parameter(Mandatory)][ValidateSet( 'ComputersSummary' )][string] $Reports, [switch] $PassThru ) if ($Reports -eq 'ComputersSummary') { Get-WSUSReportSummary -FilePath $FilePath -ShowHTML:$ShowHTML -Online:$Online -PassThru:$PassThru } else { Write-Warning -Message "Show-MyWSUS - Unknown report: $Reports" } } Export-ModuleMember -Function @('Connect-MyWSUS', 'Get-MyWSUSComputerClient', 'Get-MyWSUSComputerSummary', 'Get-MyWSUSComputerTargetGroups', 'Get-MyWSUScomputerUpdates', 'Get-MyWSUSConfiguration', 'Get-MyWSUSStatus', 'Get-MyWSUSSummaryUpdateInstallation', 'Get-MyWSUSUpdateState', 'Show-MyWSUS') -Alias @() # SIG # Begin signature block # MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAszIML3jyLkGgl # wbRCEsznM6xsicslZHHypfXTmDZTSaCCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp # BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy # NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD # VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD # Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo # 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO # H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R # eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX # j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV # DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J # idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C # acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N # Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X # CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB # AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s # BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X # LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l # U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl # cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU # aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS # R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB # b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC # zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1 # UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp # ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE # sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8 # a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF # YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q # EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE # deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J # ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq # F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX # DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz # dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ # cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA # 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh # vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn # 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr # W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w # iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP # lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf # QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28 # eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4 # fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0 # 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC # AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz # ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 # dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu # Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB # CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E # BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc # 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ # f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51 # 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev # xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx # h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY # D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/ # Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq # G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH # v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP # AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2 # IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF # AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor # BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3 # DQEJBDEiBCAddm4ONNHQ12mHLqhVyIDhl/gYc7KlFu+4F1sDLews8jANBgkqhkiG # 9w0BAQEFAASCAgBiRHUuX47S3JGFcbrJa2xPt25dLtabv3o/2+kUmBZMXOVQqKn5 # VeaIrlkGvOOiMc/ss2EyHzraHNx21HkChHwQ/1dG6SQM01XH7WF+QimpGba50aX/ # IVEiS5tbh7g0dRNHIt5KJJAxdkKSd2+j3XQQHjz6hcgAQk8e47l8TtYThDxAZ5iu # 4fiNLXS6mbfw3B/IYswBRN53HX42MPEb+F3nli5f6I9lTjODhBsmG/RDHLNj9q0b # ZJhHMLyBq+hxDllM2jy8BWCWplBNB75eLTXrczk3vxhNmD7wipkNICbivEKy+uJY # zklg55tzsjTGGOcnaCACF0Qq21+hmHnEFVJpbUQ0RlsPuPRGD3AXyG1GqtAtLFPV # cWtQgCuyBCxVZ5Nyp49igsEjUa4jZhqQmSoajD+o9IMYhVUfVz1f2j25tRuQXajH # GwnrdQeXl1x15v+YWM61q+KmQkLOnswR/I0/4t+eLWxjM8JDNquk4vvJKhFe0Efb # mEJCBxZhUDs914TTgG8ipZxoU5YK2Y+ijnD4Nhb1tVDgbXAJc+fMbSDDTcG+yNi4 # Np+VWOoSXdOft8xgr+y+kOATSwpyjy3l3dVfGsbg/lytZOjNpnNbys5NgqFxcTL0 # zKPWbQVqWdWzRtF1xelppVeT7bXaYBee/TiB5/95CySJLZbwHUsenp1mN6GCAyAw # ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH # NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj # MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG # CSqGSIb3DQEJBTEPFw0yNDEwMjAxNzA4NTRaMC8GCSqGSIb3DQEJBDEiBCA/6piY # +AomvwbQs9OWVkPbbq50VfjrOiP9wBdHvXcuHzANBgkqhkiG9w0BAQEFAASCAgC2 # GpDb5hjCNFcdOgOBPAhduDhVd68tRh5lMf4FJtMUkXVvoYfvAt2IlZgm5UkVzBcL # Z9dJEfI9bi6YGL9IwLhSWYt91Ln+73QsWyht99SkyTMa2mmT4cqg6aAv7fq2Rxby # CdqFHLjK0xYP/0pTCIOucQg8iK1DhxgP5jNYXWlVogT75UdA3FZFfDlGoyGJy1cQ # zkqJm4ucGm+ha3HSeJ2NOjbylcSN6Ci8RUefwu38WlzMD4HJySnEUBZbzgBbq0Kh # J960C2ZZJFiacaOFvyqcWVwYPer6vngl4No3ciMMmZK3JKAOcNUEHtjt7sp6MH1/ # KbCEGrUExy2wESaHApo2D0vd5KAn1sStmGIU+zccUgzrhxcM5m2WZNXt7yoK4q+E # zg7Xf+rDy2j0TqefVb1mTp6uVT7KxKe1Km8j9PGYqT2/62bjjDZyCvIozZGMBbni # fi42lmvWe5ma0oyRl+9u0rswFPwj0XK6P/OCm2nITl8YEZDLvIKaQ6MAeg+53zeP # EqkgsDfXqxnBhT8OOqB+FVNDxEeNWtcqi5Qmkeahw17bSx1Z1beqzA8MEaEIRFqD # Wygf8guS/f2CrD59zZ63CspdjdqY5jI63tvsx4XRr3unJsOM89Q/mFFL+XJMPFaz # Dw3JyS4JsrirRA9zIZryd6pD7bZKwFV1o4zr6yQJhw== # SIG # End signature block |