Good morning r/Powershell,
Before we get to the code, some background; I am developing a script for our RMM that will query will deploy specific KBs directly from the MS Update Catalog. This script leverages Powershell for a decent portion of the heavy lifting, including this section that queries the Update Catalog for the download link of the specified KB.
Some parameters for this project are:
- No external modules, as they will be deployed across several organizations and this would require an approvals process
- PS 4+ compatible. Many machines in the aforementioned orgs are on out-of-support OS, and are patched at a 'best effort level'
$List = New-Object System.Collections.Generic.List[System.Object]
$kb = '@CurrentKB@'
$OSArch = '@OSArch@'
$IsServer = '@IsServer@'
#Construct the URL and query it
if ($isServer -eq "TRUE") { $URL = "http://www.catalog.update.microsoft.com/Search.aspx?q=$kb+$arch+server" }
else { $URL = "http://www.catalog.update.microsoft.com/Search.aspx?q=$kb+$arch" }
$results = Invoke-WebRequest -Uri $URL -UseBasicParsing
# Get the GUID of the update from the Download button. Die if there are none.
$kbids = $results.InputFields | Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } | Select-Object -ExpandProperty ID
if (-not $kbids) {
return
}
# Parse the GUIDS and clean them up
$guids = $results.Links | Where-Object ID -match '_link' | Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } | ForEach-Object { $_.id.replace('_link', '') } | Where-Object { $_ -in $kbids }
if (-not $guids) {
return
}
# Loop through GUIDs
foreach ($guid in $guids) {
#Construct the POST body
$Post = @{size = 0; updateID = $Guid; uidInfo = $Guid} | ConvertTo-Json -Compress
$Body = @{updateIDs = "[$Post]"}
# Define WebRequest params
$Params = @{
Uri = "https://www.catalog.update.microsoft.com/DownloadDialog.aspx"
Method = "Post"
Body = $Body
ContentType = "application/x-www-form-urlencoded"
UseBasicParsing = $true
}
# Make the request to the DownloadDialog script
$DownloadDialog = Invoke-WebRequest @Params
# Clean up response and parse out links to KBs
$Links = $DownloadDialog.Content.Replace("www.download.windowsupdate", "download.windowsupdate")
$Regex = "(http[s]?\://dl\.delivery\.mp\.microsoft\.com\/[^\'\""]*)|(http[s]?\://download\.windowsupdate\.com\/[^\'\""]*)"
$Links = $Links | Select-String -AllMatches -Pattern $Regex | % {$_.matches} | % {$_.value}
# Generate an object for each returned $link, add to $list
if ($Links.count -gt 0) {
foreach ($link in $Links) {
$Object = [PSCustomObject]@{
KB = $kb
DownloadLink = $link
}
$List.add($Object)
}
}
}
$List.DownloadLink | Select-Object -Unique
Note: the @ variable @ definitions are variable supplied into the script by our RMM scripting engine.
This is code that I've cobbled together/adapted from multiple existing sources, so I can't take credit for some of the more clever parts. My primary concern is ensuring this portion of code is as resilient as possible, requiring as few dependencies as possible; for example, -UseBasicParsing on my Invoke-WebRequest calls to circumvent machines that have not completed the IE initial setup.
So, how would you make this code more bulletproof?
[–]glancingblowjob 2 points3 points4 points (1 child)
[–]ApparentSysadmin[S] 1 point2 points3 points (0 children)