如何复现Invoke-WebRequest在Win11 24H2/PowerShell5.1中的部分下载问题?
关于PowerShell Invoke-WebRequest部分下载问题的解答
一、如何复现部分下载场景
要复现生成不完整文件的情况,可以尝试以下操作:
- 模拟服务器中途断开:用本地HTTP服务器(比如Python的
http.server)托管大文件,下载过程中强行终止服务器进程。PowerShell 5.1的Invoke-WebRequest可能不会检测到异常,直接保留已下载的部分内容。 - 限速后中断传输:通过Windows QoS策略限制下载带宽至极低,在下载进行到一半时手动断开网络。部分场景下,客户端会将已接收数据写入文件,且不会触发错误。
- 构造不完整响应接口:编写简单Web接口,仅返回文件前N字节就关闭连接,
Invoke-WebRequest会将已接收内容写入OutFile且不抛出错误。
二、Invoke-WebRequest的错误行为与错误码
在PowerShell 5.1中,Invoke-WebRequest的错误抛出逻辑分场景:
- 网络中断/超时:会抛出终止错误,对应WinHTTP错误码,比如
ERROR_WINHTTP_TIMEOUT(12002)、ERROR_WINHTTP_CANNOT_CONNECT(12029)等。 - 服务器中途断开返回不完整数据:不会抛出终止错误,会将已接收数据写入文件,响应对象
StatusCode可能仍为200,但ContentLength与实际文件大小不匹配。 - HTTP错误(4xx/5xx):会抛出终止错误,可通过
$_.Exception.Response.StatusCode获取对应HTTP状态码。
三、避免此类问题的改进方案
针对原脚本仅检查文件存在的缺陷,可从以下维度优化:
1. 校验文件大小
下载后对比本地文件大小与服务器返回的Content-Length:
$ProgressPreference = 'SilentlyContinue' $tries = 0 $targetPath = "C:\Recovery\OEM\Agent.msi" $uri = "https://abc.xyz.com/path/Agent.msi" do { if ($tries -gt 0) { Start-Sleep -Seconds ($tries*5) } try { # 获取响应头确认预期文件大小 $response = Invoke-WebRequest -Uri $uri -Method Head -ErrorAction Stop $expectedSize = [long]$response.Headers['Content-Length'] # 执行下载 Invoke-WebRequest -Uri $uri -OutFile $targetPath -ErrorAction Stop # 校验文件大小 $actualSize = (Get-Item $targetPath).Length if ($actualSize -eq $expectedSize) { break # 下载成功,退出循环 } else { Write-Warning "文件大小不匹配:预期$expectedSize字节,实际$actualSize字节" Remove-Item $targetPath -Force # 删除不完整文件 } } catch { Write-Warning "下载失败: $_" # 清理可能存在的不完整文件 if (Test-Path $targetPath) { Remove-Item $targetPath -Force } } $tries++ } while ($tries -lt 5)
2. 使用哈希校验
如果服务器提供文件哈希值(如SHA256),下载后计算本地文件哈希对比:
# 假设服务器提供的SHA256哈希 $expectedHash = "示例哈希值" # 计算本地文件哈希 $actualHash = (Get-FileHash -Path $targetPath -Algorithm SHA256).Hash if ($actualHash -ne $expectedHash) { Write-Warning "文件校验失败" Remove-Item $targetPath -Force }
3. 强制错误终止行为
在Invoke-WebRequest命令中添加-ErrorAction Stop,确保大部分网络错误进入catch块,避免静默失败。
4. 改用.NET类实现下载
PowerShell 5.1的Invoke-WebRequest对不完整响应处理较宽松,可直接使用.NET的WebClient或HttpClient类,检测更严谨:
$client = New-Object System.Net.WebClient try { $client.DownloadFile($uri, $targetPath) } catch { Write-Warning "下载失败: $_" if (Test-Path $targetPath) { Remove-Item $targetPath -Force } } finally { $client.Dispose() }
内容的提问来源于stack exchange,提问作者Emilio Garcia
相关产品推荐
相关产品推荐

