1. PowerShell下载文件为什么需要优化如果你经常用PowerShell下载大文件可能会发现默认的Invoke-WebRequest命令速度慢得让人抓狂。我最初以为是网络问题后来才发现罪魁祸首竟然是进度条显示功能。每次下载文件时PowerShell都会实时计算并更新进度条这个看似贴心的功能实际上会严重拖慢下载速度。举个例子我最近需要从微软官网下载一个500MB的安装包用默认方式花了整整3分钟。但当我禁用进度条后同样的文件只用了15秒这种性能差异在自动化脚本中尤为明显特别是需要批量下载多个文件时优化后的方法能节省大量时间。2. 禁用进度条提升下载速度2.1 进度条的性能影响原理PowerShell的进度条功能会不断计算已下载数据量、剩余时间和传输速率这些计算操作会占用CPU资源并增加I/O等待时间。对于大文件下载这种实时更新会导致频繁的上下文切换显著降低整体性能。更糟糕的是进度条更新是同步操作意味着下载线程必须等待进度条渲染完成才能继续传输数据。这种设计在小文件下载时影响不大但当文件体积超过100MB时性能损耗就会变得非常明显。2.2 禁用进度条的具体方法禁用进度条非常简单只需要设置$ProgressPreference变量# 禁用进度条显示 $ProgressPreference SilentlyContinue # 下载文件示例 $url https://example.com/largefile.zip $output C:\Downloads\largefile.zip Invoke-WebRequest -Uri $url -OutFile $output -UseBasicParsing # 恢复默认设置可选 $ProgressPreference Continue这个设置会影响当前会话中所有命令的进度显示。如果只想临时禁用某个命令的进度条可以使用局部作用域 { $ProgressPreference SilentlyContinue Invoke-WebRequest -Uri $url -OutFile $output }2.3 实测性能对比我用微软官方的Admin Center安装包(约300MB)做了测试方法平均耗时(秒)CPU占用率默认Invoke-WebRequest18025%禁用进度条48%WebClient57%测试环境Windows 10 Proi5-8250U100Mbps宽带。可以看到禁用进度条后速度提升了45倍3. 使用WebClient替代方案3.1 WebClient的基本用法.NET的System.Net.WebClient类是更底层的下载方案它不包含进度显示等额外功能因此性能更好$url https://example.com/largefile.zip $output C:\Downloads\largefile.zip # 创建WebClient实例 $webClient New-Object System.Net.WebClient # 下载文件 $webClient.DownloadFile($url, $output) # 释放资源 $webClient.Dispose()对于需要多次下载的场景可以复用WebClient实例$webClient New-Object System.Net.WebClient try { $webClient.DownloadFile($url1, $output1) $webClient.DownloadFile($url2, $output2) } finally { $webClient.Dispose() }3.2 WebClient的高级配置WebClient支持更多自定义选项# 设置超时时间毫秒 $webClient.Timeout 30000 # 设置User-Agent $webClient.Headers.Add(user-agent, Mozilla/5.0) # 添加自定义请求头 $webClient.Headers.Add(X-Custom-Header, Value) # 使用代理 $webClient.Proxy [System.Net.WebRequest]::GetSystemWebProxy() $webClient.Proxy.Credentials [System.Net.CredentialCache]::DefaultCredentials3.3 异步下载实现对于需要同时下载多个文件的情况可以使用异步方法$urls ( https://example.com/file1.zip, https://example.com/file2.zip ) $jobs () foreach ($url in $urls) { $fileName [System.IO.Path]::GetFileName($url) $outputPath C:\Downloads\$fileName $webClient New-Object System.Net.WebClient $webClient.DownloadFileAsync([Uri]$url, $outputPath) $jobs $webClient } # 等待所有下载完成 while ($jobs | Where-Object { $_.IsBusy }) { Start-Sleep -Milliseconds 100 } $jobs | ForEach-Object { $_.Dispose() }4. 两种方法的深度对比4.1 功能特性对比特性Invoke-WebRequestWebClient进度显示支持可禁用不支持HTTP方法完整支持基础支持响应处理自动解析HTML仅原始数据证书验证严格可自定义代理支持是是超时设置通过参数需手动配置异步操作不支持支持4.2 性能优化建议根据我的经验选择方案时应考虑以下因素小文件下载10MB两种方法差异不大可以使用Invoke-WebRequest保持代码简洁中等文件10-100MB禁用进度条的Invoke-WebRequest是最佳选择大文件100MB优先使用WebClient特别是需要批量下载时需要进度反馈可以结合使用WebClient和事件回调实现自定义进度条$webClient New-Object System.Net.WebClient # 注册进度事件 Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action { param($sender, $e) Write-Progress -Activity 下载中 -Status $($e.ProgressPercentage)% -PercentComplete $e.ProgressPercentage } $webClient.DownloadFileAsync($url, $output) # 取消注册 Unregister-Event -SourceIdentifier $event.Id4.3 异常处理最佳实践无论使用哪种方法都应该添加异常处理try { $ProgressPreference SilentlyContinue Invoke-WebRequest -Uri $url -OutFile $output -ErrorAction Stop } catch [System.Net.WebException] { Write-Warning 网络错误: $($_.Exception.Message) if ($_.Exception.Response.StatusCode -eq 404) { Write-Error 文件不存在 } } catch { Write-Error 未知错误: $_ } finally { $ProgressPreference Continue }对于WebClienttry { $webClient New-Object System.Net.WebClient $webClient.DownloadFile($url, $output) } catch [System.Net.WebException] { Write-Warning 下载失败: $($_.Exception.Message) } catch [System.IO.IOException] { Write-Warning 文件写入错误: $_ } finally { if ($webClient -ne $null) { $webClient.Dispose() } }5. 实际应用场景示例5.1 自动化部署脚本在CI/CD流程中经常需要下载构建产物或依赖包。这是我常用的下载函数function Download-File { param( [string]$Url, [string]$Path, [switch]$UseWebClient ) $dir [System.IO.Path]::GetDirectoryName($Path) if (![System.IO.Directory]::Exists($dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } if ($UseWebClient) { $wc New-Object System.Net.WebClient try { $wc.DownloadFile($Url, $Path) } finally { $wc.Dispose() } } else { $ProgressPreference SilentlyContinue try { Invoke-WebRequest -Uri $Url -OutFile $Path -UseBasicParsing } finally { $ProgressPreference Continue } } if (Test-Path $Path) { Get-Item $Path } else { throw 下载失败: $Url } }5.2 批量下载管理器结合CSV文件实现批量下载# downloads.csv # Url,SavePath # http://example.com/file1.zip,C:\temp\file1.zip # http://example.com/file2.zip,C:\temp\file2.zip $downloads Import-Csv .\downloads.csv $results () foreach ($item in $downloads) { try { $stopwatch [System.Diagnostics.Stopwatch]::StartNew() Download-File -Url $item.Url -Path $item.SavePath -UseWebClient $results [PSCustomObject]{ FileName $item.SavePath Status Success Elapsed $stopwatch.Elapsed.ToString() Size (Get-Item $item.SavePath).Length / 1MB } } catch { $results [PSCustomObject]{ FileName $item.SavePath Status Failed Error $_.Exception.Message } } } $results | Format-Table -AutoSize5.3 断点续传实现虽然PowerShell原生不支持断点续传但可以通过检查文件大小实现简单续传function Download-FileWithResume { param( [string]$Url, [string]$Path ) $tempFile $Path.partial $webRequest [System.Net.HttpWebRequest]::Create($Url) if (Test-Path $tempFile) { $existingSize (Get-Item $tempFile).Length $webRequest.AddRange($existingSize) $stream [System.IO.File]::OpenWrite($tempFile) $stream.Seek(0, [System.IO.SeekOrigin]::End) | Out-Null } else { $stream [System.IO.File]::Create($tempFile) } try { $response $webRequest.GetResponse() $responseStream $response.GetResponseStream() $buffer New-Object byte[] 1MB $totalRead 0 $totalSize $existingSize $response.ContentLength while (($read $responseStream.Read($buffer, 0, $buffer.Length)) -gt 0) { $stream.Write($buffer, 0, $read) $totalRead $read $percent [Math]::Round(($totalRead / $totalSize) * 100) Write-Progress -Activity 下载中 -Status $percent% -PercentComplete $percent } } finally { $stream.Close() if ($response -ne $null) { $response.Close() } if (Test-Path $tempFile) { Move-Item $tempFile $Path -Force } } }6. 其他优化技巧6.1 使用UseBasicParsing参数Invoke-WebRequest默认会调用IE引擎解析HTML这会增加额外开销。如果只需要下载文件而不需要解析内容应该添加-UseBasicParsing参数Invoke-WebRequest -Uri $url -OutFile $output -UseBasicParsing6.2 调整缓冲区大小对于WebClient可以通过继承类来调整缓冲区大小class FastWebClient : System.Net.WebClient { protected override [System.Net.WebRequest] GetWebRequest([System.Uri] $address) { $request ([System.Net.WebClient]$this).GetWebRequest($address) $request.Timeout 30000 if ($request -is [System.Net.HttpWebRequest]) { $request.ReadWriteTimeout 60000 $request.AllowWriteStreamBuffering $false } return $request } } $webClient [FastWebClient]::new() $webClient.DownloadFile($url, $output) $webClient.Dispose()6.3 并行下载优化对于多个大文件下载可以使用PowerShell工作流实现并行下载workflow Download-Files { param( [string[]]$Urls, [string]$OutputFolder ) foreach -parallel ($url in $Urls) { $fileName [System.IO.Path]::GetFileName($url) $outputPath Join-Path $OutputFolder $fileName $webClient New-Object System.Net.WebClient try { $webClient.DownloadFile($url, $outputPath) 下载完成: $fileName } catch { 下载失败: $fileName - $_ } finally { $webClient.Dispose() } } } Download-Files -Urls (http://example.com/file1.zip, http://example.com/file2.zip) -OutputFolder C:\Downloads7. 常见问题排查7.1 SSL/TLS证书错误遇到证书错误时可以临时忽略验证仅限测试环境# 方法1对所有请求禁用证书验证 [System.Net.ServicePointManager]::ServerCertificateValidationCallback { $true } # 方法2仅对当前会话 $webClient New-Object System.Net.WebClient $webClient.DownloadFile($url, $output) [System.Net.ServicePointManager]::ServerCertificateValidationCallback $null7.2 下载速度不稳定如果下载速度波动较大可以尝试限制带宽避免占用全部网络资源$webClient New-Object System.Net.WebClient $webClient.DownloadFile($url, $output) $webClient.Dispose()使用分段下载$chunkSize 1MB $totalSize (Invoke-WebRequest -Uri $url -Method Head).Headers.Content-Length $chunks [Math]::Ceiling($totalSize / $chunkSize) $fileStream [System.IO.File]::Create($output) try { for ($i0; $i -lt $chunks; $i) { $start $i * $chunkSize $end ($start $chunkSize) - 1 if ($end -ge $totalSize) { $end $totalSize - 1 } $webRequest [System.Net.HttpWebRequest]::Create($url) $webRequest.AddRange($start, $end) $response $webRequest.GetResponse() $stream $response.GetResponseStream() $buffer New-Object byte[] 64KB while (($read $stream.Read($buffer, 0, $buffer.Length)) -gt 0) { $fileStream.Write($buffer, 0, $read) } $stream.Close() } } finally { $fileStream.Close() }7.3 内存占用过高下载特大文件时可以使用流式处理避免内存溢出$request [System.Net.HttpWebRequest]::Create($url) $response $request.GetResponse() $stream $response.GetResponseStream() $fileStream [System.IO.File]::Create($output) try { $buffer New-Object byte[] 1MB while (($read $stream.Read($buffer, 0, $buffer.Length)) -gt 0) { $fileStream.Write($buffer, 0, $read) } } finally { $fileStream.Close() $stream.Close() }8. 终极解决方案混合方法结合两种方法的优点我开发了一个智能下载函数它会根据文件大小自动选择最佳下载方式function Get-RemoteFile { param( [Parameter(Mandatory$true)] [string]$Url, [Parameter(Mandatory$true)] [string]$Destination, [int]$Threshold 50MB, [switch]$ForceWebClient, [switch]$ForceInvokeWebRequest ) # 获取文件信息 try { $request [System.Net.HttpWebRequest]::Create($Url) $request.Method HEAD $response $request.GetResponse() $fileSize $response.ContentLength $fileName [System.IO.Path]::GetFileName( [System.Web.HttpUtility]::UrlDecode($response.ResponseUri.AbsolutePath) ) if ([string]::IsNullOrEmpty($fileName)) { $fileName [System.IO.Path]::GetRandomFileName() } $finalPath if ([System.IO.Path]::IsPathRooted($Destination)) { $Destination } else { Join-Path (Get-Location).Path $Destination } if ([System.IO.Directory]::Exists($finalPath)) { $finalPath Join-Path $finalPath $fileName } $response.Close() } catch { throw 无法获取远程文件信息: $_ } # 选择下载方法 $useWebClient $ForceWebClient -or ($fileSize -gt $Threshold -and -not $ForceInvokeWebRequest) # 创建目录 $directory [System.IO.Path]::GetDirectoryName($finalPath) if (-not [System.IO.Directory]::Exists($directory)) { New-Item -ItemType Directory -Path $directory -Force | Out-Null } # 执行下载 if ($useWebClient) { Write-Verbose 使用WebClient下载大文件 ($([Math]::Round($fileSize/1MB)) MB) $webClient New-Object System.Net.WebClient try { $webClient.DownloadFile($Url, $finalPath) } finally { $webClient.Dispose() } } else { Write-Verbose 使用Invoke-WebRequest下载小文件 $ProgressPreference SilentlyContinue try { Invoke-WebRequest -Uri $Url -OutFile $finalPath -UseBasicParsing } finally { $ProgressPreference Continue } } if (Test-Path $finalPath) { Get-Item $finalPath } else { throw 下载失败: $Url } }这个函数会自动处理文件名解析、目录创建、方法选择等问题是我日常工作中最常用的下载工具。你可以通过-Threshold参数调整大小阈值默认为50MB或者用-ForceWebClient/-ForceInvokeWebRequest强制指定下载方法。