PowerShell小技巧之实现文件下载(类wget)
对Linux熟悉的读者可能会对Linux通过wget下载文件有印象,这个工具功能很强大,在.NET环境下提到下载文件大多数人熟悉的是通过System.Net.WebClient进行下载,这个程序集能实现下载的功能,但是有缺陷,如果碰上类似于…/scripts/?dl=417这类的下载链接将无法正确识别文件名,下载的文件通常会被命名为dl=417这样古怪的名字,其实对应的文件名是在访问这个链接返回结果的HTTP头中包含的。事实上微软也提供了避免这些缺陷的程序集System.Net.HttpWebRequest和HttpWebResponse,本文将会使用这两个程序集来实现PowerShell版wget的功能。
代码不怎么复杂,基本上就是创建HttpWebRequest对象,设定UserAgent和CookieContainer以免在遇到设置防盗链的服务器出现无法下载的情况。然后通过HttpWebRequest对象的GetResponse()方法从http头中获取目标文件的大小以及文件名,以便能在下载到文件时提示当前下载进度,在下载完文件后,列出当前目录下对应的文件。代码不复杂,有任何疑问的读者可以留言给我,进行交流,下面上代码:
=====文件名:Get-WebFile.ps1===== functionGet-WebFile{ <#Author:fuhj(powershell#live.cn,http://fuhaijun.com) Downloadsafileorpagefromtheweb .Example Get-WebFilehttp://mirrors.cnnic.cn/apache/couchdb/binary/win/1.4.0/setup-couchdb-1.4.0_R16B01.exe Downloadsthelatestversionofthisfiletothecurrentdirectory #>
[CmdletBinding(DefaultParameterSetName="NoCredentials")] param( # TheURLofthefile/pagetodownload [Parameter(Mandatory=$true,Position=0)] [System.Uri][Alias("Url")]$Uri#=(Read-Host"TheURLtodownload") , # APathtosavethedownloadedcontent. [string]$FileName , # Leavethefileunblockedinsteadofblocked [Switch]$Unblocked , # Ratherthansavingthedownloadedcontenttoafile,outputit. # Thisisfortextdocumentslikewebpagesandrssfeeds,andallowsyoutoavoidtemporarilycachingthetextinafile. [switch]$Passthru , # SupressestheWrite-Progressduringdownload [switch]$Quiet , # Thenameofavariabletostorethesession(cookies)in [String]$SessionVariableName , # TexttoincludeatthefrontoftheUserAgentstring [string]$UserAgent="PowerShellWget/$(1.0)" )
Write-Verbose"Downloading'$Uri'" $EAP,$ErrorActionPreference=$ErrorActionPreference,"Stop" $request=[System.Net.HttpWebRequest]::Create($Uri); $ErrorActionPreference=$EAP $request.UserAgent=$( "{0}(PowerShell{1};.NETCLR{2};{3};http://fuhaijun.com)"-f$UserAgent, $(if($Host.Version){$Host.Version}else{"1.0"}), [Environment]::Version, [Environment]::OSVersion.ToString().Replace("MicrosoftWindows","Win") )
$Cookies=New-ObjectSystem.Net.CookieContainer if($SessionVariableName){ $Cookies=Get-Variable$SessionVariableName-Scope1 } $request.CookieContainer=$Cookies if($SessionVariableName){ Set-Variable$SessionVariableName-Scope1-Value$Cookies }
try{ $res=$request.GetResponse(); }catch[System.Net.WebException]{ Write-Error$_.Exception-CategoryResourceUnavailable return }catch{ Write-Error$_.Exception-CategoryNotImplemented return }
if((Test-Pathvariable:res)-and$res.StatusCode-eq200){ if($fileName-and!(Split-Path$fileName)){ $fileName=Join-Path(Convert-Path(Get-Location-PSProvider"FileSystem"))$fileName } elseif((!$Passthru-and!$fileName)-or($fileName-and(Test-Path-PathType"Container"$fileName))) { [string]$fileName=([regex]'(?i)filename=(.*)$').Match($res.Headers["Content-Disposition"]).Groups[1].Value $fileName=$fileName.trim("\/""'")
$ofs="" $fileName=[Regex]::Replace($fileName,"[$([Regex]::Escape(""$([System.IO.Path]::GetInvalidPathChars())$([IO.Path]::AltDirectorySeparatorChar)$([IO.Path]::DirectorySeparatorChar)""))]","_") $ofs=""
if(!$fileName){ $fileName=$res.ResponseUri.Segments[-1] $fileName=$fileName.trim("\/") if(!$fileName){ $fileName=Read-Host"Pleaseprovideafilename" } $fileName=$fileName.trim("\/") if(!([IO.FileInfo]$fileName).Extension){ $fileName=$fileName+"."+$res.ContentType.Split(";")[0].Split("/")[1] } } $fileName=Join-Path(Convert-Path(Get-Location-PSProvider"FileSystem"))$fileName } if($Passthru){ $encoding=[System.Text.Encoding]::GetEncoding($res.CharacterSet) [string]$output="" }
[int]$goal=$res.ContentLength $reader=$res.GetResponseStream() if($fileName){ try{ $writer=new-objectSystem.IO.FileStream$fileName,"Create" }catch{ Write-Error$_.Exception-CategoryWriteError return } } [byte[]]$buffer=new-objectbyte[]4096 [int]$total=[int]$count=0 do { $count=$reader.Read($buffer,0,$buffer.Length); if($fileName){ $writer.Write($buffer,0,$count); } if($Passthru){ $output+=$encoding.GetString($buffer,0,$count) }elseif(!$quiet){ $total+=$count if($goal-gt0){ Write-Progress"Downloading$Uri""Saving$totalof$goal"-id0-percentComplete(($total/$goal)*100) }else{ Write-Progress"Downloading$Uri""Saving$totalbytes..."-id0 } } }while($count-gt0)
$reader.Close() if($fileName){ $writer.Flush() $writer.Close() } if($Passthru){ $output } } if(Test-Pathvariable:res){$res.Close();} if($fileName){ ls$fileName } }