利用powershell script每個月定期從microsoft download網站上抓補丁

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括號位置的改變。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章