This artical will be published in English also: http://www.cnblogs.com/LarryAtCNBlog/p/4026695.html
本人所在的公司對於安全性要求較高,除了平時各種內網加密外網firewall之外,對於server所使用的OS也要求更新到最新的security級別的補丁。
但是樣本數量一多就總有些是打不上補丁的,這可能由於各種各樣如update配置錯誤,SCCM/WSUS抽風,加上第3方掃描補丁軟件的2X機制和security team的壓力,不得不把缺失的補丁一個個打上。這樣的話就導致了經常性的要把KB或MS號貼在google裏,然後找到鏈接,再找補丁下載。機器一多就亂的要死,重複下載就是經常發生的事~
於是爲了讓生活輕鬆一些就做了這樣一個script,從security RSS裏抓出MS號,然後從MS的鏈接裏抓出KB號,再抓出補丁下載鏈接把其下載到本地。所以只需要每月schedule一次或多次運行就可以把所有補丁放在一個固定的共享中了。microsoft每個月第二個星期二release當月的補丁(米國的週二,亞太大概就是週三)。
Security RSS: https://technet.microsoft.com/en-us/security/rss/bulletin
流程:讀取RSS內容 -> 腳本抓出MS號和鏈接 -> 循環讀MS號鏈接內容,取出所有KB -> 使用一些條件filter掉不要的KB,比如我只管server,我就不想下載非server的補丁 -> 腳本抓出KB的下載鏈接 -> 再從KB下載鏈接內容中抓取具體的下載路徑 -> 下載補丁到本地
下面一步步分解該腳本
$Url = 'https://technet.microsoft.com/en-us/security/rss/bulletin' $ExcludeProducts = 'lync|Itanium|for mac' $IncludeProducts = 'server' $ExcludePatches = '-IA64|Windows6\.0|-RT-|ServiceBusServer' $PatchStoreTo = '.\'
在上面的幾行中,定義了幾個變量,
$Url 當然就是RSS的鏈接;
$ExcludeProducts 就是抓出MS號網頁內容之後根據提供的正則表達式過濾掉不想要的product,比如lync,安騰cpu相關的補丁;
$IncludeProducts 就是經過上面exclude過濾後留下來的KB再過濾一次,而這次就是濾出包涵server信息的KB;
$ExcludePatches 是另一個過濾,在取到具體的補丁下載鏈接後對補丁的名字進行匹配,過濾掉安騰補丁之類的安裝包(因爲有些KB信息裏沒有直接寫明安騰cpu,所以加了該過濾從文件名來判斷);
$PatchStoreTo 就是把補丁存在哪個地方,當然,要有寫權限纔行。
$WebClient = New-Object System.Net.WebClient $WebClient.Encoding = [System.Text.Encoding]::UTF8
以上建立Webclient類並指定編碼
do { $RSSContent = $WebClient.DownloadString($Url) } while( $(if(!$?) { Write-Host 'Failed to get RSS' -ForegroundColor Red Start-Sleep -Seconds 600 $true }) )
上面就是從RSS鏈接中取到RSS的內容,如果不成功的話就等10分鐘再試一次。
([xml]$RSSContent).rss.channel.Item | Sort-Object link | %{...}
把RSS內容轉換爲xml對象,然後就可以方便的從xml中讀節點數據了
$MSRC_URL = $_.link Write-Host "Processing: [$MSRC_URL]" -ForegroundColor Yellow $MSRC = ([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號的鏈接放在了$MSRC_URL變量中,然後用黃字輸出到屏幕上,利用正則匹配到MS號存在$MSRC中,同樣輸出到屏幕。後面write-host這種輸出信息的就不描述了。隨後是創建以MS號爲名稱的文件夾,方便之後用來存放補丁文件。
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 }) )
上面的代碼則是抓取MS號的鏈接內容存在$MSContent中。
MS鏈接例如 https://technet.microsoft.com/en-us/library/security/MS14-063,MSContent就是該網頁的後臺代碼。
[regex]::Matches($MSContent, '(?i)<tr>[\s\S]+?<a href="(http://www.microsoft.com/downloads/details.aspx\?FamilyID=[\w\-]+?)">[\s\S]+?\((\d{7})\)') | %{...}
上面就是從MS的網頁內容中抓到具體的KB信息,如KB的鏈接,KB號。
它匹配到的內容如下,圖片中所有的內容都會被匹配出來。
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
上面的內容以KB的內容排除了$excludeProducts中描述的產品名稱,然後通過的KB又會經過$IncludeProducts的過濾,最終都通過的話,KB號存放於$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 }) )
以上代碼則是從已抓出的KB鏈接中取KB的網頁內容放於$KBContent中。
KB鏈接在MSContent中長這樣:http://www.microsoft.com/downloads/details.aspx?familyid=8a59fc6d-cbad-4905-842b-e5aa1fc6fedf
但是訪問它後會跳轉成:http://www.microsoft.com/en-us/download/details.aspx?id=44400
當然,這是Web server自動的,不需要我們手動在代碼中做什麼,該網頁並不包涵補丁下載信息,它只是讓我們確認一下語言,還有告訴我們KB具體信息而已,截圖如下。
因此,我們還要接着抓該網頁後臺的字符串信息,找到下載鏈接confirmation.aspx,當把鼠標放於"Download"按鈕上時,可以在狀態欄看到confirmation.aspx的鏈接。
$KBConfirm = ([regex]::Match($KBContent, '(?i)href="(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 }) )
以上是從$KBContent中抓到confirmation.aspx鏈接,並會抓出該confirmation.aspx中的內容,其實confirmation.aspx後面跟的id好像和KB details.aspx後面的id是一樣的,但是爲了保險一點,我還是選擇了從網頁內容中抓confirmation.aspx的信息。
$KBLinks = @() $KBLinks = [regex]::Matches($KBContent, '(?i)<a href="(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
在抓到KB的confirmation.aspx內容之後,然後從內容中用正則匹配具體的下載鏈接,最後做一個排序並去除重複的條目,完成後$KBLinks中就包涵了該KB中所有補丁的下載鏈接。
當在瀏覽器中打開confirmation.aspx後其實會自動彈出下載,但這是遊覽器的行爲,代碼是不會自動下載的,但是我們可以看到一個"click here"就是下載鏈接了。
要做的也就是分析它後面的網頁代碼了,依然還是用正則。
$KBLinks | %{ $FileName = $null $FileName = $_.Split('/')[-1] if($FileName -imatch $ExcludePatches) { Write-Host "Patch excluded: [$($Matches[0])]" -ForegroundColor Red return }
既然有了補丁的具體下載鏈接,剩下的就是下載了,但在下載之前又對補丁的名稱做了一次過濾,之前也提到了KB的信息有時候是不完整的,因此要做這裏的過濾。
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 }) ) }
上面就是下載補丁的代碼了,當然,如果補丁已經存在,腳本不會重複下載。
以上就是腳本的分析和介紹了,最後貼張運行圖還有完整的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 = '.\' $WebClient = New-Object System.Net.WebClient $WebClient.Encoding = [System.Text.Encoding]::UTF8 do { $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 = ([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="(https?://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="(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://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 }) ) } } } } }
附,關於proxy,WebClient類會自動使用IE裏所設置的proxy,所以如果要用proxy的話,把IE設置好就行了。
2015-03-02:抓取KB號的正則表達式更新,因爲加入了原本未考慮到的https鏈接及後臺html括號位置的改變。