首页 > 代码库 > Use powershell script to download windows patches monthly
Use powershell script to download windows patches monthly
My company concerns security, request us to deploy the newest patches on our servers in time, even we have firewall/encryption internally.
With the number of servers increasing, there must be some servers can‘t be patched as expected, probably caused by SCCM/WSUS or incorrect configuration on server, plus 3rd party patch scanning software and presures from security team, support team like me have to patch missing patches one by one. This makes me have to paste the MS number in google, and find the correct KB, then download and install, turns out it‘s a mess.
To make life easier, I did the script to automatic download patches from MS with scheduled time, the script will grab contents from MS RSS and get MS numbers and links, it will loop MS links and grabs KBs, and download patches to local path.
Security RSS: https://technet.microsoft.com/en-us/security/rss/bulletin
Workflow: Get contents of RSS -> grab MS numbers and links -> Enum MS links and grab KBs -> filter out some KB -> grab KB details link -> grab KB download links -> Download
Step by step to analysis the script,
$Url = ‘https://technet.microsoft.com/en-us/security/rss/bulletin‘$ExcludeProducts = ‘lync|Itanium|for mac‘$IncludeProducts = ‘server‘$ExcludePatches = ‘-IA64|Windows6\.0|-RT-|ServiceBusServer‘$PatchStoreTo = ‘.\‘
some variables defined,
$Url is the link of RSS;
$ExcludeProducts when get the contents of MS link, use regular expression to filter out unwanted product, for me is lync and patches for Itanium cpu;
$IncludeProducts after product filter, i want to filter again for KBs for "server";
$ExcludePatches is another filter, when final get patch link, I don‘t want patches for Itanium (Why? because some KB doesn‘t have enough details, so this filter added);
$PatchStoreTo is a path to store patches.
$WebClient = New-Object System.Net.WebClient$WebClient.Encoding = [System.Text.Encoding]::UTF8
Create the webclient object and set the encoding
do{ $RSSContent = $WebClient.DownloadString($Url)}while( $(if(!$?) { Write-Host ‘Failed to get RSS‘ -ForegroundColor Red Start-Sleep -Seconds 600 $true }))
Get the contents of RSS, if failed, will report with red words and sleep 10 minutes to do again.
([xml]$RSSContent).rss.channel.Item | Sort-Object link | %{...}
Convert RSS contents to XML type, then can easily retrieve data.
$MSRC_URL = $_.link Write-Host "Processing: [$MSRC_URL]" -ForegroundColor Yellow $MSRC = http://www.mamicode.com/([regex]::Match($MSRC_URL, ‘(?i)MS\d+-\d+$‘)).Value Write-Host "MS number: [$MSRC]" -ForegroundColor Green if(!(Test-Path -LiteralPath "$PatchStoreTo\$MSRC")) { do { New-Item -Path "$PatchStoreTo\$MSRC" -ItemType Directory | Out-Null } while( $(if(!$?) { Write-Host ‘Failed to create MSRC folder‘ -ForegroundColor Red Start-Sleep 300 $true }) ) }
MS link stores in $MSRC_URL, and output to screen as color yellow, then use regular expression to grab MS number and stored in $MSRC, after that create a folder named as MS number to store patches.
Write-Host "Trying to capture KBs from MSRC URL" -ForegroundColor Yellow do { $MSContent = $null $MSContent = $WebClient.DownloadString($MSRC_URL) } while( $(if(!$?) { Write-Host ‘Failed to capture MSRC content‘ -ForegroundColor Red Start-Sleep 300 $true }) )
Above codes is to grab MS link contents and store in $MSContent.
MS link is like https://technet.microsoft.com/en-us/library/security/MS14-063, MS contents are the source codes behind the web page.
[regex]::Matches($MSContent, ‘(?i)<tr>[\s\S]+?<a href="http://www.mamicode.com/(http://www.microsoft.com/downloads/details.aspx/?FamilyID=[\w\-]+?)">[\s\S]+?\((\d{7})\)‘) | %{...}
The code is to grab KB information, like KB number, and KB link.
It will match contents like below screenshot, all characters in the grah will be matched by the regular expression pattern.
Write-Host "KB: [$($_.Groups[2].Value)]" -NoNewline -ForegroundColor Green if($_.Value -imatch $ExcludeProducts) { Write-Host " --- Excluded: [$($Matches[0])]" -ForegroundColor Red } else { if($_.Value -notmatch $IncludeProducts) { Write-Host " --- Excluded: Not match [$IncludeProducts]" -ForegroundColor Red return } $KBNumber = "KB$($_.Groups[2].Value)"
Write-Host "`nDownload URL: [$($_.Groups[1].Value)]" -ForegroundColor Gray
Above code excludes the KBs matched product names in $excludeProducts, and left KB filtered again by $IncludeProducts, final passed KBs store in $KBNumber.
do { $KBContent = $null $KBContent = $WebClient.DownloadString($_.Groups[1].Value) }while( $(if(!$?) { Write-Host ‘Failed to capture KB content‘ -ForegroundColor Red Start-Sleep 300 $true }) )
Above code get contents from KB link and stores in $KBContent,
KB link looks like this in $MSContent: http://www.microsoft.com/downloads/details.aspx?familyid=8a59fc6d-cbad-4905-842b-e5aa1fc6fedf
Access KB link will automatic redirect to:http://www.microsoft.com/en-us/download/details.aspx?id=44400
Surely this is a automation behavior of web server, I don‘t need to do anything in the script. Anyway the KB link page doesn‘t contain patch link, it just ask for confimation on languages and provide us some KB details, you can find screenshot followed.
As followed, I need to analysis KB contents, and find the page called "confirmation.aspx". Actually I can see it when I move my cursor on the "Download" button.
$KBConfirm = ([regex]::Match($KBContent, ‘(?i)href="http://www.mamicode.com/(confirmation.aspx/?id=\d+)"‘)).Groups[1].Value $KBConfirm = "http://www.microsoft.com/en-us/download/$KBConfirm" Write-Host "KB confirm URL: [$KBConfirm]" -ForegroundColor Gray do { $KBContent = $null $KBContent = $WebClient.DownloadString($KBConfirm) }while( $(if(!$?) { Write-Host ‘Failed to capture KB download content‘ -ForegroundColor Red Start-Sleep 300 $true }) )
Codes used to grab "confirmation.aspx" page link from KB contents, you may find the "id" behind "confirmation.aspx" is the same like "details.aspx" of KB link, but just for safey, I choose to grab "confirmation.aspx" page from KB content.
$KBLinks = @() $KBLinks = [regex]::Matches($KBContent, ‘(?i)<a href="http://www.mamicode.com/(http://download.microsoft.com/download/.+?)".+?>Click here</span>‘) | %{ $_.Groups[1].Value } $KBLinks = @($KBLinks | Sort-Object -Unique) Write-Host "The KB contains updates: [$($KBLinks.Count)]" -ForegroundColor Green
After I get the contents of "confirmation.aspx", I use regular expression to match patch links and do a unique sort for final results, now $KBLinks contains all patches belong to that KB.
Followed screenshot is the "confirmation.aspx", it contains all patches download link, I used regular expression again to grab those links.
$KBLinks | %{ $FileName = $null $FileName = $_.Split(‘/‘)[-1] if($FileName -imatch $ExcludePatches) { Write-Host "Patch excluded: [$($Matches[0])]" -ForegroundColor Red return }
Now I have patch links in hand, the job left is download, but I do another filter before the downloading, as i mentioned previously, sometimes KB contents don‘t have enough information, so in here I use another filter to remove patches i don‘t want by patch names.
if(Test-Path -Path $FilePath) { Write-Host ‘File already exists, skip!‘ -ForegroundColor Gray } else { do { $WebClient.DownloadFile($_, $FilePath) }while( $(if(!$?) { Write-Host ‘Download file failed!‘ -ForegroundColor Red Start-Sleep -Seconds 300 $true }) ) }
Real download codes here, if patch already exists, script will skip it.
Last, one screenshot when script running, and full script followed.
Full script here,
$Url = ‘https://technet.microsoft.com/en-us/security/rss/bulletin‘$ExcludeProducts = ‘lync|Itanium|for mac‘$IncludeProducts = ‘server‘$ExcludePatches = ‘-IA64|Windows6\.0|-RT-|ServiceBusServer‘$PatchStoreTo = ‘.\‘$WebClient = New-Object System.Net.WebClient$WebClient.Encoding = [System.Text.Encoding]::UTF8do{ $RSSContent = $WebClient.DownloadString($Url)}while( $(if(!$?) { Write-Host ‘Failed to get RSS‘ -ForegroundColor Red Start-Sleep -Seconds 600 $true }))([xml]$RSSContent).rss.channel.Item | Sort-Object link | %{ $MSRC_URL = $_.link Write-Host "Processing: [$MSRC_URL]" -ForegroundColor Yellow $MSRC = http://www.mamicode.com/([regex]::Match($MSRC_URL, ‘(?i)MS\d+-\d+$‘)).Value Write-Host "MS number: [$MSRC]" -ForegroundColor Green if(!(Test-Path -LiteralPath "$PatchStoreTo\$MSRC")) { do { New-Item -Path "$PatchStoreTo\$MSRC" -ItemType Directory | Out-Null } while( $(if(!$?) { Write-Host ‘Failed to create MSRC folder‘ -ForegroundColor Red Start-Sleep 300 $true }) ) } Write-Host "Trying to capture KBs from MSRC URL" -ForegroundColor Yellow do { $MSContent = $null $MSContent = $WebClient.DownloadString($MSRC_URL) } while( $(if(!$?) { Write-Host ‘Failed to capture MSRC content‘ -ForegroundColor Red Start-Sleep 300 $true }) ) [regex]::Matches($MSContent, ‘(?i)<tr>[\s\S]+?<a href="http://www.mamicode.com/(http://www.microsoft.com/downloads/details.aspx/?FamilyID=[\w\-]+?)">[\s\S]+?\((\d{7})\)‘) | %{ Write-Host "KB: [$($_.Groups[2].Value)]" -NoNewline -ForegroundColor Green if($_.Value -imatch $ExcludeProducts) { Write-Host " --- Excluded: [$($Matches[0])]" -ForegroundColor Red } else { if($_.Value -notmatch $IncludeProducts) { Write-Host " --- Excluded: Not match [$IncludeProducts]" -ForegroundColor Red return } $KBNumber = "KB$($_.Groups[2].Value)" Write-Host "`nDownload URL: [$($_.Groups[1].Value)]" -ForegroundColor Gray<# if(!(Test-Path -Path "$MSRC\$KBNumber")) { do { New-Item -Name "$MSRC\$KBNumber" -ItemType Directory | Out-Null } while( $(if(!$?) { Write-Host ‘Failed to create KB folder‘ -ForegroundColor Red Start-Sleep 300 $true }) ) }#> do { $KBContent = $null $KBContent = $WebClient.DownloadString($_.Groups[1].Value) }while( $(if(!$?) { Write-Host ‘Failed to capture KB content‘ -ForegroundColor Red Start-Sleep 300 $true }) ) $KBConfirm = ([regex]::Match($KBContent, ‘(?i)href="http://www.mamicode.com/(confirmation.aspx/?id=\d+)"‘)).Groups[1].Value $KBConfirm = "http://www.microsoft.com/en-us/download/$KBConfirm" Write-Host "KB confirm URL: [$KBConfirm]" -ForegroundColor Gray do { $KBContent = $null $KBContent = $WebClient.DownloadString($KBConfirm) }while( $(if(!$?) { Write-Host ‘Failed to capture KB download content‘ -ForegroundColor Red Start-Sleep 300 $true }) ) $KBLinks = @() $KBLinks = [regex]::Matches($KBContent, ‘(?i)<a href="http://www.mamicode.com/(http://download.microsoft.com/download/.+?)".+?>Click here</span>‘) | %{ $_.Groups[1].Value } $KBLinks = @($KBLinks | Sort-Object -Unique) Write-Host "The KB contains updates: [$($KBLinks.Count)]" -ForegroundColor Green $KBLinks | %{ $FileName = $null $FileName = $_.Split(‘/‘)[-1] if($FileName -imatch $ExcludePatches) { Write-Host "Patch excluded: [$($Matches[0])]" -ForegroundColor Red return } $FilePath = $null $FilePath = "$MSRC\$FileName" Write-Host "Going to download file: [$FilePath]" -ForegroundColor Gray $FilePath = "$PatchStoreTo\$FilePath" if(Test-Path -Path $FilePath) { Write-Host ‘File already exists, skip!‘ -ForegroundColor Gray } else { do { $WebClient.DownloadFile($_, $FilePath) }while( $(if(!$?) { Write-Host ‘Download file failed!‘ -ForegroundColor Red Start-Sleep -Seconds 300 $true }) ) } } } }}
PS, about the proxy, WebClient class will use proxy settings on IE automatically, if want download patches via proxy, set IE to the right settings.
Use powershell script to download windows patches monthly