首页 > 代码库 > 利用powershell script每个月定期从microsoft download网站上抓补丁

利用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.mamicode.com/"(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=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                })            )

以上是从$KBContent中抓到confirmation.aspx链接,并会抓出该confirmation.aspx中的内容,其实confirmation.aspx后面跟的id好像和KB details.aspx后面的id是一样的,但是为了保险一点,我还是选择了从网页内容中抓confirmation.aspx的信息。

 

            $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

在抓到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]::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 = ([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                        })                    )                }            }        }    }}

 

附,关于proxy,WebClient类会自动使用IE里所设置的proxy,所以如果要用proxy的话,把IE设置好就行了。

利用powershell script每个月定期从microsoft download网站上抓补丁