在PowerShell中使用WebClient对象下载文件时,有没有办法监视下载进度?

16

我正在使用这样一个简单的命令行下载文件:

$webclient = New-Object -TypeName System.Net.WebClient
$webclient.DownloadFile("https://www.example.com/file", "C:/Local/Path/file")

问题是我想在下载过程中使用弹出窗口向用户显示消息,或者在shell中使用进度条。是否可能制作一个弹出框,在下载完成后自动消失,或者制作一个可以监视下载进度的进度条?


3
如果您可以升级至v3版本,您可以使用内置命令Invoke-WebRequest https://www.site.com/file -OutFile C:/Local/Path/file,它将自动显示进度。 - Keith Hill
5个回答

23
要在下载文件时显示进度条,请查看Jason Niver的博客文章: 使用Power Shell(带有进度)从互联网下载文件 基本上,您可以创建一个函数,仍然使用Web客户端功能,但包括一种捕获状态的方法。然后,您可以使用Write-Progress Power shell功能向用户显示状态。
function DownloadFile($url, $targetFile)

{

   $uri = New-Object "System.Uri" "$url"

   $request = [System.Net.HttpWebRequest]::Create($uri)

   $request.set_Timeout(15000) #15 second timeout

   $response = $request.GetResponse()

   $totalLength = [System.Math]::Floor($response.get_ContentLength()/1024)

   $responseStream = $response.GetResponseStream()

   $targetStream = New-Object -TypeName System.IO.FileStream -ArgumentList $targetFile, Create

   $buffer = new-object byte[] 10KB

   $count = $responseStream.Read($buffer,0,$buffer.length)

   $downloadedBytes = $count

   while ($count -gt 0)

   {

       $targetStream.Write($buffer, 0, $count)

       $count = $responseStream.Read($buffer,0,$buffer.length)

       $downloadedBytes = $downloadedBytes + $count

       Write-Progress -activity "Downloading file '$($url.split('/') | Select -Last 1)'" -status "Downloaded ($([System.Math]::Floor($downloadedBytes/1024))K of $($totalLength)K): " -PercentComplete ((([System.Math]::Floor($downloadedBytes/1024)) / $totalLength)  * 100)

   }

   Write-Progress -activity "Finished downloading file '$($url.split('/') | Select -Last 1)'"

   $targetStream.Flush()

   $targetStream.Close()

   $targetStream.Dispose()

   $responseStream.Dispose()

}

然后你只需要调用这个函数:
downloadFile "http://example.com/largefile.zip" "c:\temp\largefile.zip"

此外,这里还有一些来自docs.microsoft的powrshell 7的Write-Progress示例。 Write-Progress

我知道write-progress的使用方法;但是,我认为没有办法像那样递增一个计数器以显示下载进度。 - EGr
1
这个函数比直接下载要慢得多。但是,如果你将缓冲区大小改为1000KB,它会有巨大的改善。当缓冲区为10KB时,一个300MB的文件需要392秒才能下载完成。而当缓冲区为1000KB时,只需要43秒就可以完成下载。理想的大小可能因文件大小和连接速度而异,但对于大多数下载来说,10KB可能太小了。 - Grant
1
您的 Write-Progress 链接已失效。 - Gwasshoppa
1
@Gwasshoppa 谢谢!已更新到微软链接。https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-progress?view=powershell-7 - SoftwareCarpenter
1
Jason Niver博客链接失效。 - codeulike
@codeulike 更新了原始博客文章,也将微软存档的原始文章链接添加进去。谢谢。 - SoftwareCarpenter

11
在V2中,您只需使用BitsTransfer模块,例如:
Import-Module BitsTransfer
Start-BitsTransfer https://www.example.com/file C:/Local/Path/file

$urls | % { $name = ([uri]$_).Segments[-1]; Start-BitsTransfer $_ -DisplayName $name } - Markus Jarderot
3
请记住,BITS仅使用空闲的网络带宽。 - Ci3
请注意,如果您想在计划任务中使用脚本,则此方法不起作用 - Sebazzz
为什么如果在计划任务中运行就不能正常工作?如果您确定,是否找到了检查的方法?如果手动运行将使用一个bitstransfer运行的代码,但如果通过计划任务运行,则使用另一种方法? - karezza

3

当我在重写一些安装脚本时,我遇到了这个问题。这些方法都不是理想的方法。第一种方法在Windows PowerShell上存在性能问题,第二种方法需要系统更新,我们都知道在现实生活中它是如何工作的。

为了实现最佳性能,我将两种方法结合成了一个更智能、更通用的解决方案,遵循PowerShell最佳实践。

一些基准测试结果可以在这里找到。

function Get-File
{
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.Uri]
        $Uri,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo]
        $TargetFile,
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [Int32]
        $BufferSize = 1,
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('KB, MB')]
        [String]
        $BufferUnit = 'MB',
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('KB, MB')]
        [Int32]
        $Timeout = 10000
    )

    $useBitTransfer = $null -ne (Get-Module -Name BitsTransfer -ListAvailable) -and ($PSVersionTable.PSVersion.Major -le 5)

    if ($useBitTransfer)
    {
        Write-Information -MessageData 'Using a fallback BitTransfer method since you are running Windows PowerShell'
        Start-BitsTransfer -Source $Uri -Destination "$($TargetFile.FullName)"
    }
    else
    {
        $request = [System.Net.HttpWebRequest]::Create($Uri)
        $request.set_Timeout($Timeout) #15 second timeout
        $response = $request.GetResponse()
        $totalLength = [System.Math]::Floor($response.get_ContentLength() / 1024)
        $responseStream = $response.GetResponseStream()
        $targetStream = New-Object -TypeName ([System.IO.FileStream]) -ArgumentList "$($TargetFile.FullName)", Create
        switch ($BufferUnit)
        {
            'KB' { $BufferSize = $BufferSize * 1024 }
            'MB' { $BufferSize = $BufferSize * 1024 * 1024 }
            Default { $BufferSize = 1024 * 1024 }
        }
        Write-Verbose -Message "Buffer size: $BufferSize B ($($BufferSize/("1$BufferUnit")) $BufferUnit)"
        $buffer = New-Object byte[] $BufferSize
        $count = $responseStream.Read($buffer, 0, $buffer.length)
        $downloadedBytes = $count
        $downloadedFileName = $Uri -split '/' | Select-Object -Last 1
        while ($count -gt 0)
        {
            $targetStream.Write($buffer, 0, $count)
            $count = $responseStream.Read($buffer, 0, $buffer.length)
            $downloadedBytes = $downloadedBytes + $count
            Write-Progress -Activity "Downloading file '$downloadedFileName'" -Status "Downloaded ($([System.Math]::Floor($downloadedBytes/1024))K of $($totalLength)K): " -PercentComplete ((([System.Math]::Floor($downloadedBytes / 1024)) / $totalLength) * 100)
        }

        Write-Progress -Activity "Finished downloading file '$downloadedFileName'"

        $targetStream.Flush()
        $targetStream.Close()
        $targetStream.Dispose()
        $responseStream.Dispose()
    }
}

2

这是一个老问题,但我有一个优雅的解决方案。

$webClient = New-Object -TypeName System.Net.WebClient
$webClient.Credentials = $login
$task = $webClient.DownloadFileTaskAsync($uri, $PSScriptRoot + "\" + $asset)

Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -SourceIdentifier WebClient.DownloadProgressChanged | Out-Null

Start-Sleep -Seconds 3

while (!($task.IsCompleted)) {
    $EventData = Get-Event -SourceIdentifier WebClient.DownloadProgressChanged | Select-Object -ExpandProperty "SourceEventArgs" -Last 1

    $ReceivedData = ($EventData | Select-Object -ExpandProperty "BytesReceived")
    $TotalToReceive = ($EventData | Select-Object -ExpandProperty "TotalBytesToReceive")
    $TotalPercent = $EventData | Select-Object -ExpandProperty "ProgressPercentage"

    Start-Sleep -Seconds 2

    Write-Progress -Activity "Downloading File" -Status "Percent Complete: $($TotalPercent)%" -CurrentOperation "Downloaded $(convertFileSize -bytes $ReceivedData) / $(convertFileSize -bytes $TotalToReceive)" -PercentComplete $TotalPercent
}

Unregister-Event -SourceIdentifier WebClient.DownloadProgressChanged
$webClient.Dispose()

现在,您需要另一个用于转换文件大小的函数:

function convertFileSize {
    param(
        $bytes
    )

    if ($bytes -lt 1MB) {
        return "$([Math]::Round($bytes / 1KB, 2)) KB"
    }
    elseif ($bytes -lt 1GB) {
        return "$([Math]::Round($bytes / 1MB, 2)) MB"
    }
    elseif ($bytes -lt 1TB) {
        return "$([Math]::Round($bytes / 1GB, 2)) GB"
    }
}

我从Invoke-DownloadFile中提取了代码片段。感谢Tim Small。


0

你也可以使用它来获得更快的下载速度,以及一个令人印象深刻的进度条。

$fullurl='here put URL of the file which one you want to download'
$BASE_DIR= 'specify the path of the file to be stored '
$useBitTransfer = $null -ne (Get-Module -Name BitsTransfer -ListAvailable) -and ($PSVersionTable.PSVersion.Major -le 5)
if ($useBitTransfer)
    {
        Write-Information -MessageData 'Using a fallback BitTransfer method since you are running Windows PowerShell'
        Start-BitsTransfer -Source $fullUrl -Destination $BASE_DIR\kubescape.exe
        
    }
    else
    {
       Invoke-WebRequest -Uri $fullUrl -OutFile $BASE_DIR\kubescape.exe
    }

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Vikram Parimi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接