利用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="(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="(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设置好就行了。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。