Auto-commit: 2025-10-31 08:55:43
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -4,3 +4,11 @@ config*.json
|
||||
.env.*
|
||||
*.key
|
||||
*.pem
|
||||
# Ignore local config.json
|
||||
config.json
|
||||
|
||||
# Ignore log files
|
||||
*.log
|
||||
|
||||
# Ignore CSV exports
|
||||
*.csv
|
||||
|
||||
42
Connect-IntegratedDatabase/readme.md
Normal file
42
Connect-IntegratedDatabase/readme.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Connect to WSUS Integrated Database (WID)
|
||||
|
||||
Connect locally to the WSUS Windows Internal Database (WID) via the named pipe to run maintenance or reporting against the `SUSDB` database. WID does not allow remote connections—run tools **on the WSUS server** with administrative privileges.
|
||||
|
||||
## Prerequisites
|
||||
- Run on the WSUS server, **elevated** (Run as Administrator).
|
||||
- One of: SSMS, `sqlcmd`, or the PowerShell `SqlServer` module.
|
||||
- Pipe (WID on Server 2012+): `\\.\pipe\MICROSOFT##WID\tsql\query`
|
||||
Database: `SUSDB`
|
||||
|
||||
## SSMS (GUI)
|
||||
1. Start SSMS “Run as administrator”.
|
||||
2. Server type: Database Engine
|
||||
Server name: `\\.\pipe\MICROSOFT##WID\tsql\query`
|
||||
Authentication: Windows Authentication
|
||||
3. Connect and open a new query window.
|
||||
|
||||
Quick smoke test:
|
||||
```sql
|
||||
SELECT @@VERSION AS SqlEngineVersion, DB_NAME() AS CurrentDatabase;
|
||||
SELECT TOP (5) name, create_date FROM sys.tables ORDER BY create_date DESC;
|
||||
```
|
||||
## sqlcmd (CLI)
|
||||
```bat
|
||||
:: Run locally on the WSUS server (elevated)
|
||||
sqlcmd -S np:\\.\pipe\MICROSOFT##WID\tsql\query -d SUSDB -E -Q "SELECT TOP (1) GETDATE() AS ConnectedAt;"
|
||||
```
|
||||
|
||||
## PowerShell (Invoke-Sqlcmd)
|
||||
```PowerShell
|
||||
Import-Module SqlServer
|
||||
|
||||
$Instance = "\\.\pipe\MICROSOFT##WID\tsql\query"
|
||||
$Database = "SUSDB"
|
||||
|
||||
# One-liner test
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Database $Database -Query "SELECT TOP (1) GETDATE() AS ConnectedAt;"
|
||||
|
||||
# Run a maintenance script
|
||||
$FileSql = ".\Maintenance.sql" # e.g., reindex/cleanup queries
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Database $Database -InputFile $FileSql
|
||||
```
|
||||
37
Dashboard/readme.md
Normal file
37
Dashboard/readme.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# WSUS HTML Report (PowerShell)
|
||||
|
||||
Generates a **single-page HTML dashboard** for WSUS: target-group inventory, KB deployment status (timeline + donuts), per-KB computer states, and a table of computers with failed updates.
|
||||
|
||||
## Features
|
||||
- Computers by **Target Group** (table + pie).
|
||||
- KB status per group: **Installed vs Missing** and **approval age timeline**.
|
||||
- Per-KB list of affected computers (hidden sections you can expand).
|
||||
- **Failed** updates: computers in error.
|
||||
|
||||
## Requirements
|
||||
- Run on the **WSUS server** as Administrator.
|
||||
- Module: `PSWriteHTML`
|
||||
```powershell
|
||||
Install-Module PSWriteHTML -Scope CurrentUser
|
||||
```
|
||||
- WSUS API available (either the `UpdateServices` module or
|
||||
`Microsoft.UpdateServices.Administration.dll` at
|
||||
`"%ProgramFiles%\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"`).
|
||||
- The script uses `-Online` (CDN assets). Remove that flag if your server has no internet.
|
||||
|
||||
## Quick start
|
||||
```powershell
|
||||
# 1) Save the script as: C:\Scripts\Wsus-Report.ps1
|
||||
# 2) Run it (elevated)
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Wsus-Report.ps1
|
||||
# Output:
|
||||
# C:\exploit\report\default.htm
|
||||
```
|
||||
|
||||
## Customize
|
||||
- Output path: `$outFile` (default `C:\exploit\report\default.htm`).
|
||||
- Exclusions for titles (ARM64, SSU, Flash, etc.) in the KB section.
|
||||
- Table/chart IDs if you embed multiple reports.
|
||||
|
||||
## Full article
|
||||
https://blog.wuibaille.fr/2024/10/creating-a-wsus-dashboard/
|
||||
231
Dashboard/report.ps1
Normal file
231
Dashboard/report.ps1
Normal file
@@ -0,0 +1,231 @@
|
||||
#Requires -RunAsAdministrator
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ****************************** MODULE IMPORT & INITIALIZATION ******************************
|
||||
# HTML reporting module
|
||||
Import-Module -Name PSWriteHTML -ErrorAction Stop
|
||||
|
||||
Write-Host "Starting WSUS report generation..."
|
||||
|
||||
# Load WSUS admin assembly explicitly (no deprecated LoadWithPartialName)
|
||||
$wsusDll = "$env:ProgramFiles\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"
|
||||
if (Test-Path $wsusDll) { Add-Type -Path $wsusDll } else { throw "WSUS Admin DLL not found: $wsusDll" }
|
||||
|
||||
# Connect to local WSUS and pull data
|
||||
$wsusServer = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
|
||||
$updates = $wsusServer.GetUpdates()
|
||||
$computers = $wsusServer.GetComputerTargets()
|
||||
$targetGroups = $wsusServer.GetComputerTargetGroups()
|
||||
|
||||
# Map groupId -> groupName for quick lookups
|
||||
$groupHashTable = @{}
|
||||
$targetGroups | ForEach-Object { $groupHashTable[$_.ID] = $_.Name }
|
||||
|
||||
Write-Host "Retrieved WSUS data: Updates, Computers, and Target Groups."
|
||||
|
||||
# Ensure output folder exists
|
||||
$outFile = "C:\exploit\report\default.htm"
|
||||
$outDir = Split-Path $outFile -Parent
|
||||
if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null }
|
||||
|
||||
# ****************************** REPORT GENERATION START ******************************
|
||||
New-HTML -TitleText 'WSUS Report' {
|
||||
|
||||
# ****************************** COMPUTERS BY TARGET GROUP ******************************
|
||||
# Build a simple dataset: group name + computer count (skip 'All Computers')
|
||||
$DataTable = New-Object System.Collections.Generic.List[Object]
|
||||
foreach ($group in $targetGroups) {
|
||||
$computersCount = $group.GetComputerTargets().Count
|
||||
if ($computersCount -gt 0 -and $group.Name -ne 'All Computers') {
|
||||
$DataTable.Add([PSCustomObject]@{
|
||||
TargetName = $group.Name
|
||||
Total = $computersCount
|
||||
})
|
||||
}
|
||||
}
|
||||
Write-Host "Populated DataTable with computer counts per Target Group."
|
||||
|
||||
New-HTMLSection -HeaderText 'Computers By TargetGroup' {
|
||||
# Table
|
||||
New-HTMLPanel {
|
||||
New-HTMLTable -DataTable $DataTable -HideFooter -DataTableID 'IDtargetGroup' {
|
||||
# Bind events to the correct table ID
|
||||
New-TableEvent -ID 'IDtargetGroup' -SourceColumnID 0 -TargetColumnId 0
|
||||
}
|
||||
}
|
||||
# Pie chart per group
|
||||
New-HTMLPanel {
|
||||
New-HTMLChart {
|
||||
New-ChartToolbar -Download
|
||||
foreach ($row in $DataTable) { New-ChartPie -Name $row.TargetName -Value $row.Total }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ****************************** COMPUTER LIST (HIDDEN) ******************************
|
||||
$ListeComputersWSUS = @()
|
||||
foreach ($computer in $computers) {
|
||||
foreach ($tg in $computer.GetComputerTargetGroups()) {
|
||||
if ($tg.Name -ne 'All Computers') {
|
||||
$ListeComputersWSUS += [PSCustomObject]@{
|
||||
TargetName = $tg.Name
|
||||
ComputerName = $computer.FullDomainName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
New-HTMLSection -HeaderText 'Computers' -Invisible {
|
||||
New-HTMLTable -DataTable $ListeComputersWSUS -DataTableID 'AllComputertargetGroup' -HideFooter
|
||||
}
|
||||
|
||||
# ****************************** DEPLOYMENT STATUS OF KB UPDATES ******************************
|
||||
$DateNow = Get-Date
|
||||
$ListeKB = @()
|
||||
$MaxDaysReport = 90 # (kept for future filtering if needed)
|
||||
|
||||
foreach ($update in $updates) {
|
||||
foreach ($approval in $update.GetUpdateApprovals()) {
|
||||
foreach ($tg in $targetGroups) {
|
||||
if ($tg.Id -eq $approval.ComputerTargetGroupId) {
|
||||
$DateKB = $approval.GoLiveTime
|
||||
$LastChange = New-TimeSpan -Start $DateKB -End $DateNow
|
||||
$ActionType = $approval.Action
|
||||
|
||||
if ($ActionType -eq 'Install') {
|
||||
$KBTitre = $update.Title
|
||||
$installKB = 0; $MissKB = 0
|
||||
|
||||
# Summaries per group for this update
|
||||
foreach ($sum in $update.GetSummaryPerComputerTargetGroup()) {
|
||||
$groupName = $groupHashTable[$sum.ComputerTargetGroupId]
|
||||
if ($tg.Name -eq $groupName) {
|
||||
$installKB = $sum.InstalledCount + $sum.InstalledPendingRebootCount
|
||||
$MissKB = $sum.UnknownCount + $sum.NotInstalledCount + $sum.DownloadedCount + $sum.FailedCount
|
||||
}
|
||||
}
|
||||
|
||||
# Keep only meaningful KBs (exclude ARM64, SSU, Flash, specific KBs)
|
||||
if (($installKB -ne 0 -or $MissKB -ne 0) -and
|
||||
($KBTitre -notmatch 'ARM64') -and
|
||||
($KBTitre -notmatch 'Servicing Stack Update') -and
|
||||
($KBTitre -notmatch 'Flash Player') -and
|
||||
($KBTitre -notlike '*KB4470788*') -and
|
||||
($KBTitre -notlike '*KB4499728*')) {
|
||||
$ListeKB += [PSCustomObject]@{
|
||||
KBTitre = $KBTitre
|
||||
KBGroup = $tg.Name
|
||||
KBChang = $LastChange.Days
|
||||
ActionType = $ActionType
|
||||
installKB = $installKB
|
||||
MissKB = $MissKB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sort by age then title; get unique titles for chart grouping
|
||||
$ListeKB = $ListeKB | Sort-Object KBChang, KBTitre
|
||||
$groupedByTitle = @{}
|
||||
foreach ($o in $ListeKB) {
|
||||
if (-not $groupedByTitle.ContainsKey($o.KBTitre)) { $groupedByTitle[$o.KBTitre] = @() }
|
||||
$groupedByTitle[$o.KBTitre] += $o
|
||||
}
|
||||
|
||||
foreach ($Title in $groupedByTitle.Keys) {
|
||||
# Timeline per group (how many days since approval)
|
||||
New-HTMLSection -HeaderText $Title {
|
||||
New-HTMLChart -Title $Title -TitleAlignment center {
|
||||
foreach ($o in $groupedByTitle[$Title]) {
|
||||
New-ChartTimeline -DateFrom (Get-Date).AddDays(-$o.KBChang - 1) -DateTo (Get-Date) -Name $o.KBGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Donut charts (Installed vs Missing) per group – hidden
|
||||
New-HTMLSection -HeaderText 'Computers' -Invisible {
|
||||
foreach ($o in $groupedByTitle[$Title]) {
|
||||
New-HTMLPanel {
|
||||
New-HTMLChart -Title $o.KBGroup {
|
||||
New-ChartLegend -Name "Installed","Missing" -Color Green,Red
|
||||
New-ChartDonut -Name "Installed" -Value $o.installKB
|
||||
New-ChartDonut -Name "Missing" -Value $o.MissKB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------- KB and Computers status table (hidden) -------------------------
|
||||
$updateScope2 = New-Object Microsoft.UpdateServices.Administration.UpdateScope
|
||||
$updateScope2.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved
|
||||
$updateScope2.UpdateApprovalActions = [Microsoft.UpdateServices.Administration.UpdateApprovalActions]::Install
|
||||
$updateScope2.UpdateSources = [Microsoft.UpdateServices.Administration.UpdateSources]::MicrosoftUpdate
|
||||
$updateScope2.ExcludedInstallationStates = @('NotApplicable','Installed','InstalledPendingReboot')
|
||||
|
||||
$allComputers = $wsusServer.GetComputerTargets()
|
||||
$resultsComputerStatus = @()
|
||||
|
||||
foreach ($pc in $allComputers) {
|
||||
$updatelist = $pc.GetUpdateInstallationInfoPerUpdate($updateScope2)
|
||||
foreach ($ui in $updatelist) {
|
||||
$updInfo = $ui.GetUpdate()
|
||||
$approvalGroup = $ui.GetUpdateApprovalTargetGroup().Name
|
||||
if ($approvalGroup -ne 'All Computers' -and $updInfo.IsApproved) {
|
||||
$resultsComputerStatus += [pscustomobject][Ordered]@{
|
||||
ComputerName = $pc.FullDomainName
|
||||
Status = $ui.UpdateInstallationState
|
||||
ApprovalTargetGroup= $approvalGroup
|
||||
Approved = $updInfo.IsApproved
|
||||
Title = $updInfo.Title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
New-HTMLSection -HeaderText 'Computers' -Invisible {
|
||||
foreach ($o in $groupedByTitle[$Title]) {
|
||||
New-HTMLPanel {
|
||||
$specific = $resultsComputerStatus |
|
||||
Where-Object { $_.Title -like "*$Title*" -and $_.ApprovalTargetGroup -like "*$($o.KBGroup)*" } |
|
||||
Select-Object ComputerName, Status
|
||||
New-HTMLTable -DataTable $specific -HideFooter -HideButtons
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ****************************** COMPUTERS IN ERROR ******************************
|
||||
$computersInError = @()
|
||||
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
|
||||
$updateScope.IncludedInstallationStates = 'Failed'
|
||||
$computerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
|
||||
$computerScope.IncludedInstallationStates = 'Failed'
|
||||
|
||||
$computersInError = $wsusServer.GetComputerTargets($computerScope) | ForEach-Object {
|
||||
$compName = $_.FullDomainName
|
||||
$failedTitles = ($_.GetUpdateInstallationInfoPerUpdate($updateScope) | ForEach-Object {
|
||||
$_.GetUpdate().Title
|
||||
}) -join ', '
|
||||
if ($failedTitles) {
|
||||
[pscustomobject]@{
|
||||
Computername = $compName
|
||||
TargetGroups = ($_.GetComputerTargetGroups() | Select-Object -ExpandProperty Name) -join ', '
|
||||
Updates = $failedTitles
|
||||
}
|
||||
}
|
||||
} | Sort-Object Computername
|
||||
|
||||
New-HTMLSection -HeaderText 'Computers in ERROR' {
|
||||
New-HTMLTable -DataTable $computersInError -DataTableID 'NewIDtoSearchInChartERR' -HideFooter
|
||||
}
|
||||
|
||||
# ****************************** FOOTER ******************************
|
||||
New-HTMLFooter {
|
||||
New-HTMLText -Text ("Report generated (GMT): {0:u}" -f (Get-Date).ToUniversalTime()) -Color Blue -Alignment center
|
||||
}
|
||||
|
||||
} -FilePath $outFile -Online
|
||||
|
||||
Write-Host "WSUS report has been generated successfully at $outFile"
|
||||
123
auto-approval/approval.ps1
Normal file
123
auto-approval/approval.ps1
Normal file
@@ -0,0 +1,123 @@
|
||||
$SyncApprovals = @(
|
||||
# Sync TESTERS -> PILOT
|
||||
@{
|
||||
"Source" = "Pilot" # Source group: TESTERS
|
||||
"Target" = "Global1" # Target group: PILOT
|
||||
"MinDays" = 5 # Minimum number of days before synchronization
|
||||
},
|
||||
|
||||
# Sync PILOT -> PROD
|
||||
@{
|
||||
"Source" = "Global1" # Source group: PILOT
|
||||
"Target" = "Global2" # Target group: PROD
|
||||
"MinDays" = 5 # Minimum number of days before synchronization
|
||||
}
|
||||
)
|
||||
|
||||
$logFolder = "C:\logs"
|
||||
$maxLogs = 60
|
||||
|
||||
# Ensure the log folder exists (create if missing)
|
||||
If (-not (Test-Path $logFolder)) {
|
||||
New-Item -Path $logFolder -ItemType Directory
|
||||
Write-Host "The directory $logFolder has been created."
|
||||
} else {
|
||||
Write-Host "The directory $logFolder already exists."
|
||||
}
|
||||
|
||||
# Limit the number of log files (keep the most recent $maxLogs)
|
||||
$logFiles = Get-ChildItem -Path $logFolder -Filter "WSUS-ManageApprovals*.log" | Sort-Object LastWriteTime -Descending
|
||||
if ($logFiles.Count -gt $maxLogs) {
|
||||
$logFiles | Select-Object -Skip $maxLogs | Remove-Item -Force
|
||||
}
|
||||
|
||||
# Build current log file path
|
||||
$logFile = Join-Path $logFolder ("WSUS-ManageApprovals" + (Get-Date -format "yyyyMMdd-HHmmss") + ".log")
|
||||
|
||||
# Connect to the WSUS server
|
||||
Try {
|
||||
[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null
|
||||
$wsusServer = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
|
||||
$subscription = $wsusServer.GetSubscription()
|
||||
$wsusServerConfig = $wsusServer.GetConfiguration()
|
||||
$targetGroups = $wsusServer.GetComputerTargetGroups()
|
||||
$updates = $wsusServer.GetUpdates()
|
||||
} Catch {
|
||||
Write-Output "Error connecting to the WSUS server: $_" | Add-Content -Path $logFile
|
||||
Exit
|
||||
}
|
||||
|
||||
# Start a WSUS synchronization (non-blocking)
|
||||
Try {
|
||||
$subscription.StartSynchronization()
|
||||
Write-Output "Synchronization successfully started." | Add-Content -Path $logFile
|
||||
} Catch {
|
||||
Write-Output "Error during WSUS synchronization: $_" | Add-Content -Path $logFile
|
||||
Exit
|
||||
}
|
||||
|
||||
# Filter working updates (exclude declined) and accept EULAs if needed
|
||||
$workingUpdates = $updates | Where-Object { -not $_.IsDeclined }
|
||||
Foreach ($update in $workingUpdates) {
|
||||
# Accept license agreement if required
|
||||
If ($update.RequiresLicenseAgreementAcceptance) {
|
||||
$update.AcceptLicenseAgreement()
|
||||
Write-Output "License accepted for: $($update.Title)" | Add-Content -Path $logFile
|
||||
}
|
||||
}
|
||||
|
||||
# Approve updates to target groups after MinDays since source approval GoLiveTime
|
||||
Write-Output "********** Approve KB **********" | Add-Content -Path $logFile
|
||||
Foreach ($update in $workingUpdates) {
|
||||
$approvals = $update.GetUpdateApprovals()
|
||||
Foreach ($syncApproval in $SyncApprovals) {
|
||||
$sourceGroup = $targetGroups | Where-Object { $_.Name -eq $syncApproval.Source }
|
||||
|
||||
# Ensure source group exists and action is Install
|
||||
If ($sourceGroup) {
|
||||
$sourceApproval = $approvals | Where-Object { $_.ComputerTargetGroupId -eq $sourceGroup.ID }
|
||||
If ($sourceApproval -and $($sourceApproval.Action) -eq "Install") {
|
||||
# Check if enough days have passed since GoLiveTime
|
||||
$LastChangeKB = (New-TimeSpan -Start $sourceApproval.GoLiveTime -End (Get-Date)).Days
|
||||
If ($LastChangeKB -ge $syncApproval.MinDays) {
|
||||
$targetGroup = $targetGroups | Where-Object { $_.Name -eq $syncApproval.Target }
|
||||
$targetApproval = $approvals | Where-Object { $_.ComputerTargetGroupId -eq $targetGroup.ID }
|
||||
|
||||
# Approve the update if it's not already approved for installation
|
||||
If ($($targetApproval.Action) -ne "Install") {
|
||||
Write-Output "Approving: $($syncApproval.Target) => $($update.Title) - $LastChangeKB days" | Add-Content -Path $logFile
|
||||
$update.Approve("Install", $targetGroup) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Output "Source group $($syncApproval.Source) not found." | Add-Content -Path $logFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decline superseded updates if a superseding one is installed for the expected target groups
|
||||
Write-Output "********** Disable Superseded Updates **********" | Add-Content -Path $logFile
|
||||
$workingUpdates = $workingUpdates | Where-Object { $_.IsSuperseded }
|
||||
Foreach ($update in $workingUpdates) {
|
||||
Write-Output "$($update.Title)" | Add-Content -Path $logFile
|
||||
|
||||
# Find updates that supersede the current update
|
||||
Foreach ($Supersede in $update.GetRelatedUpdates([Microsoft.UpdateServices.Administration.UpdateRelationship]::UpdatesThatSupersedeThisUpdate)) {
|
||||
Write-Output "----- Replaced By: $($Supersede.Title)" | Add-Content -Path $logFile
|
||||
$approvals = $Supersede.GetUpdateApprovals()
|
||||
|
||||
# If a superseding update is approved for Install in Global2, decline the superseded one
|
||||
Foreach ($approval in $approvals) {
|
||||
Foreach ($targetGroup in $targetGroups) {
|
||||
If (($targetGroup.Id -eq $approval.ComputerTargetGroupId) -and ($($approval.Action) -eq "Install")) {
|
||||
Write-Output "---------- $($targetGroup.Name)" | Add-Content -Path $logFile
|
||||
if ($($targetGroup.Name) -eq "Global2") { # fixed: target group is Global2
|
||||
Write-Output "---------- Declining: $($update.Title)" | Add-Content -Path $logFile
|
||||
$update.Decline()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
auto-approval/readme.md
Normal file
31
auto-approval/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# WSUS – Automate Patch Assignment to Groups
|
||||
|
||||
PowerShell script to automatically **promote WSUS approvals** across groups after a delay, accept EULAs, and decline superseded updates when a newer update is approved in production.
|
||||
|
||||
## What it does
|
||||
- Sync approvals after X days:
|
||||
- `Pilot → Global1`
|
||||
- `Global1 → Global2`
|
||||
- Accept license agreements when required.
|
||||
- Decline superseded updates if a superseding update is approved for **Global2**.
|
||||
- Write rotating logs to `C:\Logs` (keeps the latest 60 files).
|
||||
|
||||
## Requirements
|
||||
- Run on the WSUS server with **Administrator** privileges.
|
||||
- WSUS installed (PowerShell `UpdateServices` module or the WSUS Admin DLL available).
|
||||
- Existing WSUS groups: `Pilot`, `Global1`, `Global2` (adjust names if different).
|
||||
|
||||
## Configuration
|
||||
Edit the top of the script:
|
||||
- `\$SyncApprovals` – define source/target groups and `MinDays`.
|
||||
- `\$logFolder`, `\$maxLogs` – logging folder and retention.
|
||||
- Superseded-decline rule currently targets `Global2`.
|
||||
|
||||
## Quick start
|
||||
```powershell
|
||||
# Run the script (no parameters)
|
||||
.\Wsus-ManageApprovals.ps1
|
||||
```
|
||||
|
||||
## Full documentation
|
||||
https://blog.wuibaille.fr/2024/10/automate-assign-patch-to-group/
|
||||
32
cleanup-server/CleanupWSUS.ps1
Normal file
32
cleanup-server/CleanupWSUS.ps1
Normal file
@@ -0,0 +1,32 @@
|
||||
#Requires -RunAsAdministrator
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ServerName = 'localhost',
|
||||
[int]$Port = 8530,
|
||||
[switch]$UseSsl
|
||||
)
|
||||
|
||||
# Load the WSUS admin assembly explicitly if the module isn't present
|
||||
$wsusDll = "$env:ProgramFiles\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"
|
||||
if (-not (Get-Module -ListAvailable UpdateServices)) {
|
||||
if (Test-Path $wsusDll) { Add-Type -Path $wsusDll } else { throw "WSUS Admin DLL not found: $wsusDll" }
|
||||
}
|
||||
|
||||
try {
|
||||
$wsusServer = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($ServerName, [bool]$UseSsl, $Port)
|
||||
$cleanupInterface = $wsusServer.GetCleanupManager()
|
||||
$cleanupScope = New-Object Microsoft.UpdateServices.Administration.CleanupScope
|
||||
$cleanupScope.DeclineSupersededUpdates = $true
|
||||
$cleanupScope.DeclineExpiredUpdates = $true
|
||||
$cleanupScope.CleanupObsoleteComputers = $true
|
||||
$cleanupScope.CleanupObsoleteUpdates = $true
|
||||
$cleanupScope.CompressUpdates = $true
|
||||
$cleanupScope.CleanupUnneededContentFiles = $true
|
||||
|
||||
$cleanupInterface.PerformCleanup($cleanupScope)
|
||||
Write-Host "WSUS cleanup completed."
|
||||
}
|
||||
catch {
|
||||
Write-Error "WSUS cleanup failed: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
28
cleanup-server/readme.md
Normal file
28
cleanup-server/readme.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# WSUS Cleanup (PowerShell)
|
||||
|
||||
Powershell scripts to run a safe, repeatable **WSUS** cleanup:
|
||||
- Decline superseded/expired updates
|
||||
- Remove obsolete updates/computers
|
||||
- Compress updates
|
||||
- Delete unneeded content files
|
||||
|
||||
Works with the supported WSUS cmdlets (`UpdateServices` module). A legacy .NET fallback is included.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
- Run on the WSUS server in an **elevated** PowerShell session.
|
||||
- WSUS PowerShell module: `UpdateServices` (installed with WSUS/RSAT).
|
||||
- Port **8530** (HTTP) or **8531** (HTTPS).
|
||||
- Expect long runtimes on large servers; schedule outside business hours.
|
||||
|
||||
## Quick start
|
||||
|
||||
```powershell
|
||||
# Standard full cleanup (HTTP 8530)
|
||||
.\Wsus-Cleanup.ps1 -Verbose
|
||||
|
||||
# HTTPS on 8531
|
||||
.\Wsus-Cleanup.ps1 -UseSsl -Port 8531 -Verbose
|
||||
```
|
||||
|
||||
10
generate-windows-update-log/readme.md
Normal file
10
generate-windows-update-log/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Generate a Windows Update Log
|
||||
|
||||
## Procedure
|
||||
|
||||
1) Generate the Windows Update log file
|
||||
Use the `Get-WindowsUpdateLog` cmdlet in PowerShell to merge the Windows Update ETL files into a single log file.
|
||||
|
||||
2) Locate the generated log file
|
||||
`Get-WindowsUpdateLog` creates `WindowsUpdate.log` on your Desktop by default.
|
||||
|
||||
72
install-msu-web-download/installmsu.ps1
Normal file
72
install-msu-web-download/installmsu.ps1
Normal file
@@ -0,0 +1,72 @@
|
||||
# MSU Prerequisite Check and Install Script
|
||||
|
||||
$LogPath = "C:\Windows\Temp\MSU_Install.log"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Add-Content -Path $LogPath -Value "$timestamp $Message"
|
||||
Write-Host $Message
|
||||
}
|
||||
|
||||
# Check 1: Reboot pending
|
||||
$RebootRegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
||||
if (Test-Path $RebootRegKey) {
|
||||
Write-Log "Pending reboot detected."
|
||||
exit 101
|
||||
} else {
|
||||
Write-Log "No pending reboot."
|
||||
}
|
||||
|
||||
# Check 3: Free space on C:
|
||||
$minFreeGB = 5
|
||||
$drive = Get-PSDrive -Name C
|
||||
if ($null -eq $drive -or ($drive.Free/1GB) -lt $minFreeGB) {
|
||||
Write-Log "Insufficient free space on C:. Required: ${minFreeGB}GB"
|
||||
exit 103
|
||||
} else {
|
||||
Write-Log ("Sufficient free space on C:. Free: {0:N2}GB" -f ($drive.Free/1GB))
|
||||
}
|
||||
|
||||
# MSU download info
|
||||
$MSUFile = "windows10.0-kb5060531-x64.msu"
|
||||
$MSUUrl = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/secu/2025/06/windows10.0-kb5060531-x64_83789c3b9350e10e207370622c4ef54dd685ee02.msu"
|
||||
$ScriptDir = "c:\windows\temp"
|
||||
$MSUPath = Join-Path $ScriptDir $MSUFile
|
||||
|
||||
# Always delete the MSU file before download
|
||||
if (Test-Path $MSUPath) {
|
||||
Write-Log "Deleting existing MSU file: $MSUPath"
|
||||
Remove-Item -Path $MSUPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Log "Downloading MSU from $MSUUrl using System.Net.WebClient"
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile($MSUUrl, $MSUPath)
|
||||
$sw.Stop()
|
||||
$downloadTime = "{0:N2}" -f $sw.Elapsed.TotalSeconds
|
||||
|
||||
if (-not (Test-Path $MSUPath)) {
|
||||
Write-Log "Failed to download MSU (file not found after download)"
|
||||
exit 104
|
||||
} else {
|
||||
Write-Log "Download completed: $MSUPath"
|
||||
Write-Log "Download time: $downloadTime seconds"
|
||||
}
|
||||
} catch {
|
||||
Write-Log "Failed to download MSU with WebClient: $_"
|
||||
exit 104
|
||||
}
|
||||
|
||||
Write-Log "Starting installation of $MSUPath"
|
||||
|
||||
# Install the MSU silently, no restart
|
||||
$process = Start-Process -FilePath "wusa.exe" -ArgumentList "`"$MSUPath`" /quiet /norestart" -Wait -PassThru
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
Write-Log "wusa.exe exit code: $exitCode"
|
||||
|
||||
# Return the exit code as the script's own exit code
|
||||
exit $exitCode
|
||||
250
install-msu-web-download/installmsuv2.ps1
Normal file
250
install-msu-web-download/installmsuv2.ps1
Normal file
@@ -0,0 +1,250 @@
|
||||
#requires -version 5.1
|
||||
<#
|
||||
MSU Prereq + Install + WS logging + STRICT selection by FULL BUILD (major.UBR)
|
||||
- Mapping is downloaded from https://nas.wuibaille.fr/WS/patchs/msu.json
|
||||
- Supports multiple packages per build (e.g., SSU then LCU)
|
||||
- JSON needs only: { "<build>": { "packages": [ { "url": "...", "type": "LCU", "order": 20 }, ... ] } }
|
||||
- No defaults: if build key missing -> exit 111
|
||||
- If JSON cannot be downloaded and no cache -> exit 112
|
||||
- If cached JSON invalid -> exit 113
|
||||
#>
|
||||
|
||||
# -------- Settings --------
|
||||
$ErrorActionPreference = 'Stop'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$ComputerName = $env:COMPUTERNAME
|
||||
$LogPath = "C:\Windows\Temp\MSU_Install.log"
|
||||
$ApiUrl = "https://nas.wuibaille.fr/WS/message.php"
|
||||
$SendLogsToWS = $true
|
||||
|
||||
# Remote JSON map + local cache
|
||||
$MsuMapUrl = "https://nas.wuibaille.fr/WS/patchs/msu.json"
|
||||
$MsuMapCache = "C:\Windows\Temp\msu.json"
|
||||
|
||||
# Optional: force a build key like '17763.7786'
|
||||
$OverrideBuildFull = $null
|
||||
|
||||
# -------- Helpers --------
|
||||
function Write-Log {
|
||||
param([Parameter(Mandatory)][string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$fullMessage = "[$ComputerName] $Message"
|
||||
Add-Content -Path $LogPath -Value "$timestamp $fullMessage" -Encoding UTF8
|
||||
Write-Host $fullMessage
|
||||
if ($SendLogsToWS) {
|
||||
try {
|
||||
$body = @{ message = $fullMessage } | ConvertTo-Json -Depth 2
|
||||
$null = Invoke-RestMethod -Uri $ApiUrl -Method POST -Body $body -ContentType "application/json" -TimeoutSec 10 -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Warning ("[$ComputerName] Webservice log failed: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BuildFull {
|
||||
# Returns PSCustomObject with BuildMajor, UBR, BuildFull="major.UBR"
|
||||
$cvPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
||||
$cv = Get-ItemProperty -LiteralPath $cvPath
|
||||
|
||||
$buildStr = [string]$cv.CurrentBuild
|
||||
if ([string]::IsNullOrWhiteSpace($buildStr)) { $buildStr = [string]$cv.CurrentBuildNumber }
|
||||
$buildMajor = [int]$buildStr
|
||||
|
||||
$ubr = 0; try { $ubr = [int]$cv.UBR } catch {}
|
||||
|
||||
[PSCustomObject]@{
|
||||
BuildMajor = $buildMajor
|
||||
UBR = $ubr
|
||||
BuildFull = ('{0}.{1}' -f $buildMajor, $ubr)
|
||||
}
|
||||
}
|
||||
|
||||
function Get-MSUMapFromJson {
|
||||
# Downloads JSON (and caches it). On failure, tries cache; else exits.
|
||||
Write-Log ("Fetching MSU map: {0}" -f $MsuMapUrl)
|
||||
try {
|
||||
$json = Invoke-RestMethod -Uri $MsuMapUrl -Method GET -TimeoutSec 15 -ErrorAction Stop
|
||||
try { ($json | ConvertTo-Json -Depth 10) | Out-File -FilePath $MsuMapCache -Encoding UTF8 -Force } catch {}
|
||||
return $json
|
||||
} catch {
|
||||
Write-Log ("Online map fetch failed: {0}" -f $_.Exception.Message)
|
||||
if (Test-Path -LiteralPath $MsuMapCache) {
|
||||
Write-Log ("Using cached map: {0}" -f $MsuMapCache)
|
||||
try {
|
||||
$raw = Get-Content -LiteralPath $MsuMapCache -Raw -ErrorAction Stop
|
||||
$json = $raw | ConvertFrom-Json -ErrorAction Stop
|
||||
return $json
|
||||
} catch {
|
||||
Write-Log ("Cached map is invalid: {0}. Exiting 113." -f $_.Exception.Message)
|
||||
exit 113
|
||||
}
|
||||
} else {
|
||||
Write-Log "No cached map available. Exiting 112."
|
||||
exit 112
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-PackagesArray {
|
||||
param(
|
||||
[Parameter(Mandatory)]$Entry,
|
||||
[Parameter(Mandatory)][string]$KeyForErrors
|
||||
)
|
||||
<#
|
||||
Accepts:
|
||||
- { "packages": [ { "url": "...", "type": "LCU", "order": 20 }, ... ] }
|
||||
- { "url": "..." } (single package)
|
||||
- "https://..." (single package as string)
|
||||
Returns: array of package objects with at least .url
|
||||
#>
|
||||
if ($Entry -is [string]) {
|
||||
if ($Entry -notmatch '^https?://') { throw "Invalid entry for build '$KeyForErrors': expected URL string." }
|
||||
return @(@{ url = $Entry; type = $null; order = $null })
|
||||
}
|
||||
|
||||
$props = @($Entry.PSObject.Properties.Name)
|
||||
if ($props -contains 'packages') {
|
||||
$arr = @($Entry.packages)
|
||||
if (-not $arr -or $arr.Count -eq 0) { throw "Entry for build '$KeyForErrors' has empty 'packages'." }
|
||||
foreach ($p in $arr) {
|
||||
if (-not $p.url) { throw "One package in build '$KeyForErrors' is missing 'url'." }
|
||||
}
|
||||
return $arr
|
||||
}
|
||||
|
||||
if ($props -contains 'url') {
|
||||
return @(@{ url = [string]$Entry.url; type = $Entry.type; order = $Entry.order })
|
||||
}
|
||||
|
||||
throw "Invalid entry format for build '$KeyForErrors'. Expected 'packages' array or 'url' string/property."
|
||||
}
|
||||
|
||||
function Sort-Packages {
|
||||
param([Parameter(Mandatory)][array]$Packages)
|
||||
# Priority: explicit 'order' asc, else by 'type' (SSU=10, LCU=20, NET=30, other=50)
|
||||
$prio = @{ 'SSU' = 10; 'LCU' = 20; 'NET' = 30 }
|
||||
return $Packages | Sort-Object `
|
||||
@{ Expression = { if ($_.order -ne $null -and "$($_.order)" -match '^\d+$') { [int]$_.order } else { 999 } } },
|
||||
@{ Expression = { $t = ("" + $_.type).ToUpper(); if ($prio.ContainsKey($t)) { $prio[$t] } else { 50 } } }
|
||||
}
|
||||
|
||||
function Resolve-PackagesForBuild {
|
||||
param(
|
||||
[Parameter(Mandatory)]$BuildInfo,
|
||||
[Parameter(Mandatory)]$JsonMap,
|
||||
[string]$OverrideBuildFull
|
||||
)
|
||||
$key = if ([string]::IsNullOrWhiteSpace($OverrideBuildFull)) { $BuildInfo.BuildFull } else { $OverrideBuildFull }
|
||||
|
||||
# Convert root object to hashtable keyed by build strings
|
||||
$map = @{}
|
||||
foreach ($p in $JsonMap.PSObject.Properties) { $map[$p.Name] = $p.Value }
|
||||
|
||||
if (-not $map.ContainsKey($key)) { throw ("No MSU mapping defined for full build '{0}'." -f $key) }
|
||||
|
||||
$entry = $map[$key]
|
||||
$packages = Ensure-PackagesArray -Entry $entry -KeyForErrors $key
|
||||
$packages = Sort-Packages -Packages $packages
|
||||
return @{ Key=$key; Packages=$packages }
|
||||
}
|
||||
|
||||
function Test-IsAdmin {
|
||||
$wi = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$wp = [Security.Principal.WindowsPrincipal]::new($wi)
|
||||
return $wp.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
function Filename-FromUrl {
|
||||
param([Parameter(Mandatory)][string]$Url)
|
||||
try {
|
||||
$u = [Uri]$Url
|
||||
$name = [IO.Path]::GetFileName($u.AbsolutePath)
|
||||
if ([string]::IsNullOrWhiteSpace($name)) { $name = "update.msu" }
|
||||
} catch { $name = "update.msu" }
|
||||
if ($name -notmatch '\.msu$') { $name = "$name.msu" }
|
||||
return $name
|
||||
}
|
||||
|
||||
# -------- Script start --------
|
||||
if (-not (Test-IsAdmin)) { Write-Log "Warning: Script is not running elevated." }
|
||||
|
||||
# Build detection
|
||||
$b = Get-BuildFull
|
||||
Write-Log ("Detected OS: Build={0}" -f $b.BuildFull)
|
||||
|
||||
# Load mapping + resolve package list
|
||||
$msuJson = Get-MSUMapFromJson
|
||||
try {
|
||||
$sel = Resolve-PackagesForBuild -BuildInfo $b -JsonMap $msuJson -OverrideBuildFull $OverrideBuildFull
|
||||
Write-Log ("Selected {0} package(s) for Build={1}" -f $sel.Packages.Count, $sel.Key)
|
||||
} catch {
|
||||
Write-Log ("MSU selection error: {0}. Exiting 111." -f $_.Exception.Message)
|
||||
exit 111
|
||||
}
|
||||
|
||||
# Checks
|
||||
try {
|
||||
$RebootRegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
||||
if (Test-Path $RebootRegKey) { Write-Log "Pending reboot detected. Exiting 101."; exit 101 }
|
||||
} catch { Write-Log ("Error while checking free space: {0}" -f $_.Exception.Message); exit 199 }
|
||||
|
||||
try {
|
||||
$minFreeGB = 5
|
||||
$drive = Get-PSDrive -Name C -ErrorAction Stop
|
||||
$freeGB = [math]::Round(($drive.Free/1GB),2)
|
||||
if ($freeGB -lt $minFreeGB) { Write-Log ("Insufficient free space: required {0}GB, available {1}GB. Exiting 103." -f $minFreeGB, $freeGB); exit 103 }
|
||||
} catch { Write-Log ("Error while checking free space: {0}" -f $_.Exception.Message); exit 198 }
|
||||
|
||||
# Download + install each package in order
|
||||
$ScriptDir = "C:\Windows\Temp"
|
||||
if (-not (Test-Path -LiteralPath $ScriptDir)) { New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null }
|
||||
|
||||
$anyNeedReboot = $false
|
||||
$anyInstalled = $false
|
||||
|
||||
foreach ($pkg in $sel.Packages) {
|
||||
$url = [string]$pkg.url
|
||||
if ([string]::IsNullOrWhiteSpace($url)) { Write-Log "Package without URL encountered. Aborting (bad JSON)."; exit 113 }
|
||||
$type = ("" + $pkg.type)
|
||||
$fileName = Filename-FromUrl -Url $url
|
||||
$destPath = Join-Path $ScriptDir $fileName
|
||||
|
||||
Write-Log ("Downloading {0}{1} from {2}" -f (if ($type) { "[$type] " } else { "" }), $fileName, $url)
|
||||
try {
|
||||
$wc = New-Object System.Net.WebClient
|
||||
try {
|
||||
$wc.Headers['User-Agent'] = ("MSU-Installer/1.0 ({0})" -f $ComputerName)
|
||||
if (Test-Path -LiteralPath $destPath) { Remove-Item -LiteralPath $destPath -Force -ErrorAction Stop }
|
||||
$wc.DownloadFile($url, $destPath)
|
||||
} finally { if ($wc) { $wc.Dispose() } }
|
||||
if (-not (Test-Path -LiteralPath $destPath)) { Write-Log "Download failed: file not found after download. Exiting 104."; exit 104 }
|
||||
} catch {
|
||||
Write-Log ("Failed to download package {0}: {1}. Exiting 104." -f $fileName, $_.Exception.Message)
|
||||
exit 104
|
||||
}
|
||||
|
||||
Write-Log ("Installing {0}{1}" -f (if ($type) { "[$type] " } else { "" }), $fileName)
|
||||
try {
|
||||
$proc = Start-Process -FilePath "wusa.exe" -ArgumentList "`"$destPath`" /quiet /norestart" -Wait -PassThru
|
||||
$code = $proc.ExitCode
|
||||
Write-Log ("wusa.exe exit code: {0} for {1}" -f $code, $fileName)
|
||||
|
||||
if ($code -eq 3010) { $anyNeedReboot = $true; $anyInstalled = $true }
|
||||
elseif ($code -eq 0) { $anyInstalled = $true }
|
||||
elseif ($code -eq 2359302) { } # already installed, continue
|
||||
else {
|
||||
Write-Log ("Installation failed for {0} with code {1}. Aborting." -f $fileName, $code)
|
||||
exit $code
|
||||
}
|
||||
} catch {
|
||||
Write-Log ("Installation failed for {0}: {1}" -f $fileName, $_.Exception.Message)
|
||||
exit 195
|
||||
}
|
||||
}
|
||||
|
||||
# Overall exit code policy
|
||||
if ($anyNeedReboot) { exit 3010 }
|
||||
elseif ($anyInstalled) { exit 0 }
|
||||
else { exit 2359302 }
|
||||
329
install-msu-web-download/installmsuv3.ps1
Normal file
329
install-msu-web-download/installmsuv3.ps1
Normal file
@@ -0,0 +1,329 @@
|
||||
#requires -version 5.1
|
||||
<#
|
||||
Auto-install latest OS cumulative updates using PSWindowsUpdate (no JSON)
|
||||
- Forces Windows Update public service (-WindowsUpdate)
|
||||
- Installs SSU (if present) first, then latest non-Preview LCU
|
||||
- ASCII-only logs; messages sanitized before sending to web service
|
||||
#>
|
||||
|
||||
# -------- Settings --------
|
||||
$ErrorActionPreference = 'Stop'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$ComputerName = $env:COMPUTERNAME
|
||||
$LogPath = "C:\Windows\Temp\MSU_Install.log"
|
||||
$ApiUrl = "https://nas.wuibaille.fr/WS/message.php"
|
||||
$SendLogsToWS = $true
|
||||
|
||||
# Optional WS retry tunables (defaults: 6 tries, 1s delay)
|
||||
$script:WsMaxRetries = 6
|
||||
$script:WsRetryDelaySec = 1
|
||||
|
||||
# Module install policy
|
||||
$AllowInstallPSWindowsUpdate = $true
|
||||
|
||||
# -------- Utilities --------
|
||||
function Sanitize-Ascii {
|
||||
param([string]$Text)
|
||||
if ($null -eq $Text) { return "" }
|
||||
|
||||
$t = $Text
|
||||
$t = $t -replace [char]0x2013, "-" # en dash
|
||||
$t = $t -replace [char]0x2014, "-" # em dash
|
||||
$t = $t -replace [char]0x2026, "..." # ellipsis
|
||||
$t = $t -replace [char]0x2018, "'" # left single quote
|
||||
$t = $t -replace [char]0x2019, "'" # right single quote
|
||||
$t = $t -replace [char]0x201C, '"' # left double quote
|
||||
$t = $t -replace [char]0x201D, '"' # right double quote
|
||||
$t = $t -replace [char]0x00A0, " " # nbsp
|
||||
$t = $t -replace [char]0x2022, "-" # bullet
|
||||
$t = [System.Text.RegularExpressions.Regex]::Replace($t, '[^\x09\x0A\x0D\x20-\x7E]', '')
|
||||
return $t
|
||||
}
|
||||
|
||||
# -------- Logging --------
|
||||
function Write-Log {
|
||||
param([Parameter(Mandatory)][string]$Message)
|
||||
|
||||
$msg = Sanitize-Ascii $Message
|
||||
$full = "[$env:COMPUTERNAME] $msg"
|
||||
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
Add-Content -Path $LogPath -Value "$ts $full" -Encoding UTF8
|
||||
Write-Host $full
|
||||
|
||||
if (-not $SendLogsToWS) { return }
|
||||
|
||||
$payload = @{ message = $full } | ConvertTo-Json -Compress
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($payload)
|
||||
|
||||
$max = if ($script:WsMaxRetries) { [int]$script:WsMaxRetries } else { 6 }
|
||||
$delay = if ($script:WsRetryDelaySec) { [int]$script:WsRetryDelaySec } else { 1 }
|
||||
|
||||
$ok = $false
|
||||
$lastErr = $null
|
||||
|
||||
for ($attempt = 1; $attempt -le $max -and -not $ok; $attempt++) {
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri $ApiUrl -Method POST -Body $bytes `
|
||||
-ContentType 'application/json; charset=utf-8' -TimeoutSec 10 -ErrorAction Stop
|
||||
$ok = $true
|
||||
} catch {
|
||||
$lastErr = $_.Exception.Message
|
||||
if ($attempt -lt $max) { Start-Sleep -Seconds $delay }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $ok) {
|
||||
Write-Warning "[$env:COMPUTERNAME] Webservice log failed after $max attempts: $lastErr"
|
||||
}
|
||||
}
|
||||
|
||||
# -------- Prereqs --------
|
||||
function Test-IsAdmin {
|
||||
$wi = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$wp = [Security.Principal.WindowsPrincipal]::new($wi)
|
||||
return $wp.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
function Get-BuildFull {
|
||||
$cv = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
||||
$buildStr = [string]$cv.CurrentBuild
|
||||
if ([string]::IsNullOrWhiteSpace($buildStr)) { $buildStr = [string]$cv.CurrentBuildNumber }
|
||||
$major = [int]$buildStr
|
||||
$ubr = 0; try { $ubr = [int]$cv.UBR } catch {}
|
||||
[PSCustomObject]@{ BuildMajor=$major; UBR=$ubr; BuildFull=("{0}.{1}" -f $major,$ubr) }
|
||||
}
|
||||
|
||||
# -------- PSWindowsUpdate bootstrap (silent) --------
|
||||
function Ensure-PSWindowsUpdate {
|
||||
try {
|
||||
Import-Module PSWindowsUpdate -ErrorAction Stop
|
||||
$m = Get-Module PSWindowsUpdate
|
||||
Write-Log ("PSWindowsUpdate module loaded (v{0})." -f $m.Version)
|
||||
return $true
|
||||
} catch {}
|
||||
|
||||
if (-not $AllowInstallPSWindowsUpdate) {
|
||||
Write-Log "PSWindowsUpdate module not found and auto-install is disabled. Exiting 120."
|
||||
exit 120
|
||||
}
|
||||
|
||||
$scope = if (Test-IsAdmin) { 'AllUsers' } else { 'CurrentUser' }
|
||||
|
||||
try {
|
||||
$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue |
|
||||
Sort-Object Version -Descending | Select-Object -First 1
|
||||
if (-not $nuget -or ([Version]$nuget.Version -lt [Version]"2.8.5.201")) {
|
||||
Write-Log "Installing NuGet provider silently..."
|
||||
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope $scope -ErrorAction Stop
|
||||
}
|
||||
|
||||
$repo = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
||||
if (-not $repo) {
|
||||
Write-Log "Registering PSGallery repository..."
|
||||
try { Register-PSRepository -Default -ErrorAction Stop } catch {}
|
||||
$repo = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($repo -and $repo.InstallationPolicy -ne 'Trusted') {
|
||||
Write-Log "Setting PSGallery as Trusted..."
|
||||
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
try {
|
||||
$psget = Get-Module -ListAvailable PowerShellGet | Sort-Object Version -Descending | Select-Object -First 1
|
||||
if (-not $psget -or ([Version]$psget.Version -lt [Version]"2.2.5")) {
|
||||
Write-Log "Updating PowerShellGet silently..."
|
||||
Install-Module PowerShellGet -Scope $scope -Force -AllowClobber -ErrorAction Stop
|
||||
}
|
||||
} catch {
|
||||
Write-Log ("PowerShellGet update failed (continuing): {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Write-Log "Installing PSWindowsUpdate silently..."
|
||||
Install-Module PSWindowsUpdate -Scope $scope -Force -AllowClobber -ErrorAction Stop
|
||||
|
||||
Import-Module PSWindowsUpdate -ErrorAction Stop
|
||||
$m = Get-Module PSWindowsUpdate
|
||||
Write-Log ("PSWindowsUpdate installed and loaded (v{0})." -f $m.Version)
|
||||
return $true
|
||||
} catch {
|
||||
Write-Log ("Failed to install/load PSWindowsUpdate: {0}. Exiting 120." -f $_.Exception.Message)
|
||||
exit 120
|
||||
}
|
||||
}
|
||||
|
||||
# -------- Discovery & Install using PSWindowsUpdate --------
|
||||
function Get-AvailableOSUpdates {
|
||||
# Returns: @{ SSU = <update or $null>; LCU = <update or $null>; Raw = <array> }
|
||||
try {
|
||||
$raw = Get-WindowsUpdate -WindowsUpdate -UpdateType Software -IgnoreReboot -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Log ("Windows Update query failed: {0}" -f $_.Exception.Message)
|
||||
Write-Log "This system may be WSUS-managed or blocked by policy/network. Exiting 121."
|
||||
exit 121
|
||||
}
|
||||
|
||||
if (-not $raw) {
|
||||
return [PSCustomObject]@{ SSU=$null; LCU=$null; Raw=@() }
|
||||
}
|
||||
|
||||
$filtered = $raw | Where-Object {
|
||||
$_.Title -and
|
||||
($_.Title -notmatch 'Preview') -and
|
||||
($_.Title -notmatch 'for\s*\.NET') -and
|
||||
($_.Categories -notcontains 'Drivers') -and
|
||||
($_.Categories -notcontains 'Definition Updates')
|
||||
}
|
||||
|
||||
$norm = {
|
||||
param($s)
|
||||
if (-not $s) { return '' }
|
||||
$t = Sanitize-Ascii $s
|
||||
return $t.ToLowerInvariant()
|
||||
}
|
||||
|
||||
$ssu = $filtered | Where-Object {
|
||||
$t = & $norm $_.Title
|
||||
($t -match 'servicing stack update' -or $t -match 'pile de maintenance')
|
||||
} | Sort-Object -Property LastDeploymentChangeTime -Descending | Select-Object -First 1
|
||||
|
||||
$lcu = $filtered | Where-Object {
|
||||
$t = & $norm $_.Title
|
||||
($t -match 'cumulative update' -or $t -match 'mise a jour cumulative')
|
||||
} | Sort-Object -Property LastDeploymentChangeTime -Descending | Select-Object -First 1
|
||||
|
||||
[PSCustomObject]@{ SSU=$ssu; LCU=$lcu; Raw=$filtered }
|
||||
}
|
||||
|
||||
function Install-OneWU {
|
||||
param([Parameter(Mandatory)]$UpdateObject)
|
||||
|
||||
$updateId = $UpdateObject.UpdateID
|
||||
$title = Sanitize-Ascii $UpdateObject.Title
|
||||
$kb = $UpdateObject.KB
|
||||
|
||||
$kbPrefix = if ($kb) { "$kb " } else { "" }
|
||||
Write-Log ("Installing: {0}{1}" -f $kbPrefix, $title)
|
||||
|
||||
try {
|
||||
if ($updateId) {
|
||||
$res = Install-WindowsUpdate -WindowsUpdate -UpdateID $updateId -AcceptAll -IgnoreReboot -Confirm:$false -ErrorAction Stop
|
||||
} elseif ($kb) {
|
||||
$res = Install-WindowsUpdate -WindowsUpdate -KBArticleID $kb -AcceptAll -IgnoreReboot -Confirm:$false -ErrorAction Stop
|
||||
} else {
|
||||
$res = Install-WindowsUpdate -WindowsUpdate -AcceptAll -IgnoreReboot -Confirm:$false -ErrorAction Stop |
|
||||
Where-Object { (Sanitize-Ascii $_.Title) -eq $title }
|
||||
}
|
||||
} catch {
|
||||
Write-Log ("Install failed for {0}{1}: {2}" -f $kbPrefix, $title, $_.Exception.Message)
|
||||
return [PSCustomObject]@{
|
||||
Title = $title; KB = $kb; Result = 'Failed'; HResult = $null; RebootRequired = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Build a clean result array without inline 'if'
|
||||
$out = @()
|
||||
foreach ($o in @($res)) {
|
||||
$rTitle = Sanitize-Ascii $o.Title
|
||||
|
||||
$rKB = $null
|
||||
if ($o.PSObject.Properties['KB'] -and $o.KB) { $rKB = $o.KB }
|
||||
elseif ($o.PSObject.Properties['KBArticleID'] -and $o.KBArticleID) { $rKB = $o.KBArticleID }
|
||||
|
||||
$rResult = $null
|
||||
if ($o.PSObject.Properties['Result'] -and $o.Result) { $rResult = $o.Result } else { $rResult = $o.ResultCode }
|
||||
|
||||
$rHResult = $null
|
||||
if ($o.PSObject.Properties['HResult']) { $rHResult = $o.HResult }
|
||||
|
||||
$rReboot = $false
|
||||
if ($o.PSObject.Properties['RebootRequired']) { $rReboot = [bool]$o.RebootRequired }
|
||||
|
||||
$out += [PSCustomObject]@{
|
||||
Title = $rTitle
|
||||
KB = $rKB
|
||||
Result = $rResult
|
||||
HResult = $rHResult
|
||||
RebootRequired = $rReboot
|
||||
}
|
||||
}
|
||||
return ,$out # ensure array even if single item
|
||||
}
|
||||
|
||||
# =================== Main ===================
|
||||
if (-not (Test-IsAdmin)) { Write-Log "Warning: script is not running elevated." }
|
||||
|
||||
try {
|
||||
$RebootRegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
||||
if (Test-Path $RebootRegKey) { Write-Log "Pending reboot detected. Exiting 101."; exit 101 }
|
||||
} catch { Write-Log ("Error while checking pending reboot: {0}" -f $_.Exception.Message); exit 199 }
|
||||
|
||||
try {
|
||||
$minFreeGB = 5
|
||||
$drive = Get-PSDrive -Name C -ErrorAction Stop
|
||||
$freeGB = [math]::Round(($drive.Free/1GB),2)
|
||||
if ($freeGB -lt $minFreeGB) { Write-Log ("Insufficient free space: required {0}GB, available {1}GB. Exiting 103." -f $minFreeGB, $freeGB); exit 103 }
|
||||
} catch { Write-Log ("Error while checking free space: {0}" -f $_.Exception.Message); exit 198 }
|
||||
|
||||
$build = Get-BuildFull
|
||||
Write-Log ("Detected OS: Build={0}" -f $build.BuildFull)
|
||||
|
||||
Ensure-PSWindowsUpdate | Out-Null
|
||||
|
||||
$updates = Get-AvailableOSUpdates
|
||||
|
||||
# Plan (pre-calc strings to avoid inline if in -f)
|
||||
if ($updates.SSU) {
|
||||
$kbPrefix = if ($updates.SSU.KB) { "$($updates.SSU.KB) " } else { "" }
|
||||
$title = Sanitize-Ascii $updates.SSU.Title
|
||||
Write-Log ("Plan: SSU -> {0}{1}" -f $kbPrefix, $title)
|
||||
}
|
||||
if ($updates.LCU) {
|
||||
$kbPrefix = if ($updates.LCU.KB) { "$($updates.LCU.KB) " } else { "" }
|
||||
$title = Sanitize-Ascii $updates.LCU.Title
|
||||
Write-Log ("Plan: LCU -> {0}{1}" -f $kbPrefix, $title)
|
||||
}
|
||||
|
||||
if (-not $updates.SSU -and -not $updates.LCU) {
|
||||
Write-Log "No OS SSU/LCU updates available (system appears up-to-date)."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$needReboot = $false
|
||||
$installed = $false
|
||||
$failedAny = $false
|
||||
|
||||
if ($updates.SSU) {
|
||||
$r = Install-OneWU -UpdateObject $updates.SSU
|
||||
foreach ($i in @($r)) {
|
||||
$kbPrefix = if ($i.KB) { "$($i.KB) " } else { "" }
|
||||
$hrSuf = if ($i.HResult) { " (HResult=$($i.HResult))" } else { "" }
|
||||
$rbSuf = if ($i.RebootRequired) { " [RebootRequired]" } else { "" }
|
||||
Write-Log ("Result: {0}{1} => {2}{3}{4}" -f $kbPrefix, $i.Title, $i.Result, $hrSuf, $rbSuf)
|
||||
|
||||
if ($i.Result -match 'Succeeded') { $installed = $true }
|
||||
elseif ($i.Result -match 'Failed|Aborted|Error') { $failedAny = $true }
|
||||
if ($i.RebootRequired) { $needReboot = $true }
|
||||
}
|
||||
}
|
||||
|
||||
if ($updates.LCU) {
|
||||
$r = Install-OneWU -UpdateObject $updates.LCU
|
||||
foreach ($i in @($r)) {
|
||||
$kbPrefix = if ($i.KB) { "$($i.KB) " } else { "" }
|
||||
$hrSuf = if ($i.HResult) { " (HResult=$($i.HResult))" } else { "" }
|
||||
$rbSuf = if ($i.RebootRequired) { " [RebootRequired]" } else { "" }
|
||||
Write-Log ("Result: {0}{1} => {2}{3}{4}" -f $kbPrefix, $i.Title, $i.Result, $hrSuf, $rbSuf)
|
||||
|
||||
if ($i.Result -match 'Succeeded') { $installed = $true }
|
||||
elseif ($i.Result -match 'Failed|Aborted|Error') { $failedAny = $true }
|
||||
if ($i.RebootRequired) { $needReboot = $true }
|
||||
}
|
||||
}
|
||||
|
||||
if ($failedAny) { Write-Log "One or more updates failed."; exit 195 }
|
||||
if ($needReboot) { exit 3010 }
|
||||
if ($installed) { exit 0 }
|
||||
exit 0
|
||||
41
install-msu-web-download/readme.md
Normal file
41
install-msu-web-download/readme.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# MSU Direct Download & Silent Install (PowerShell)
|
||||
|
||||
Download a specific **MSU** from Microsoft Update Catalog, run **pre-checks**, install with `wusa.exe /quiet /norestart`, and return the installer’s exit code. Logs to `C:\Windows\Temp\MSU_Install.log`.
|
||||
|
||||
## What it checks
|
||||
- **Pending reboot** → exits **101**.
|
||||
- **Free space on C:** ≥ **5 GB** (default) → exits **103**.
|
||||
- **Download** success from the Catalog URL → exits **104** on failure.
|
||||
- Otherwise runs `wusa` and returns its native **exit code** (`0` OK, `3010` reboot required).
|
||||
|
||||
## Quick start
|
||||
1. Edit the variables at the top of the script:
|
||||
- `$MSUUrl` (direct Catalog URL)
|
||||
- `$MSUFile` (e.g. `windows10.0-kb5060531-x64.msu`)
|
||||
- Optional: `$minFreeGB`
|
||||
2. Run as Administrator:
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Install-MSU.ps1
|
||||
```
|
||||
|
||||
## Exit codes
|
||||
- **0** = Installed successfully
|
||||
- **3010** = Installed, reboot required (from `wusa`)
|
||||
- **101** = Pending reboot detected
|
||||
- **103** = Not enough free space on C:
|
||||
- **104** = Download failed
|
||||
- **others** = Native `wusa.exe` codes
|
||||
|
||||
## Requirements
|
||||
- Windows 10/11 or Server, **elevated** PowerShell.
|
||||
- Outbound access to the Microsoft Update Catalog URL.
|
||||
- `wusa.exe` available (built-in).
|
||||
|
||||
## Notes
|
||||
- The script deletes any existing MSU at the target path before downloading.
|
||||
- If proxy/TLS blocks `WebClient`, switch to `Invoke-WebRequest` and enforce TLS 1.2:
|
||||
```powershell
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -Uri $MSUUrl -OutFile $MSUPath -UseBasicParsing
|
||||
```
|
||||
|
||||
73
report-failed-computers/Display.ps1
Normal file
73
report-failed-computers/Display.ps1
Normal file
@@ -0,0 +1,73 @@
|
||||
# Initializations
|
||||
[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | out-null
|
||||
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
|
||||
$computerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
|
||||
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
|
||||
$summariesComputerFailed = $wsus.GetSummariesPerComputerTarget($updateScope,$computerScope) | Where-Object FailedCount -NE 0 | Sort-Object FailedCount, UnknownCount, NotInstalledCount -Descending
|
||||
$computers = Get-WsusComputer
|
||||
$computersErrorEvents = $wsus.GetUpdateEventHistory([System.DateTime]::Today.AddDays(-7), [System.DateTime]::Today) | Where-Object ComputerId -ne [Guid]::Empty | Where-Object IsError -eq $true
|
||||
|
||||
function Get-TimeStamp {
|
||||
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
|
||||
}
|
||||
|
||||
$csvFile = "$PSScriptRoot\WSUSFailedComputer_" + "{0:MMddyy}" -f (Get-Date) + ".csv"
|
||||
Get-WsusComputer -ComputerUpdateStatus Failed | Export-Csv -Path $csvFile -NoTypeInformation
|
||||
$ComputersErrorToday = Get-WsusComputer -ComputerUpdateStatus Failed
|
||||
|
||||
$csvList = (Get-ChildItem -Path $PSScriptRoot -Filter *.csv).FullName
|
||||
$AllCsv = $csvList | Import-Csv
|
||||
$AllCsv = $AllCsv | Sort-Object "FullDomainName"
|
||||
|
||||
$Ordinateur = @()
|
||||
$CountError = 1
|
||||
$OldComputer = "XXXX"
|
||||
Foreach ($csv In $AllCsv) {
|
||||
$FullDomainName = $csv.FullDomainName
|
||||
|
||||
$ExitError = $true
|
||||
Foreach ($ComputerErrorToday In $ComputersErrorToday) {
|
||||
$FullNameComputerErrorToday = $ComputerErrorToday.FullDomainName
|
||||
if ($FullNameComputerErrorToday -eq $FullDomainName) { $ExitError = $false }
|
||||
}
|
||||
|
||||
If (-not $ExitError) {
|
||||
If ($OldComputer -eq $FullDomainName) {
|
||||
$CountError += 1
|
||||
} Else {
|
||||
if ($OldComputer -ne "XXXX") {
|
||||
$CountError = $CountError.ToString("0000")
|
||||
$Ordinateur += "$CountError;$OldComputer"
|
||||
}
|
||||
$CountError = 1
|
||||
$OldComputer = $FullDomainName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$Ordinateur = $Ordinateur | Sort-Object -Descending
|
||||
$TotalAffiche = 10
|
||||
$NbAffiche = 0
|
||||
Foreach ($Computererror In $Ordinateur) {
|
||||
If ($NbAffiche -lt $TotalAffiche) {
|
||||
Write-Host $Computererror
|
||||
|
||||
$NbAffiche += 1
|
||||
|
||||
$NomOrdinateur = $Computererror.Substring(5)
|
||||
ForEach ($computerFailed In $summariesComputerFailed) {
|
||||
$computer = $computers | Where-Object Id -eq $computerFailed.ComputerTargetId
|
||||
|
||||
$Computername = $computer.FullDomainName
|
||||
$ComputerIP = $computer.IPAddress
|
||||
|
||||
if ($Computername -eq $NomOrdinateur) {
|
||||
$computerUpdatesFailed = ($wsus.GetComputerTargets($computerScope) | Where-Object Id -EQ $computerFailed.ComputerTargetId).GetUpdateInstallationInfoPerUpdate($updateScope) | Where-Object UpdateInstallationState -EQ "Failed"
|
||||
ForEach ($update In $computerUpdatesFailed) {
|
||||
$outputText = $wsus.GetUpdate($update.UpdateId).Title
|
||||
Write-Host "------------ $outputText"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
report-failed-computers/readme.md
Normal file
27
report-failed-computers/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# WSUS – Failed Computers (Display.ps1)
|
||||
Generate a CSV of client machines that had Windows Update installation errors over a recent time window, by querying **WSUS event history (errors only)**. Faster and more accurate than stitching multiple daily exports. If the `UpdateServices` module isn’t present, the script uses the WSUS Admin .NET DLL directly.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Run on the **WSUS server**, in an **elevated** 64-bit Windows PowerShell (5.1).
|
||||
- WSUS PowerShell module `UpdateServices` (installed with WSUS/RSAT) **or** the Admin DLL:
|
||||
`C:\Program Files\Update Services\Tools\Microsoft.UpdateServices.Administration.dll`
|
||||
- Local access to WSUS (HTTP 8530 or HTTPS 8531).
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
1. Copy the script here: `DisplayToErrorComputer/Display.ps1`
|
||||
2. Unblock if downloaded from the internet:
|
||||
```powershell
|
||||
Unblock-File .\Display.ps1
|
||||
```
|
||||
|
||||
## How to run
|
||||
Standard run (last 7 days, top 100):
|
||||
```powershell
|
||||
.\Display.ps1 -Days 7 -Top 100 -Verbose
|
||||
```
|
||||
61
report-top-error-computers/DisplayToError.ps1
Normal file
61
report-top-error-computers/DisplayToError.ps1
Normal file
@@ -0,0 +1,61 @@
|
||||
#Requires -RunAsAdministrator
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ---- Parameters ----
|
||||
$Days = 7 # time window for error events
|
||||
$Top = 10 # top N computers by error count
|
||||
$OutCsv = "$PSScriptRoot\WSUS_FailedComputers_{0:yyyyMMdd}.csv" -f (Get-Date)
|
||||
|
||||
# ---- Connect to WSUS (module if available, else DLL) ----
|
||||
if (Get-Module -ListAvailable UpdateServices) {
|
||||
Import-Module UpdateServices -ErrorAction Stop
|
||||
$wsus = Get-WsusServer
|
||||
} else {
|
||||
$dll = "$env:ProgramFiles\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"
|
||||
if (-not (Test-Path $dll)) { throw "WSUS Admin DLL not found: $dll" }
|
||||
Add-Type -Path $dll
|
||||
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
|
||||
}
|
||||
|
||||
# ---- Pull error events in the time window ----
|
||||
$from = (Get-Date).Date.AddDays(-[int]$Days)
|
||||
$to = (Get-Date).Date
|
||||
$events = $wsus.GetUpdateEventHistory($from, $to) |
|
||||
Where-Object { $_.IsError -and $_.ComputerId -ne [Guid]::Empty }
|
||||
|
||||
# ---- Aggregate per computer and take Top N ----
|
||||
$allComputers = $wsus.GetComputerTargets()
|
||||
$compById = @{}; foreach ($c in $allComputers) { $compById[$c.Id] = $c }
|
||||
|
||||
$topByCount = $events | Group-Object ComputerId |
|
||||
Sort-Object Count -Descending | Select-Object -First $Top
|
||||
|
||||
$result = foreach ($g in $topByCount) {
|
||||
$c = $compById[$g.Name]
|
||||
$lastEvt = $events | Where-Object { $_.ComputerId -eq $c.Id } |
|
||||
Sort-Object CreationDate -Descending | Select-Object -First 1
|
||||
[pscustomobject]@{
|
||||
ComputerName = $c.FullDomainName
|
||||
IPAddress = $c.IPAddress
|
||||
Errors = $g.Count
|
||||
LastError = $lastEvt.CreationDate
|
||||
}
|
||||
}
|
||||
|
||||
# ---- Output: table + CSV ----
|
||||
$result | Format-Table -AutoSize
|
||||
$result | Export-Csv -Path $OutCsv -NoTypeInformation -Encoding UTF8
|
||||
Write-Host "CSV: $OutCsv"
|
||||
|
||||
# ---- For each top computer, list failed updates (titles) ----
|
||||
$updScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
|
||||
foreach ($item in $result) {
|
||||
$pc = $allComputers | Where-Object { $_.FullDomainName -eq $item.ComputerName }
|
||||
if (-not $pc) { continue }
|
||||
$failed = $pc.GetUpdateInstallationInfoPerUpdate($updScope) |
|
||||
Where-Object UpdateInstallationState -EQ 'Failed'
|
||||
if ($failed) {
|
||||
Write-Host "`n$($item.ComputerName) — failed updates:"
|
||||
foreach ($u in $failed) { ($wsus.GetUpdate($u.UpdateId).Title) | ForEach-Object { Write-Host " - $_" } }
|
||||
}
|
||||
}
|
||||
40
report-top-error-computers/readme.md
Normal file
40
report-top-error-computers/readme.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# WSUS – Display Top Error Computers (PowerShell)
|
||||
|
||||
Find the **top N computers** with the most Windows Update failures in the last **N days** from WSUS, list their failed updates, and export results to CSV.
|
||||
|
||||
## What the script does
|
||||
- Reads WSUS **event history** over a time window.
|
||||
- Aggregates errors **per computer** and shows the **Top N**.
|
||||
- Prints failed **KB titles per machine**.
|
||||
- Exports a CSV: `WSUS_FailedComputers_YYYYMMDD.csv`.
|
||||
|
||||
## Requirements
|
||||
- Run **on the WSUS server**, in an **elevated** 64-bit PowerShell.
|
||||
- WSUS API available:
|
||||
- `UpdateServices` module **or**
|
||||
- `"%ProgramFiles%\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"`.
|
||||
|
||||
## Usage
|
||||
Save as `Top-ErrorComputers.ps1`, then run:
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Top-ErrorComputers.ps1
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Edit the variables at the top of the script:
|
||||
- `$Days` – time window (default: `7`)
|
||||
- `$Top` – number of computers to display (default: `10`)
|
||||
- `$OutCsv` – export path
|
||||
|
||||
## Output
|
||||
- Console table: `ComputerName`, `IPAddress`, `Errors`, `LastError`
|
||||
- CSV file at `$OutCsv`
|
||||
- For each “top” computer, a list of **failed update titles** is printed below the table.
|
||||
|
||||
## Optional: schedule (Task Scheduler)
|
||||
```powershell
|
||||
schtasks /Create /TN "WSUS Top Error Computers" ^
|
||||
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Top-ErrorComputers.ps1" ^
|
||||
/SC DAILY /ST 02:00 /RU SYSTEM /RL HIGHEST /F
|
||||
```
|
||||
|
||||
68
reset-windows-update-agent/CleanFull.ps1
Normal file
68
reset-windows-update-agent/CleanFull.ps1
Normal file
@@ -0,0 +1,68 @@
|
||||
# Script to stop update-related services, rename folders, and restart services
|
||||
|
||||
$LogPath = "C:\Windows\Temp\WUA_Reset.log"
|
||||
$Services = @("wuauserv", "cryptSvc", "bits", "msiserver")
|
||||
$SoftwareDistribution = "C:\Windows\SoftwareDistribution"
|
||||
$SoftwareDistributionOld = "C:\Windows\SoftwareDistribution.old"
|
||||
$Catroot2 = "C:\Windows\System32\catroot2"
|
||||
$Catroot2Old = "C:\Windows\System32\catroot2.old"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Add-Content -Path $LogPath -Value "$timestamp $Message"
|
||||
}
|
||||
|
||||
# Stop services
|
||||
foreach ($svc in $Services) {
|
||||
try {
|
||||
Write-Log "Stopping service: $svc"
|
||||
Stop-Service -Name $svc -Force -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Log "Failed to stop service $svc: $_"
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Rename SoftwareDistribution folder
|
||||
if (Test-Path $SoftwareDistribution) {
|
||||
try {
|
||||
if (Test-Path $SoftwareDistributionOld) {
|
||||
Remove-Item -Path $SoftwareDistributionOld -Recurse -Force
|
||||
Write-Log "Removed existing $SoftwareDistributionOld"
|
||||
}
|
||||
Rename-Item -Path $SoftwareDistribution -NewName "SoftwareDistribution.old"
|
||||
Write-Log "Renamed $SoftwareDistribution to $SoftwareDistributionOld"
|
||||
} catch {
|
||||
Write-Log "Failed to rename $SoftwareDistribution: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Rename catroot2 folder
|
||||
if (Test-Path $Catroot2) {
|
||||
try {
|
||||
if (Test-Path $Catroot2Old) {
|
||||
Remove-Item -Path $Catroot2Old -Recurse -Force
|
||||
Write-Log "Removed existing $Catroot2Old"
|
||||
}
|
||||
Rename-Item -Path $Catroot2 -NewName "catroot2.old"
|
||||
Write-Log "Renamed $Catroot2 to $Catroot2Old"
|
||||
} catch {
|
||||
Write-Log "Failed to rename $Catroot2: $_"
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Start services
|
||||
foreach ($svc in $Services) {
|
||||
try {
|
||||
Write-Log "Starting service: $svc"
|
||||
Start-Service -Name $svc -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Log "Failed to start service $svc: $_"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Script completed."
|
||||
51
reset-windows-update-agent/Cleanuplight.ps1
Normal file
51
reset-windows-update-agent/Cleanuplight.ps1
Normal file
@@ -0,0 +1,51 @@
|
||||
$global:LogPath = "C:\Windows\Temp\Wuauserv_Cleanup.log"
|
||||
$ServiceName = "wuauserv"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Add-Content -Path $global:LogPath -Value "$timestamp $Message"
|
||||
write-host $Message
|
||||
}
|
||||
|
||||
function Clean-WUClientFolders {
|
||||
Write-Log "Cleaning up Windows Update client folders"
|
||||
$windir = $env:WINDIR
|
||||
$WUDownload = Join-Path $windir "SoftwareDistribution\Download"
|
||||
$WUDataStore = Join-Path $windir "SoftwareDistribution\Datastore"
|
||||
|
||||
if (Test-Path $WUDownload) {
|
||||
try {
|
||||
Remove-Item $WUDownload -Recurse -Force
|
||||
Write-Log "Deleted $WUDownload"
|
||||
} catch {
|
||||
Write-Log ("Failed to delete $WUDownload")
|
||||
}
|
||||
}
|
||||
if (Test-Path $WUDataStore) {
|
||||
try {
|
||||
Remove-Item $WUDataStore -Recurse -Force
|
||||
Write-Log "Deleted $WUDataStore"
|
||||
} catch {
|
||||
Write-Log ("Failed to delete $WUDataStore")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Log "Stopping $ServiceName"
|
||||
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
|
||||
Start-Sleep -Seconds 3
|
||||
} catch {
|
||||
Write-Log ("Failed to stop $ServiceName")
|
||||
}
|
||||
|
||||
Clean-WUClientFolders
|
||||
|
||||
try {
|
||||
Write-Log "Starting $ServiceName"
|
||||
Start-Service -Name $ServiceName -ErrorAction Stop
|
||||
Write-Log "$ServiceName started"
|
||||
} catch {
|
||||
Write-Log ("Failed to start $ServiceName")
|
||||
}
|
||||
43
reset-windows-update-agent/readme.md
Normal file
43
reset-windows-update-agent/readme.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Reset Windows Update Components (PowerShell)
|
||||
|
||||
Two maintained scripts to fix stuck Windows Update on Windows 10/11 and Server:
|
||||
|
||||
- **cleanlight.ps1** — quick cache cleanup (fast, minimal).
|
||||
- **cleanFull.ps1** — full component reset (deeper, reversible).
|
||||
|
||||
## What each script does
|
||||
- `cleanlight.ps1`
|
||||
- Stops `wuauserv`
|
||||
- Deletes `%WINDIR%\SoftwareDistribution\Download` and `...\Datastore`
|
||||
- Restarts `wuauserv`
|
||||
- Log: `C:\Windows\Temp\Wuauserv_Cleanup.log`
|
||||
|
||||
- `cleanFull.ps1`
|
||||
- Stops `wuauserv`, `bits`, `cryptSvc`, `msiserver`
|
||||
- Renames `%WINDIR%\SoftwareDistribution` → `SoftwareDistribution.old`
|
||||
- Renames `%WINDIR%\System32\catroot2` → `catroot2.old`
|
||||
- Restarts services
|
||||
- Log: `C:\Windows\Temp\WUA_Reset.log`
|
||||
|
||||
> Note: Both approaches reset the **local** Windows Update history view. The first scan afterward can be longer.
|
||||
|
||||
## Requirements
|
||||
- Run **as Administrator** on the affected device.
|
||||
- Use **64-bit** PowerShell path.
|
||||
- Close the Windows Update UI before running.
|
||||
|
||||
## Usage
|
||||
```powershell
|
||||
# Light cleanup
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\cleanlight.ps1
|
||||
|
||||
# Full reset
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\cleanFull.ps1
|
||||
```
|
||||
|
||||
## After running
|
||||
- Trigger a scan: `USOClient StartScan` (Win10/11) or wait for the next schedule.
|
||||
- Optionally delete `*.old` folders after a few days if disk space matters.
|
||||
- Check logs (paths above) if issues persist; see also `C:\Windows\WindowsUpdate.log` and `C:\Windows\Logs\CBS\CBS.log`.
|
||||
|
||||
|
||||
118
sql-maintenance/Maintenance.sql
Normal file
118
sql-maintenance/Maintenance.sql
Normal file
@@ -0,0 +1,118 @@
|
||||
USE SUSDB;
|
||||
GO
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- Rebuild or reorganize indexes based on their fragmentation levels
|
||||
DECLARE @work_to_do TABLE (
|
||||
objectid int
|
||||
, indexid int
|
||||
, pagedensity float
|
||||
, fragmentation float
|
||||
, numrows int
|
||||
)
|
||||
|
||||
DECLARE @objectid int;
|
||||
DECLARE @indexid int;
|
||||
DECLARE @schemaname nvarchar(130);
|
||||
DECLARE @objectname nvarchar(130);
|
||||
DECLARE @indexname nvarchar(130);
|
||||
DECLARE @numrows int
|
||||
DECLARE @density float;
|
||||
DECLARE @fragmentation float;
|
||||
DECLARE @command nvarchar(4000);
|
||||
DECLARE @fillfactorset bit
|
||||
DECLARE @numpages int
|
||||
|
||||
-- Select indexes that need to be defragmented based on the following
|
||||
-- * Page density is low
|
||||
-- * External fragmentation is high in relation to index size
|
||||
PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121)
|
||||
INSERT @work_to_do
|
||||
SELECT
|
||||
f.object_id
|
||||
, index_id
|
||||
, avg_page_space_used_in_percent
|
||||
, avg_fragmentation_in_percent
|
||||
, record_count
|
||||
FROM
|
||||
sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f
|
||||
WHERE
|
||||
(f.avg_page_space_used_in_percent < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count < page_count - 1)
|
||||
or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0)
|
||||
or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0)
|
||||
|
||||
PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20))
|
||||
|
||||
PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121)
|
||||
|
||||
SELECT @numpages = sum(ps.used_page_count)
|
||||
FROM
|
||||
@work_to_do AS fi
|
||||
INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id
|
||||
INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id
|
||||
|
||||
-- Declare the cursor for the list of indexes to be processed.
|
||||
DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do
|
||||
|
||||
-- Open the cursor.
|
||||
OPEN curIndexes
|
||||
|
||||
-- Loop through the indexes
|
||||
WHILE (1=1)
|
||||
BEGIN
|
||||
FETCH NEXT FROM curIndexes
|
||||
INTO @objectid, @indexid, @density, @fragmentation, @numrows;
|
||||
IF @@FETCH_STATUS < 0 BREAK;
|
||||
|
||||
SELECT
|
||||
@objectname = QUOTENAME(o.name)
|
||||
, @schemaname = QUOTENAME(s.name)
|
||||
FROM
|
||||
sys.objects AS o
|
||||
INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id
|
||||
WHERE
|
||||
o.object_id = @objectid;
|
||||
|
||||
SELECT
|
||||
@indexname = QUOTENAME(name)
|
||||
, @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END
|
||||
FROM
|
||||
sys.indexes
|
||||
WHERE
|
||||
object_id = @objectid AND index_id = @indexid;
|
||||
|
||||
IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation < 30.0)
|
||||
SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE';
|
||||
ELSE IF @numrows >= 5000 AND @fillfactorset = 0
|
||||
SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)';
|
||||
ELSE
|
||||
SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD';
|
||||
PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command;
|
||||
EXEC (@command);
|
||||
PRINT convert(nvarchar, getdate(), 121) + N' Done.';
|
||||
END
|
||||
|
||||
-- Close and deallocate the cursor.
|
||||
CLOSE curIndexes;
|
||||
DEALLOCATE curIndexes;
|
||||
|
||||
|
||||
IF EXISTS (SELECT * FROM @work_to_do)
|
||||
BEGIN
|
||||
PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20))
|
||||
SELECT @numpages = @numpages - sum(ps.used_page_count)
|
||||
FROM
|
||||
@work_to_do AS fi
|
||||
INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id
|
||||
INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id
|
||||
|
||||
PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20))
|
||||
END
|
||||
GO
|
||||
|
||||
|
||||
--Update all statistics
|
||||
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)
|
||||
EXEC sp_updatestats
|
||||
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)
|
||||
GO
|
||||
38
sql-maintenance/readme.md
Normal file
38
sql-maintenance/readme.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# WSUS SUSDB Maintenance (Reindex + Update Stats)
|
||||
|
||||
Keep **SUSDB** fast: rebuild/reorganize fragmented indexes, then run `sp_updatestats`. Run off-hours; back up first.
|
||||
|
||||
## Requirements
|
||||
- Admin on the WSUS server.
|
||||
- Either **SqlServer** PowerShell module (`Install-Module SqlServer`) or **sqlcmd** tools.
|
||||
- WID instance uses the named pipe: `np:\\.\pipe\MICROSOFT##WID\tsql\query`.
|
||||
|
||||
## Quick start
|
||||
1) Save the SQL script as: `C:\Scripts\WSUSDBMaintenance.sql` (same as in this repo/article).
|
||||
2) Run one of the following:
|
||||
|
||||
```powershell
|
||||
# PowerShell (SqlServer module) — SQL Server instance
|
||||
Invoke-Sqlcmd -ServerInstance 'localhost' -Database 'SUSDB' `
|
||||
-InputFile C:\Scripts\WSUSDBMaintenance.sql -AbortOnError
|
||||
|
||||
# PowerShell (SqlServer module) — WID (local only)
|
||||
Invoke-Sqlcmd -ServerInstance 'np:\\.\pipe\MICROSOFT##WID\tsql\query' `
|
||||
-Database 'SUSDB' -InputFile C:\Scripts\WSUSDBMaintenance.sql -AbortOnError
|
||||
```
|
||||
|
||||
```powershell
|
||||
# sqlcmd CLI — SQL Server
|
||||
sqlcmd -S localhost -d SUSDB -i C:\Scripts\WSUSDBMaintenance.sql -b
|
||||
|
||||
# sqlcmd CLI — WID (local only)
|
||||
sqlcmd -S np:\\.\pipe\MICROSOFT##WID\tsql\query -d SUSDB -i C:\Scripts\WSUSDBMaintenance.sql -b
|
||||
```
|
||||
|
||||
## Schedule (optional)
|
||||
```powershell
|
||||
schtasks /Create /TN "WSUS DB Maintenance" ^
|
||||
/TR "powershell.exe -NoProfile -Command Invoke-Sqlcmd -ServerInstance 'np:\\.\pipe\MICROSOFT##WID\tsql\query' -Database SUSDB -InputFile C:\Scripts\WSUSDBMaintenance.sql -AbortOnError" ^
|
||||
/SC MONTHLY /D 1 /ST 02:00 /RU "SYSTEM" /RL HIGHEST /F
|
||||
```
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,616 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Configuration>
|
||||
<ViewDefinitions>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.WindowsUpdate</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>9</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Alignment>Right</Alignment>
|
||||
<Width>6</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Status</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>KB</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Size</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Title</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.OfflineMSU</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>1</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
<TableColumnHeader>
|
||||
<Width>11</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Alignment>Right</Alignment>
|
||||
<Width>8</Width>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>X</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Result</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Title</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>LastUpdated</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Size</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.WindowsUpdateJob</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>1</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>9</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Alignment>Right</Alignment>
|
||||
<Width>6</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>X</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Result</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>KB</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Size</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Title</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.History</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>14</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>19</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Operationname</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Result</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Date</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Title</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.ServiceManager</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>36</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>9</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>IsDefault</Label>
|
||||
<Width>9</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ServiceID</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>IsManaged</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>IsDefaultAUService</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.AgentInfo</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>15</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>15</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>PSWindowsUpdate</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>PSWUModuleDll</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ApiVersion</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>WuapiDllVersion</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.InstallerStatus</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>IsBusy</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.LastResults</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>10</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader/>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>LastSearchSuccessDate</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>LastInstallationSuccessDate</PropertyName>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
<View>
|
||||
<Name>PSWindowsUpdate</Name>
|
||||
<ViewSelectedBy>
|
||||
<TypeName>PSWindowsUpdate.WUJob</TypeName>
|
||||
</ViewSelectedBy>
|
||||
<TableControl>
|
||||
<TableHeaders>
|
||||
<TableColumnHeader>
|
||||
<Width>12</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Width>20</Width>
|
||||
</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<Label>Action</Label>
|
||||
</TableColumnHeader>
|
||||
</TableHeaders>
|
||||
<TableRowEntries>
|
||||
<TableRowEntry>
|
||||
<TableColumnItems>
|
||||
<TableColumnItem>
|
||||
<PropertyName>ComputerName</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<PropertyName>Name</PropertyName>
|
||||
</TableColumnItem>
|
||||
<TableColumnItem>
|
||||
<ScriptBlock>
|
||||
$_.Definition.Actions[1].Arguments -replace '-Command "|"$'
|
||||
</ScriptBlock>
|
||||
</TableColumnItem>
|
||||
</TableColumnItems>
|
||||
</TableRowEntry>
|
||||
</TableRowEntries>
|
||||
</TableControl>
|
||||
</View>
|
||||
</ViewDefinitions>
|
||||
</Configuration>
|
||||
<!-- SIG # Begin signature block -->
|
||||
<!-- MIIuSAYJKoZIhvcNAQcCoIIuOTCCLjUCAQExDzANBglghkgBZQMEAgEFADB5Bgor -->
|
||||
<!-- BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -->
|
||||
<!-- KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDKpaJQFFx7VN+7 -->
|
||||
<!-- 59g3yyjxDqbSN9hAueD6BbACoSGELqCCJnowggXJMIIEsaADAgECAhAbtY8lKt8j -->
|
||||
<!-- AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK -->
|
||||
<!-- ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy -->
|
||||
<!-- dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l -->
|
||||
<!-- dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG -->
|
||||
<!-- A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl -->
|
||||
<!-- BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb -->
|
||||
<!-- Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC -->
|
||||
<!-- Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC -->
|
||||
<!-- 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va -->
|
||||
<!-- qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ -->
|
||||
<!-- 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet -->
|
||||
<!-- /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe -->
|
||||
<!-- GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV -->
|
||||
<!-- WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl -->
|
||||
<!-- s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb -->
|
||||
<!-- MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc -->
|
||||
<!-- noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl -->
|
||||
<!-- jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA -->
|
||||
<!-- AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 -->
|
||||
<!-- +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud -->
|
||||
<!-- DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w -->
|
||||
<!-- bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v -->
|
||||
<!-- c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np -->
|
||||
<!-- dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG -->
|
||||
<!-- CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM -->
|
||||
<!-- BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 -->
|
||||
<!-- BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR -->
|
||||
<!-- mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl -->
|
||||
<!-- lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs -->
|
||||
<!-- NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv -->
|
||||
<!-- n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggXSMIIDuqADAgECAhAh1tBKTyUPyTI3 -->
|
||||
<!-- /KpeEo3pMA0GCSqGSIb3DQEBDQUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ -->
|
||||
<!-- VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp -->
|
||||
<!-- ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 -->
|
||||
<!-- b3JrIENBIDIwIhgPMjAxMTEwMDYwODM5NTZaGA8yMDQ2MTAwNjA4Mzk1NlowgYAx -->
|
||||
<!-- CzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEu -->
|
||||
<!-- MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJDAiBgNV -->
|
||||
<!-- BAMTG0NlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EgMjCCAiIwDQYJKoZIhvcNAQEB -->
|
||||
<!-- BQADggIPADCCAgoCggIBAL35ePjm1YAMZJ2GG5ZkZz8iOh51AX3v+1xnjMnMXGup -->
|
||||
<!-- kea5QuUgS5vam3u5mV3Zm4BL14RAKyfT6Lowuz4JGqdJle8rQCTCl8en7psl76gK -->
|
||||
<!-- AJeFWqqd3CnJ4jUH63BNStbBs1a4oUE4m9H7MX+P4F/hsT8PjhZJYNcGjRj5qiYQ -->
|
||||
<!-- qyrT0NFnjRtGvkcw1S5y0cVj2udjeUR+S2MkiYYuND8pTFKLKqfA4pEoibnAW/kd -->
|
||||
<!-- 2ecnrf+aApfBxlCSmwIsvam5NFkKv4RK/9/+s5/r2Z7gmCPspmt3FirbzK07HKSH -->
|
||||
<!-- 3EZzXhliaEVX5JCCQrtC1vBh4MGjPWajXfQY7ojJjRdFKZkydQIx7ikmyGsC5rVi -->
|
||||
<!-- RX83FVojaInUPt5OJ7DwQAy8TRfLTaKzHtAGWt32k89XdZn1+oYaZ3izv5b+NNy9 -->
|
||||
<!-- 51JW5bPldXvXQZEF3F1p45UNQ7n8g5Y5lXtsgFpPE3LG130pekS6UqQq1UFGCSD+ -->
|
||||
<!-- IqC2WzCNvIkM1ddw+IdS/drvrFEuB7NO/tAJ2nDvmPpW5m3btVdL3OUsJRXIni54 -->
|
||||
<!-- TvjanJ6GLMpX8xrlyJKLGoKWesO8UBJp2A5aRos66yb6I8m2sIG+QgCk+Nb+MC7H -->
|
||||
<!-- 0kb25Y51/fLMudCHW8wGEGC7gzW3XmfeR+yZSPGkoRX+rYxijjlVTzkWubFjnf+3 -->
|
||||
<!-- AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 -->
|
||||
<!-- +tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQ0FAAOCAgEAcaUO -->
|
||||
<!-- zuTpvz841YlaxAJh+0zFFBcti09TaxAX/GWExxBJkN7bxyaTiCZvcNYCXjmg94+r -->
|
||||
<!-- lrWlE1yBFG0OgYIRG4pOxk+l3WIeRN8JWfRbdws36YsgxvgKTi5YHOsz0M+GYMna -->
|
||||
<!-- +4AvnkxghHg9IWTW+0EfGA/nyXVxvb1c3jSHPkGwDva51j8JE5YUL96aHVq5Vs41 -->
|
||||
<!-- OrBfcE1e4ynxIyhyWbarwoxmJhx3LCZ2NYsop2mg+Tv1I92FEHTJkANWkeevukfU -->
|
||||
<!-- EpcRIuOiSZRs57eUS7otpNozi0ymRP9aPMYdZNi1MeSmPHqoVwvb7WEay/HOc3dj -->
|
||||
<!-- pIdvTFE41uRfx5+2gSrkhUh5WF47+NsCgmfBOdvDdEs9Nh75KZOIaFuoRBkh8Kfo -->
|
||||
<!-- gQ0s6JM2tDeyyrAbJnqaJR+amoCeSyo/+6Oa/nMyccKexnLhimgn8eQPtMRMpWGT -->
|
||||
<!-- +JcQByowJam5yHG472jMLX714H4Pgqhvtrpsg0N3zYqSF6GeW3gWPUXiM3Ld4WbK -->
|
||||
<!-- mdPJxSb9DWgERq622ZuMvhm+scbyGeNcAsos2G9KB9nJNdpAdfLEpxlvnkIQmHXm -->
|
||||
<!-- lYtgvO3FEteKztWYXFaWA8XudwY1/8/k7j8TYe7b2i2F8M2unbIYCUXDkqFyF/xH -->
|
||||
<!-- tqALLPHE3kNoCGpfO/B2Y/vMBiymxuIOtbm+JI8wggaVMIIEfaADAgECAhEA8WQl -->
|
||||
<!-- jAm24nviDjJgjkv0qDANBgkqhkiG9w0BAQwFADBWMQswCQYDVQQGEwJQTDEhMB8G -->
|
||||
<!-- A1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0dW0g -->
|
||||
<!-- VGltZXN0YW1waW5nIDIwMjEgQ0EwHhcNMjEwNTE5MDU0MjQ2WhcNMzIwNTE4MDU0 -->
|
||||
<!-- MjQ2WjBQMQswCQYDVQQGEwJQTDEhMB8GA1UECgwYQXNzZWNvIERhdGEgU3lzdGVt -->
|
||||
<!-- cyBTLkEuMR4wHAYDVQQDDBVDZXJ0dW0gVGltZXN0YW1wIDIwMjEwggIiMA0GCSqG -->
|
||||
<!-- SIb3DQEBAQUAA4ICDwAwggIKAoICAQDVYb6AAL3dhGPuEmWYHXhUi0b6xpEWGro9 -->
|
||||
<!-- Hny+NBj26L94gmI8kONVYdu2Cz9Bftkiyvk4+3MFDrkovZZQ8WDcmGXltX4xAwPA -->
|
||||
<!-- cjXEbXgEZ0exEP5Ae2bkwKlTiyUXCaq0D9JEaK5t4Kq7rH7rndKd5kX7KARcMFWE -->
|
||||
<!-- N+ikV1cgGlKgqmJSTk0Bvbgbc67oolIhtohcEktZZFut5VJxTJ1OKsRR3FUmN+4Q -->
|
||||
<!-- rAk0RIv4dw2Z4sWilqbdBaBS/5hqLt58sptiORkxnijr33VnviLP2+wbWyQM5k/A -->
|
||||
<!-- grKj8lk6A5C8V/dShj6l/TqqRMykGAKOmi6CcvGbUDibPKkjlxlALd4mHLFujWoE -->
|
||||
<!-- 91GicKUKfVkLsFqplb/dPPXQjw2TCmZbAegDQlsAppndi9UUZxHvPcryyy0Eyh1y -->
|
||||
<!-- 4Gn7Xv1vEwnwBisZjB72My8kzUQ0gjxP26vhBkvF2Cic16nVAHxyGOPm0Y0v7lFm -->
|
||||
<!-- cSyYVWg1J56YZb+QAJZCL7BJ9CBSJpAXNGxcNURN0baABlZTHn3bbBPOBhOSY9vb -->
|
||||
<!-- GwL34nOmTFpRG5mP6HQVXc/EO9cj856a9aueDGyz2hclMIZijGEa5rwacGtPw1Hz -->
|
||||
<!-- WpgNAOI24ChDBRQ8YmD23IN1rmLlzCMsRZ9wFYIvNDtMJVMSQgC0+XQBFPOe69kP -->
|
||||
<!-- wxgPNN4CCwIDAQABo4IBYjCCAV4wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxUcS -->
|
||||
<!-- TnJXtkQUa4hxGhSsMbk/uggwHwYDVR0jBBgwFoAUvlQCL79AbHNDzqwJJU6eQ0Qa -->
|
||||
<!-- 7uAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMDMGA1Ud -->
|
||||
<!-- HwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuY2VydHVtLnBsL2N0c2NhMjAyMS5jcmww -->
|
||||
<!-- bwYIKwYBBQUHAQEEYzBhMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1j -->
|
||||
<!-- ZXJ0dW0uY29tMDUGCCsGAQUFBzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0u -->
|
||||
<!-- cGwvY3RzY2EyMDIxLmNlcjBABgNVHSAEOTA3MDUGCyqEaAGG9ncCBQELMCYwJAYI -->
|
||||
<!-- KwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwF -->
|
||||
<!-- AAOCAgEAN3PMMLfCX4nmqnSsHU2rZhE/dkqrdSYLvI3U9i49hxs+i+9oo5mJl4ur -->
|
||||
<!-- PLZJ0xIz6B7CHFBNW9dFwgahnFMXiT7QnPuZ5CAwfL/9CfsAL3XdnS0AWll+7ISo -->
|
||||
<!-- mRo8d51bfpHHt3P3jx9C6Imh1A73JSp90Cq0NqPqnEflrVxYX+sYa2SO9vGsRMYs -->
|
||||
<!-- hU7uzE1V5cYWWoFUMaDHpwQuH4DNXiZO6D7f8QGWnXNHXu6S3SlaYDG4Yox7SIW1 -->
|
||||
<!-- tQv0jskmF1vdNfoxVAymQGRdNLsGzAXn6OPAUiw1xQ6M1qpjK4UnKTUiFJfvgDXb -->
|
||||
<!-- T1cvrYsJrybB/41so+DsAt0yjKxbpS5iP7SpxyHsnch0VcI54sIf0K66f4LJGocB -->
|
||||
<!-- pDTKbU1AOq3OvHbVqI7Vwqs+TGCu7TKqrTL2NQTRDAxHkso7FtH841R2A2lvYSFD -->
|
||||
<!-- fGx87B1NvPWYU3mY/GRsmQx+RgA8Pl/7Nvp7ZAY+AU8mDVr2KXrFP4unpswVBQlH -->
|
||||
<!-- xtIOxz6jeyfdLIG2oFJll3ipcASHav/obYEt/F1GRlJ+mFIQtKDadxUBmfhRlgIg -->
|
||||
<!-- YvEEtuJGERHuxfMD26jLmixu8STPGRRco+R5Bdgu+qFbnymKfuXO4sR96JYqaOOx -->
|
||||
<!-- ilcN/xr7ms13iS7wqANpd2txKZjPy3wdWniVQcuL7yCXD2uEc20wgga5MIIEoaAD -->
|
||||
<!-- AgECAhEAmaOACiZVO2Wr3G6EprPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UE -->
|
||||
<!-- BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNV -->
|
||||
<!-- BAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2Vy -->
|
||||
<!-- dHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUx -->
|
||||
<!-- ODA1MzIxOFowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5 -->
|
||||
<!-- c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENB -->
|
||||
<!-- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5j -->
|
||||
<!-- XXp1ur5cTbq96y34vuTmflN4mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS -->
|
||||
<!-- 4IndU8s7VS5+djSoMcbvIKck6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27 -->
|
||||
<!-- a8RdrT1PH9ud0IF+njvMk2xqbNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxY -->
|
||||
<!-- DB7SYOouu9Tj1yHIohzuC8KNqfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEm -->
|
||||
<!-- hu1gMXgv8aGUsRdaCtVD2bSlbfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQ -->
|
||||
<!-- N9n/kZtMLK+Wo837Q4QOZgYqVWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3H -->
|
||||
<!-- ybZtYvj9lDV2nT8mFSkcSkAExzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOI -->
|
||||
<!-- G5knN4Q/JHuurfTI5XDYO962WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g -->
|
||||
<!-- 7qf/rCkKbWpQ5boufUnq1UiYPIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpw -->
|
||||
<!-- Zo4SlADaCi2JSplKShBSND36E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rB -->
|
||||
<!-- JAcOSnAWd18Jx5n858JSqPECAwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8w -->
|
||||
<!-- HQYDVR0OBBYEFN10XUwA23ufoHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkC -->
|
||||
<!-- w6A/joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEF -->
|
||||
<!-- BQcDAzAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5j -->
|
||||
<!-- YTIuY3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNh -->
|
||||
<!-- Lm9jc3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnku -->
|
||||
<!-- Y2VydHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYB -->
|
||||
<!-- BQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOC -->
|
||||
<!-- AgEAdYhYD+WPUCiaU58Q7EP89DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARM -->
|
||||
<!-- gwRzLRUStJl490L94C9LGF3vjzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJ -->
|
||||
<!-- TZVJeChDUyuQy6rGDxLUUAsO0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNG -->
|
||||
<!-- xHBIutiRsWrhWM2f8pXdd3x2mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQi -->
|
||||
<!-- gO0ugh7bWRLDm0CdY9rNLqyA3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4 -->
|
||||
<!-- Am1/7mATwZWwSD+B7eMcZNhpn8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lI -->
|
||||
<!-- e5Iv6RY3/bFAEloMU+vUBfSouCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7 -->
|
||||
<!-- gCBibESYkOh1/w1tVxTpV2Na3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOE -->
|
||||
<!-- MLOGGniR+x+zPF/2DaGgK2W1eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LH -->
|
||||
<!-- uH8X3n9C+xN4YaNjt2ywzOr+tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyY -->
|
||||
<!-- eEEUz/Qwt4HOUBCrW602NCmvO1nm+/80nLy5r0AZvCQxaQ4wgga5MIIEoaADAgEC -->
|
||||
<!-- AhEA5/9pxzs1zkuRJth0fGilhzANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMC -->
|
||||
<!-- UEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsT -->
|
||||
<!-- HkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVt -->
|
||||
<!-- IFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1MzIwN1oXDTM2MDUxODA1 -->
|
||||
<!-- MzIwN1owVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3Rl -->
|
||||
<!-- bXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIFRpbWVzdGFtcGluZyAyMDIxIENBMIIC -->
|
||||
<!-- IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6RIfBDXtuV16xaaVQb6KZX9O -->
|
||||
<!-- d9FtJXXTZo7b+GEof3+3g0ChWiKnO7R4+6MfrvLyLCWZa6GpFHjEt4t0/GiUQvnk -->
|
||||
<!-- LOBRdBqr5DOvlmTvJJs2X8ZmWgWJjC7PBZLYBWAs8sJl3kNXxBMX5XntjqWx1ZOu -->
|
||||
<!-- uXl0R4x+zGGSMzZ45dpvB8vLpQfZkfMC/1tL9KYyjU+htLH68dZJPtzhqLBVG+8l -->
|
||||
<!-- jZ1ZFilOKksS79epCeqFSeAUm2eMTGpOiS3gfLM6yvb8Bg6bxg5yglDGC9zbr4sB -->
|
||||
<!-- 9ceIGRtCQF1N8dqTgM/dSViiUgJkcv5dLNJeWxGCqJYPgzKlYZTgDXfGIeZpEFmj -->
|
||||
<!-- BLwURP5ABsyKoFocMzdjrCiFbTvJn+bD1kq78qZUgAQGGtd6zGJ88H4NPJ5Y2R4I -->
|
||||
<!-- argiWAmv8RyvWnHr/VA+2PrrK9eXe5q7M88YRdSTq9TKbqdnITUgZcjjm4ZUjteq -->
|
||||
<!-- 8K331a4P0s2in0p3UubMEYa/G5w6jSWPUzchGLwWKYBfeSu6dIOC4LkeAPvmdZxS -->
|
||||
<!-- B1lWOb9HzVWZoM8Q/blaP4LWt6JxjkI9yQsYGMdCqwl7uMnPUIlcExS1mzXRxUow -->
|
||||
<!-- Qref/EPaS7kYVaHHQrp4XB7nTEtQhkP0Z9Puz/n8zIFnUSnxDof4Yy650PAXSYmK -->
|
||||
<!-- 2TcbyDoTNmmt8xAxzcMCAwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYD -->
|
||||
<!-- VR0OBBYEFL5UAi+/QGxzQ86sCSVOnkNEGu7gMB8GA1UdIwQYMBaAFLahVDkCw6A/ -->
|
||||
<!-- joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcD -->
|
||||
<!-- CDAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIu -->
|
||||
<!-- Y3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9j -->
|
||||
<!-- c3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2Vy -->
|
||||
<!-- dHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUH -->
|
||||
<!-- AgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEA -->
|
||||
<!-- uJNZd8lMFf2UBwigp3qgLPBBk58BFCS3Q6aJDf3TISoytK0eal/JyCB88aUEd0wM -->
|
||||
<!-- NiEcNVMbK9j5Yht2whaknUE1G32k6uld7wcxHmw67vUBY6pSp8QhdodY4SzRRaZW -->
|
||||
<!-- zyYlviUpyU4dXyhKhHSncYJfa1U75cXxCe3sTp9uTBm3f8Bj8LkpjMUSVTtMJ6oE -->
|
||||
<!-- u5JqCYzRfc6nnoRUgwz/GVZFoOBGdrSEtDN7mZgcka/tS5MI47fALVvN5lZ2U8k7 -->
|
||||
<!-- Dm/hTX8CWOw0uBZloZEW4HB0Xra3qE4qzzq/6M8gyoU/DE0k3+i7bYOrOk/7tPJg -->
|
||||
<!-- 1sOhytOGUQ30PbG++0FfJioDuOFhj99b151SqFlSaRQYz74y/P2XJP+cF19oqozm -->
|
||||
<!-- i0rRTkfyEJIvhIZ+M5XIFZttmVQgTxfpfJwMFFEoQrSrklOxpmSygppsUDJEoliC -->
|
||||
<!-- 05vBLVQ+gMZyYaKvBJ4YxBMlKH5ZHkRdloRYlUDplk8GUa+OCMVhpDSQurU6K1ua -->
|
||||
<!-- 5dmZftnvSSz2H96UrQDzA6DyiI1V3ejVtvn2azVAXg6NnjmuRZ+wa7Pxy0H3+V4K -->
|
||||
<!-- 4rOTHlG3VYA6xfLsTunCz72T6Ot4+tkrDYOeaU1pPX1CBfYj6EW2+ELq46GP8KCN -->
|
||||
<!-- UQDirWLU4nOmgCat7vN0SD6RlwUiSsMeCiQDmZwgwrUwggbAMIIEqKADAgECAhA/ -->
|
||||
<!-- LwafXGuInpplxVMGdJYVMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEw -->
|
||||
<!-- HwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1 -->
|
||||
<!-- bSBDb2RlIFNpZ25pbmcgMjAyMSBDQTAeFw0yMjA0MTQwOTIxMzhaFw0yNTA0MTMw -->
|
||||
<!-- OTIxMzdaMGYxCzAJBgNVBAYTAlBMMREwDwYDVQQHDAhXYXJzemF3YTEhMB8GA1UE -->
|
||||
<!-- CgwYUG93ZXJDbG91ZHMgTWljaGFsIEdhamRhMSEwHwYDVQQDDBhQb3dlckNsb3Vk -->
|
||||
<!-- cyBNaWNoYWwgR2FqZGEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCe -->
|
||||
<!-- t+xkW4ECVL1DSj9xcwGDe3yTrXncKuO/UsHQjDG3wTKEMZJukC8gHXnPKlrtg9H3 -->
|
||||
<!-- KtdG9L55A+x55VCl5Kpay4ATrxAUKds5B6Zu22aoe3tepXlaQk8PO+auvtT+r0cz -->
|
||||
<!-- JvTA7/w7Fvm5V9tJBTOyRX0ZsGypifov8sZFghL9pVQBqc+aAWx/Fsg8s1JSggx5 -->
|
||||
<!-- iOSQlLvxxLb97mhpLDFxobgqndbjCquLMEV5cqY6+ODhVJBAEH4425UkMEVkX50g -->
|
||||
<!-- 42PFoHee/ZA4+5aMx8Rf9u1pA6yVqvcHoJwXjuou2r/x6ss+b9ScUyvUIcjXokyA -->
|
||||
<!-- tTtyrUOwE2jWDvznhbPmlthn2lzss6yAaTTeryTgcV0f8nhs2QFoa8TZMFi/fNjL -->
|
||||
<!-- 8orWspq/0Z1HwNNkD1Mh2B6JnD/unOy3fj77MBhMnWzSVTHH+ZYDGwYDsgmUbf8i -->
|
||||
<!-- D6MvEGzLwiyP83iL6rvcskSsKNocPB1LypTP+TvuGGIdmQheNfj1Y2ms66O/sO27 -->
|
||||
<!-- +9JA4jWxNFFQQ6ZTITTl7fff91bozeKjI+CE7xbAtODm+Gsi17YrhgeUVk1YpVOK -->
|
||||
<!-- jBhnXjjMJ9Mh+CVWfxArEs/A0ZZHzXeACYmxaMso8hXU5FSCUTMt4MjxPFamz59e -->
|
||||
<!-- 0f98qKLkVfFioQqU/D/J2u4/+UFJegzZg93b6iD+1QIDAQABo4IBeDCCAXQwDAYD -->
|
||||
<!-- VR0TAQH/BAIwADA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY2NzY2EyMDIxLmNy -->
|
||||
<!-- bC5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNybDBzBggrBgEFBQcBAQRnMGUwLAYIKwYB -->
|
||||
<!-- BQUHMAGGIGh0dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0dW0uY29tMDUGCCsGAQUF -->
|
||||
<!-- BzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNlcjAf -->
|
||||
<!-- BgNVHSMEGDAWgBTddF1MANt7n6B0yrFu9zzAMsBwzTAdBgNVHQ4EFgQUdcAoCYdl -->
|
||||
<!-- DBKMfqix4T3EIEAB5TEwSwYDVR0gBEQwQjAIBgZngQwBBAEwNgYLKoRoAYb2dwIF -->
|
||||
<!-- AQQwJzAlBggrBgEFBQcCARYZaHR0cHM6Ly93d3cuY2VydHVtLnBsL0NQUzATBgNV -->
|
||||
<!-- HSUEDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD -->
|
||||
<!-- ggIBABXxKsBFLjKAz9CvVM2Qv6+4MyUAJDT3Ws2i+9qHUsdwGkrdXvIO7z+FXxhP -->
|
||||
<!-- I04XwcZNNcjhyQY6MIOxDWIVcJ3QhO0LScuZCeO43ZSLMrkNyXBbaHCyWIGU1QlD -->
|
||||
<!-- m3Ah8uo0HMXcEDAmaUB1c2Ak4ARfTunTlSP+4cd7c3MvmZ4tOQcHKTKFd0RAFpa9 -->
|
||||
<!-- ynEd/DoAXrACdDPAc3clEzPfbw5jJGwbs9Nysk5SyiNKCQtMAnYnqGRfRkLjb73t -->
|
||||
<!-- NPH1M7UnUqnEVAqs+t64a7QCIUSDSIITGEB4bhlFUIuwAhekZj7yPIrPwv2TY8q7 -->
|
||||
<!-- ClyN4bOTSphh/JGTBlW9dgSPF5bQCeW7s3jAggLnz4xg3eojZuSPhXYdL5Ax0Yya -->
|
||||
<!-- ZoXq3K2CvP0/SYZh0xTp75nNI60tV9DsopI/ymkBwfYFS77Kmelohst2BAjCRmJN -->
|
||||
<!-- LDZJK66vUXlarANhjgL5B/WUyxIuPNaf6PC9AzWz1GHxNbeNUCZgjD9wlpILDH5g -->
|
||||
<!-- sYdimAhTvgftUyMKvyL42rKVpGZW9L8/v9/+vrkf+6fxufHxgksscAH0Qqofx++Y -->
|
||||
<!-- lq4ZQ2PzprI9YBNLkmwuSQhf+IdT/jwu2cYUN9buOZJo39RtFsVLQMGfFuliN3SA -->
|
||||
<!-- VtfFN+2f4wsRtp3q7BllwC4ke4KdtkedLNUFPUj10aBGkSacMYIHJDCCByACAQEw -->
|
||||
<!-- ajBWMQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT -->
|
||||
<!-- LkEuMSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0ECED8vBp9c -->
|
||||
<!-- a4iemmXFUwZ0lhUwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAig -->
|
||||
<!-- AoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgEL -->
|
||||
<!-- MQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgtijQ815FRBTwG3ETOLZq -->
|
||||
<!-- m3ClpfuhrSrIjiwdF3NJbLIwDQYJKoZIhvcNAQEBBQAEggIAAAhkBgKkZO5r67jR -->
|
||||
<!-- tAUWwspQC0fuYz2+WduY+zk5aFwSqkX4HB0gC3CEB82fzZPlS85T87PCJEo0z0lw -->
|
||||
<!-- H6RJDcQZUhyRm3X77Uu6deaOu/hPrXWQ7VnwH6zl2wUJNr4/m4Gil1K27CCsr7cv -->
|
||||
<!-- vCMKZb/n2aJp2kFJWlWvrcyWSlRS3iG51aciZG5dLUJZVRxmXcRufZMOzokwh8qm -->
|
||||
<!-- xUFXawjWPmlA2Vqujc1ojIyaf6lLD6bBBAWVn3DtMsEJU2ZjH76cRyHF9ikJqGqq -->
|
||||
<!-- 8haT+nZ2n59u+qQn9krHRToRuIFfpeLLieUKE9iUw9r2w8rm9c70P9VWgFIsuU8o -->
|
||||
<!-- v+Lr6a8n3NWpY2iZQXypPRSDr/BxEETaW28V6GYNMgYZBqmP2Zgm+o2UVLCjG00c -->
|
||||
<!-- LjLqKd0gFB3kbMeNeuZHVZVDzaCxMXrNN/EuLrhgJP3lKu8gnPXHi5pUs31Rt/XE -->
|
||||
<!-- 3eEbiXP686fWT+Qh382rgDpoPWVrNlRrfsZrI3mahCIoMsOoKyTquTNXZ6RTuJUL -->
|
||||
<!-- hJBGqkoS4YOFuvhoOjeRuxSj3Gz9qZwmBlC05p6zdjcWxEvS8bLnjv55NGfN8baB -->
|
||||
<!-- JWQmvuc+6r89mzM1FLSQow+wQWaTEfBzQFme/WT4CeZTLmSllQgjZmAvxa92dl8h -->
|
||||
<!-- YcA4jg0u2Kgz00eV48uIGXqS7RKhggQEMIIEAAYJKoZIhvcNAQkGMYID8TCCA+0C -->
|
||||
<!-- AQEwazBWMQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVt -->
|
||||
<!-- cyBTLkEuMSQwIgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECEQDx -->
|
||||
<!-- ZCWMCbbie+IOMmCOS/SoMA0GCWCGSAFlAwQCAgUAoIIBVzAaBgkqhkiG9w0BCQMx -->
|
||||
<!-- DQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTIyMDQxNDE2MjgwM1owNwYL -->
|
||||
<!-- KoZIhvcNAQkQAi8xKDAmMCQwIgQgG1m/6OV3K6z2Q7t5rLSOgVh4TyHFVK4TR206 -->
|
||||
<!-- Gj4FxdMwPwYJKoZIhvcNAQkEMTIEME5v0tKwiudEv1n1KYov3yE2QsVCGP8D7+aB -->
|
||||
<!-- KIZy/jHXtb9ztg4DSQShYNtQ6qtKuTCBoAYLKoZIhvcNAQkQAgwxgZAwgY0wgYow -->
|
||||
<!-- gYcEFNMRxpUxG4znP9W1Uxis31mK4ZsTMG8wWqRYMFYxCzAJBgNVBAYTAlBMMSEw -->
|
||||
<!-- HwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1 -->
|
||||
<!-- bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIRAPFkJYwJtuJ74g4yYI5L9KgwDQYJKoZI -->
|
||||
<!-- hvcNAQEBBQAEggIAh75u2Q63COfUNq/KdldXpX7Qqnn996b17Up5ji/ZZiHNkFma -->
|
||||
<!-- Utf+0g7sg6QFYmdqQC7LWQf4kuskeSLrJLhtNf/Ps+FB6uU4/ADCrSaNP7hxOcal -->
|
||||
<!-- hLtwo5N32OSoNksh4+/OPLNGGeXaY9BFv/fMzJd/2m5PdfvuZuXjzo9LkJZIZFIX -->
|
||||
<!-- 4aGw2JQ5z5P/SSjUDAWxwQk+dS2bn7i2apedDFbyJehz4TZvhNaLBR8xxDiDHZS2 -->
|
||||
<!-- KMtSKBiwO+4Z+J4KKri8LgiYy/g/KT0muVH8ZBqrE7tRffaBphREeXthG/T165fD -->
|
||||
<!-- 49iFlXtn1lRogrgUL69QqrjOhx4ZXoTyW6EveDchIXRiS8dHom6iGgxp55PSyU9t -->
|
||||
<!-- TFkoUuMIJ7BRvoePYFiqSKmKvQ26U3QukUN0zePCjaKeOw6ta+ATbJW0fhR+eEO5 -->
|
||||
<!-- Awfj4nl0rVwo1O0b+DLzQSvGuDffVMuD1RBeghwZwrPRB2WInxpK27bek01D4T13 -->
|
||||
<!-- /VjmfbEdbVXCMXCt8kViO9OwFwFwMnTPCBDg5hlVELW+U9M8RLj63AQB7GjmAtlS -->
|
||||
<!-- T8ZYX+vy5GTisBTCuS6gzK5CuQnQPczkxqbHHZbX1Jam/1isBbvz1IVk8MXp675g -->
|
||||
<!-- fCa+wIFEEexj2jUqjQGcdcHZNsV6sdaMw+StiHB57nCf40GE8hZRJMPnCX0= -->
|
||||
<!-- SIG # End signature block -->
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
34
uninstall-kb-pswindowsupdate/readme.md
Normal file
34
uninstall-kb-pswindowsupdate/readme.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Uninstall a Windows Update by KB (PSWindowsUpdate)
|
||||
|
||||
Minimal helper and examples to remove an installed Windows update by its **KB** number using the **PSWindowsUpdate** module.
|
||||
|
||||
## Requirements
|
||||
- Run PowerShell **as Administrator**.
|
||||
- Module: `PSWindowsUpdate` (install if needed: `Install-Module PSWindowsUpdate -Scope CurrentUser`).
|
||||
- A reboot may be required after removal.
|
||||
|
||||
## Quick commands (no wrapper)
|
||||
```powershell
|
||||
# List installed updates matching the KB
|
||||
Get-WindowsUpdate -IsInstalled -KBArticleID KB5028952
|
||||
|
||||
# Uninstall (no auto-restart)
|
||||
Remove-WindowsUpdate -KBArticleID KB5028952 -NoRestart -Confirm:$false
|
||||
```
|
||||
|
||||
## Script usage
|
||||
If you saved the wrapper as `Remove-KB.ps1`:
|
||||
```powershell
|
||||
# Example
|
||||
.\Remove-KB.ps1 -KB KB5028952 -NoRestart
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Some updates (especially **Servicing Stack Updates**) cannot be uninstalled.
|
||||
- If removal via PSWindowsUpdate fails for a cumulative update, find the exact package name and try DISM:
|
||||
```powershell
|
||||
DISM /Online /Get-Packages
|
||||
DISM /Online /Remove-Package /PackageName:<ExactName> /Quiet /NoRestart
|
||||
```
|
||||
- Logs: `C:\Windows\Logs\WindowsUpdate\windowsupdate.log` and `C:\Windows\Logs\CBS\CBS.log`.
|
||||
|
||||
36
uninstall-kb-pswindowsupdate/uninstallKB.ps1
Normal file
36
uninstall-kb-pswindowsupdate/uninstallKB.ps1
Normal file
@@ -0,0 +1,36 @@
|
||||
#Requires -RunAsAdministrator
|
||||
#Requires -Modules PSWindowsUpdate
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^(?i:KB)?\d+$')]
|
||||
[string]$KB,
|
||||
[switch]$NoRestart
|
||||
)
|
||||
|
||||
# Normalize input to "KBxxxxxxx"
|
||||
if ($KB -match '^\d+$') { $KB = "KB$KB" }
|
||||
|
||||
try {
|
||||
Import-Module PSWindowsUpdate -ErrorAction Stop
|
||||
|
||||
# Query only installed updates matching the KB
|
||||
$installed = Get-WindowsUpdate -IsInstalled -KBArticleID $KB -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $installed) {
|
||||
Write-Host "Update $KB not found on this system."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Found installed update: $KB"
|
||||
$removeParams = @{ KBArticleID = $KB; Confirm = $false }
|
||||
if ($NoRestart) { $removeParams['NoRestart'] = $true }
|
||||
|
||||
Write-Host "Uninstalling $KB ..."
|
||||
Remove-WindowsUpdate @removeParams -ErrorAction Stop
|
||||
Write-Host "Uninstall command issued for $KB."
|
||||
if (-not $NoRestart) { Write-Host "A restart may be required." }
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to uninstall $KB. $($_.Exception.Message)"
|
||||
}
|
||||
19
uninstall-kb-remove-windows-package/readme.md
Normal file
19
uninstall-kb-remove-windows-package/readme.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Remove Windows Updates by KB (PowerShell)
|
||||
|
||||
Uninstalls all installed Windows packages whose name matches a given **KB** via DISM PowerShell cmdlets.
|
||||
|
||||
## Usage
|
||||
```powershell
|
||||
# List installed packages and filter by KB
|
||||
Get-WindowsPackage -Online | Where-Object { $_.PackageName -match "KB5032189" } | Select-Object PackageName, State, InstallTime
|
||||
```
|
||||
|
||||
```powershell
|
||||
# Uninstall all packages matching the KB
|
||||
Remove-Package -KB "KB4589210"
|
||||
```
|
||||
|
||||
## Notes & limitations
|
||||
MSU files: Remove-WindowsPackage removes packages in the image (.cab/package identities), not .msu directly
|
||||
SSUs can’t be uninstalled: Servicing Stack Updates modify the update stack and are not removable.
|
||||
After ResetBase: If you ran DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase, existing update packages can no longer be uninstalled.
|
||||
33
uninstall-kb-remove-windows-package/remove.ps1
Normal file
33
uninstall-kb-remove-windows-package/remove.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
function Remove-Package {
|
||||
param(
|
||||
[string]$KB
|
||||
)
|
||||
|
||||
# Find matching package(s)
|
||||
$packages = Get-WindowsPackage -Online | Where-Object { $_.PackageName -match $KB }
|
||||
|
||||
if($packages.Count -eq 0) {
|
||||
Write-Host "No packages found matching $KB."
|
||||
return
|
||||
}
|
||||
|
||||
# Display found packages
|
||||
Write-Host "Packages found matching $KB"
|
||||
$packages | ForEach-Object { Write-Host $_.PackageName }
|
||||
|
||||
# Uninstall packages
|
||||
foreach($package in $packages) {
|
||||
try {
|
||||
Write-Host "Uninstalling $($package.PackageName)..."
|
||||
Remove-WindowsPackage -Online -PackageName $package.PackageName -NoRestart -ErrorAction Stop
|
||||
Write-Host "$($package.PackageName) uninstalled successfully."
|
||||
} catch {
|
||||
Write-Error "Failed to uninstall $($package.PackageName). Error: $_"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Please restart your computer."
|
||||
}
|
||||
|
||||
# Example usage
|
||||
Remove-Package -KB "KB4589210"
|
||||
Reference in New Issue
Block a user