1. 项目概述:为什么需要掌握PowerShell下载文件?
在日常的Windows系统管理、自动化运维,甚至是临时的文件抓取任务中,我们常常会遇到需要从网络下载文件的情况。对于习惯了图形界面的用户,第一反应可能是打开浏览器,找到链接,点击下载。但对于需要批量处理、集成到脚本中,或者在无图形界面的服务器核心(Server Core)环境下工作的系统管理员和开发者来说,掌握命令行下的文件下载能力,是一项不可或缺的核心技能。
PowerShell,作为Windows平台上功能强大的脚本语言和命令行外壳,内置了多种用于HTTP/HTTPS/FTP文件下载的cmdlet和.NET类。它们各有侧重,有的简单直接,有的功能丰富,有的则专为后台和大文件传输优化。理解并熟练运用这些方法,能让你编写的脚本更健壮、更高效,也能在远程管理或自动化部署中游刃有余。本文将深入拆解PowerShell中下载文件的三种主流方法:Invoke-WebRequest、System.Net.WebClient以及BitsTransfer模块,并结合大量实际场景,为你提供从入门到精通的详细指南。
2. 三种核心方法深度解析与选型指南
面对一个下载任务,选择哪种方法并非随意为之。每种方法背后都有其设计哲学和适用场景。盲目选择可能会导致脚本效率低下、功能受限,甚至在不支持的环境中运行失败。下面我们从设计初衷、核心特性与适用场景三个维度,对它们进行彻底解构。
2.1 Invoke-WebRequest:面向Web的“瑞士军刀”
Invoke-WebRequest(别名iwr、curl、wget)是PowerShell 3.0引入的cmdlet,它被设计成一个全面的HTTP客户端。其核心思想是模拟浏览器行为,不仅能获取文件内容,还能解析HTML、处理表单、管理Cookie和会话。
核心特性:
- 丰富的响应对象:它返回一个包含状态码、头信息、原始内容流、已解析的HTML DOM等丰富属性的对象,让你能对HTTP响应进行精细操作。
- 内置的进度显示:默认情况下,下载大文件时会显示进度条,对用户非常友好。
- 强大的参数集:支持
-Headers自定义请求头、-Method指定HTTP方法、-Body提交数据、-SessionVariable维持会话状态、-Proxy设置代理等。 - 内容处理灵活:可以通过管道将内容传递给其他cmdlet进行处理,或者直接保存到文件。
适用场景:
- 需要解析网页内容、抓取特定数据(如提取链接、文本)的场景。
- 需要与需要Cookie或会话保持的Web API进行交互。
- 下载文件的同时,需要检查HTTP状态码、响应头等元信息。
- 在交互式命令行中执行一次性下载,并希望看到进度反馈。
选型理由:当你需要与Web进行“对话”,而不仅仅是“搬运”数据时,Invoke-WebRequest是最佳选择。它提供了HTTP协议层面的完整控制。
2.2 System.Net.WebClient:轻量高效的.NET经典
System.Net.WebClient是一个.NET Framework中的类,在PowerShell中可以直接通过New-Object创建实例使用。它的设计更偏向于一个简单、直接的网络数据下载器。
核心特性:
- 接口简洁直观:主要方法如
.DownloadFile()用于下载到文件,.DownloadString()用于下载为文本字符串,.DownloadData()用于下载为字节数组,功能划分清晰。 - 同步与异步支持:除了同步方法,还提供了
.DownloadFileAsync()等异步方法,允许在下载时不阻塞主线程。 - 可配置性:可以通过
.Headers属性添加请求头,通过.Proxy属性设置代理,通过.Credentials属性处理认证。 - 无内置进度条:默认不显示下载进度,需要自己通过事件(如
.DownloadProgressChanged)来实现。
适用场景:
- 脚本中需要快速、简洁地下载一个已知URL的文件,无需复杂交互。
- 需要将下载内容直接作为字符串或字节数组在内存中处理,而不先落盘。
- 在PowerShell 2.0等旧版本环境中运行(
Invoke-WebRequest需要3.0+)。 - 对执行速度有轻微要求,
WebClient在某些简单场景下开销略低于Invoke-WebRequest。
选型理由:如果你追求的是“快、准、稳”地将一个远程文件弄到本地,且不需要网页解析等高级HTTP功能,WebClient的简洁API会让你感到非常舒适。它是许多传统脚本中的主力下载工具。
2.3 BitsTransfer:后台智能传输服务
BitsTransfer指的是后台智能传输服务,它是一个Windows系统服务,专注于优化文件传输。在PowerShell中,我们通过Import-Module BitsTransfer引入相关cmdlet,主要是Start-BitsTransfer。
核心特性:
- 后台与断点续传:这是其最大亮点。传输由系统服务管理,即使关闭PowerShell窗口或重启电脑,任务也可在后台继续或恢复。
- 网络感知与带宽节制:BITS能感知网络成本(如按流量计费的网络),并自动调整传输行为。可以设置传输优先级和带宽限制。
- 专为大文件与不稳定网络设计:非常适合下载操作系统ISO镜像、大型安装包等,在网络中断后能从中断处继续,无需重头开始。
- 需要管理传输任务:下载任务有状态(传输中、已暂停、已完成等),需要特定的cmdlet(如
Get-BitsTransfer,Remove-BitsTransfer)进行查询和管理。
适用场景:
- 下载体积巨大(如数GB)的文件。
- 网络环境不稳定,存在中断风险。
- 希望下载任务在后台静默进行,不干扰前台工作,且能跨会话持久化。
- 在无人值守的自动化任务(如定时更新脚本)中下载关键文件。
选型理由:当你的下载任务关乎“可靠性”和“资源管理”时,BitsTransfer是无可争议的王者。它牺牲了一点使用的简便性,换来了企业级传输的稳健性。
注意:
BitsTransfer服务默认在Windows系统上运行,但在某些精简版或严格锁定的系统上可能被禁用。如果你的脚本需要广泛分发,这一点需要确认。
3. 核心细节解析与实操要点
了解了宏观选型,我们深入到每种方法的实操细节中。这里不仅有“怎么做”,更有“为什么这么做”以及“怎么做更好”。
3.1 Invoke-WebRequest 的进阶用法与坑位指南
Invoke-WebRequest功能强大,但参数繁多,使用不当容易踩坑。
基础下载与保存:最直接的用法是使用-OutFile参数。
# 基本下载 Invoke-WebRequest -Uri "https://example.com/file.zip" -OutFile "C:\Downloads\file.zip" # 使用别名 iwr (更简洁) iwr "https://example.com/file.zip" -OutFile ".\file.zip"处理需要认证或特殊头信息的网站:有些网站会检查User-Agent,或者需要登录后的Cookie。
# 自定义User-Agent,模拟浏览器访问 $headers = @{ "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } iwr -Uri "https://some-site.com/data.csv" -Headers $headers -OutFile "data.csv" # 使用会话保存Cookie(例如登录后下载) $loginResponse = iwr -Uri "https://example.com/login" -Method Post -Body @{username='user'; password='pass'} -SessionVariable 'mySession' # $mySession 变量现在包含了登录后的会话Cookie iwr -Uri "https://example.com/dashboard/report.pdf" -WebSession $mySession -OutFile "report.pdf"跳过SSL/TLS证书验证(谨慎使用):在开发测试环境,遇到自签名证书错误时,可以临时跳过检查。生产环境强烈不建议使用。
# 该方法仅影响当前PowerShell会话,且会降低安全性 [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } iwr -Uri "https://internal-test.com/file.exe" -OutFile "file.exe" # 操作完成后,建议恢复回调 [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null实操心得与避坑:
- 进度条与静默模式:默认进度条在自动化脚本中可能会产生多余输出。使用
-UseBasicParsing参数可以禁用进度条和HTML解析,提升速度并减少输出。在PowerShell 6.0 (Core) 及以上版本,-UseBasicParsing已废弃,默认行为就是基础解析。# PowerShell 5.1 及以下,用于静默下载 iwr -Uri $url -OutFile $localPath -UseBasicParsing - 错误处理:默认情况下,当HTTP状态码为4xx或5xx时,
Invoke-WebRequest会抛出异常并终止脚本。使用try-catch块来优雅处理。try { $response = iwr -Uri $url -ErrorAction Stop # 处理成功响应 } catch [System.Net.WebException] { Write-Warning "下载失败: $($_.Exception.Message)" # 可以检查 $_.Exception.Response.StatusCode 获取具体状态码 } - 大文件内存问题:
Invoke-WebRequest会先将响应内容完整加载到内存,再写入-OutFile。对于超大文件,这可能消耗大量内存。虽然它支持分块传输编码,但对于极端情况,BitsTransfer是更好的选择。
3.2 WebClient 的同步与异步之道
WebClient的使用更像是在调用一个封装好的库函数。
同步下载:这是最常用的方式,代码会阻塞直到下载完成。
# 创建WebClient对象 $webClient = New-Object System.Net.WebClient # 下载到文件 $webClient.DownloadFile("https://example.com/picture.jpg", "C:\Downloads\picture.jpg") # 下载为字符串(适用于文本文件、JSON、XML API) $jsonString = $webClient.DownloadString("https://api.example.com/data.json") $content = $webClient.DownloadData("https://example.com/binary.bin") # 下载为字节数组 # 记得释放资源(虽然不是严格必须,但是好习惯) $webClient.Dispose()异步下载与进度监控:异步下载不会阻塞脚本执行,适合在GUI应用或需要同时执行其他任务的脚本中使用。
$webClient = New-Object System.Net.WebClient $url = "https://example.com/largefile.iso" $localPath = "largefile.iso" # 注册进度改变事件 Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action { param($sender, $e) Write-Progress -Activity "下载中" -Status "进度:" -PercentComplete $e.ProgressPercentage } # 注册下载完成事件 Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action { param($sender, $e) Write-Host "下载完成!" -ForegroundColor Green # 取消事件注册 Get-EventSubscriber | Unregister-Event } # 开始异步下载 $webClient.DownloadFileAsync([Uri]$url, $localPath) # 此时脚本可以继续做其他事情... # 如果要等待下载完成,可以循环检查或使用 Wait-Event实操心得与避坑:
- 编码问题:当使用
.DownloadString()下载文本时,如果网站编码不是UTF-8,可能会出现乱码。WebClient默认使用系统的ANSI代码页。可以通过设置.Encoding属性来指定。$webClient.Encoding = [System.Text.Encoding]::UTF8 $content = $webClient.DownloadString($url) - 超时设置:
WebClient本身没有直接的超时属性。但它的底层操作会遵循System.Net.ServicePointManager的默认设置。对于更精细的控制,可以考虑使用System.Net.Http.HttpClient(更现代,但稍复杂)。 - 对象释放:在循环中大量创建
WebClient对象时,显式调用.Dispose()有助于及时释放网络连接资源。使用try-finally块确保释放。$wc = $null try { $wc = New-Object System.Net.WebClient $wc.DownloadFile($url, $path) } finally { if ($wc -ne $null) { $wc.Dispose() } }
3.3 BitsTransfer 的任务管理与策略配置
BitsTransfer的使用模式是“启动任务-管理任务”。
基础后台下载:
# 导入模块(Windows PowerShell 通常已默认导入,但显式导入是好习惯) Import-Module BitsTransfer # 启动一个后台传输任务 Start-BitsTransfer -Source "https://example.com/windows.iso" -Destination "D:\ISOs\windows.iso" # 这个命令会立即返回,下载在后台由BITS服务进行。查看与管理任务:
# 查看所有BITS传输任务 Get-BitsTransfer # 查看更详细的任务信息 Get-BitsTransfer | Format-List * # 暂停某个任务(通过Job ID) Suspend-BitsTransfer -BitsJob <JobId> # 恢复被暂停的任务 Resume-BitsTransfer -BitsJob <JobId> # 删除(取消)某个任务 Remove-BitsTransfer -BitsJob <JobId> # 等待所有BITS任务完成 Get-BitsTransfer | Complete-BitsTransfer高级配置:
# 设置作业优先级(High / Normal / Low / Foreground) Start-BitsTransfer -Source $url -Destination $path -Priority High # 设置作业显示名称,便于管理 Start-BitsTransfer -Source $url -Destination $path -DisplayName "月度数据备份包下载" # 设置自定义HTTP请求头 $headers = @{"Custom-Header" = "MyValue"} Start-BitsTransfer -Source $url -Destination $path -Headers $headers # 设置传输策略,例如仅在非计费网络(如以太网)下载 Start-BitsTransfer -Source $url -Destination $path -TransferPolicy Unrestricted # TransferPolicy 可选: Unrestricted (默认), NotRoaming, NoSurcharge, Standard (最严格)实操心得与避坑:
- 任务持久化与清理:BITS任务会持久化在系统中,直到被显式完成或删除。一个常见的坑是脚本多次运行,创建了大量已完成但未清理的旧任务。务必在脚本末尾或任务完成后进行清理。
# 脚本示例:下载并完成后清理 $job = Start-BitsTransfer -Source $url -Destination $path -Asynchronous # ... 可以做其他事 ... # 等待特定任务完成 while (($job.JobState -eq 'Transferring') -or ($job.JobState -eq 'Connecting')) { Start-Sleep -Seconds 5 } Complete-BitsTransfer -BitsJob $job - 错误处理:BITS任务错误不会像普通cmdlet那样直接抛出异常。需要检查任务的
.JobState和.LastError属性。$job = Start-BitsTransfer -Source $url -Destination $path -ErrorAction SilentlyContinue if ($job.JobState -eq 'Transferred') { Complete-BitsTransfer -BitsJob $job Write-Host "成功" } else { Write-Error "下载失败,最后错误: $($job.LastError.Description)" Remove-BitsTransfer -BitsJob $job } - 代理与认证:
Start-BitsTransfer支持-ProxyList和-ProxyUsage参数配置代理,也支持-Credential参数进行认证,但其认证方式可能对某些复杂场景(如NTLM协商)支持不如前两者灵活。
4. 实操过程与核心环节实现
现在,我们通过几个综合性的实战场景,将上述方法融会贯通,展示如何根据具体需求选择和组合使用这些工具。
4.1 场景一:编写健壮的自动化部署脚本下载组件
假设你正在编写一个自动化部署脚本,需要从内部仓库下载一个.NET Core运行时安装包。要求:有进度提示(供管理员查看),需要重试机制,且网络环境一般。
分析与选型:需要进度提示,排除默认无进度的WebClient。需要一定的健壮性,但文件大小可能适中(几百MB),Invoke-WebRequest的内存开销可接受。我们选择Invoke-WebRequest并为其添加重试逻辑。
实现代码:
function Download-FileWithRetry { param( [string]$Url, [string]$OutputPath, [int]$MaxRetries = 3, [int]$RetryDelaySeconds = 5 ) $retryCount = 0 $success = $false while (-not $success -and $retryCount -lt $MaxRetries) { try { Write-Host "尝试下载 $Url (第 $($retryCount + 1) 次)..." -ForegroundColor Cyan # 使用 -UseBasicParsing 避免不必要的HTML解析,但保留进度条(在PowerShell 5.1中) # 在PowerShell Core中,进度条默认显示,无需 -UseBasicParsing $progressPreference = 'Continue' # 确保进度条显示 Invoke-WebRequest -Uri $Url -OutFile $OutputPath -ErrorAction Stop $progressPreference = 'SilentlyContinue' # 恢复静默,避免后续命令输出进度 Write-Host "文件已成功下载至: $OutputPath" -ForegroundColor Green $success = $true break } catch { $retryCount++ Write-Warning "下载失败: $($_.Exception.Message)" if ($retryCount -lt $MaxRetries) { Write-Host "等待 $RetryDelaySeconds 秒后重试..." -ForegroundColor Yellow Start-Sleep -Seconds $RetryDelaySeconds # 可选:指数退避增加延迟 # $RetryDelaySeconds = $RetryDelaySeconds * 2 } else { Write-Error "经过 $MaxRetries 次重试后仍无法下载文件。" throw $_ } } } } # 使用函数 Download-FileWithRetry -Url "https://internal-repo.com/dotnet-runtime.exe" -OutputPath "C:\Installer\dotnet.exe"核心环节解析:
- 错误处理与重试:通过
try-catch捕获Invoke-WebRequest可能抛出的异常(如网络超时、404错误)。通过while循环实现最多3次重试。 - 进度显示控制:通过临时修改
$progressPreference变量为'Continue',确保进度条能显示出来。这是一个常用技巧,因为有些宿主环境(如某些CI/CD平台)会默认静默进度。 - 用户体验:通过不同颜色的输出,清晰告知用户当前状态(尝试中、成功、失败、等待重试)。
4.2 场景二:静默、可靠地下载大型ISO镜像文件
你需要在一个夜间维护窗口中,通过脚本从微软服务器下载一个超过4GB的Windows Server ISO文件。要求:必须支持断点续传,脚本退出后下载不能中断,且不能占用过多前台资源。
分析与选型:大文件、要求断点续传和后台持久化,这是BitsTransfer的典型应用场景。
实现代码:
# 导入模块 Import-Module BitsTransfer $sourceUrl = "https://software-download.microsoft.com/pr/WinServer2022.iso" $destinationPath = "E:\ServerISOs\WinServer2022.iso" $jobName = "Nightly_ServerISO_Download" # 检查是否已有同名或同目标的任务存在,避免重复创建 $existingJob = Get-BitsTransfer | Where-Object { $_.DisplayName -eq $jobName -or $_.FileList.LocalName -contains $destinationPath } if ($existingJob) { Write-Host "发现已存在的BITS任务: $($existingJob.DisplayName),状态: $($existingJob.JobState)" -ForegroundColor Yellow # 可以选择恢复它 # Resume-BitsTransfer -BitsJob $existingJob $downloadJob = $existingJob } else { Write-Host "开始创建新的后台下载任务..." -ForegroundColor Cyan # 启动后台下载任务 # -TransferType 设为 Download, -Priority 设为 Low 避免影响其他网络活动 # -Asynchronous 返回作业对象以便后续跟踪 $downloadJob = Start-BitsTransfer -Source $sourceUrl -Destination $destinationPath ` -DisplayName $jobName ` -Priority Low ` -TransferType Download ` -Asynchronous } Write-Host "后台下载任务已启动。作业ID: $($downloadJob.JobId), 显示名: $($downloadJob.DisplayName)" Write-Host "你可以关闭此PowerShell窗口,下载将在后台继续。" Write-Host "要检查进度,稍后可以运行: Get-BitsTransfer | Where-Object {`$_.JobId -eq '$($downloadJob.JobId)'} | Format-List *" # 如果脚本需要等待下载完成(例如后续有校验步骤),可以循环检查状态 # while ($downloadJob.JobState -in @('Connecting', 'Transferring', 'Queued')) { # $percent = if ($downloadJob.BytesTransferred -gt 0) { # [math]::Round(($downloadJob.BytesTransferred / $downloadJob.BytesTotal) * 100, 2) # } else { 0 } # Write-Progress -Activity "后台下载中" -Status "$percent% 完成" -PercentComplete $percent # Start-Sleep -Seconds 10 # } # # if ($downloadJob.JobState -eq 'Transferred') { # Complete-BitsTransfer -BitsJob $downloadJob # Write-Host "下载已完成并确认。" -ForegroundColor Green # } else { # Write-Error "下载任务处于错误状态: $($downloadJob.JobState)" # }核心环节解析:
- 任务去重:脚本首先检查是否存在同目标或同名的BITS任务,避免在脚本意外重新运行时创建重复任务,这体现了生产环境脚本的健壮性。
- 后台与异步:使用
-Asynchronous参数让Start-BitsTransfer立即返回作业对象,而实际传输交给BITS服务。这意味着主脚本可以继续执行或直接退出。 - 状态监控与完成:注释掉的循环监控部分展示了如何主动等待任务完成。关键在于,只有任务状态变为
'Transferred'后,使用Complete-BitsTransfer才会将临时文件正式移动到目标路径。如果任务处于'Suspended'或'Error'状态,则需要相应处理。
4.3 场景三:快速批量下载一组已知URL的配置文件
你需要从一个文件服务器批量下载一批日志配置文件,URL列表已知且数量众多(上百个)。要求:速度尽可能快,实现简单。
分析与选型:批量、小文件、无需复杂HTTP交互。WebClient的简洁和轻量在这里有优势。我们可以利用.DownloadFileAsync实现简单的并行下载以提升速度。
实现代码:
# 假设有一个包含URL列表的文本文件 urls.txt,每行一个URL $urlList = Get-Content -Path ".\urls.txt" $baseOutputDir = "C:\Configs\" # 确保输出目录存在 if (-not (Test-Path $baseOutputDir)) { New-Item -ItemType Directory -Path $baseOutputDir -Force | Out-Null } $downloadJobs = @() $webClients = @() foreach ($url in $urlList) { if ([string]::IsNullOrWhiteSpace($url)) { continue } # 从URL中提取文件名 try { $uri = [Uri]$url $fileName = [System.IO.Path]::GetFileName($uri.LocalPath) if ([string]::IsNullOrEmpty($fileName)) { $fileName = "downloaded_file_$(Get-Random).dat" } $outputPath = Join-Path $baseOutputDir $fileName } catch { Write-Warning "无效的URL格式: $url,跳过。" continue } # 为每个下载创建一个WebClient对象 $webClient = New-Object System.Net.WebClient $webClients += $webClient # 保存引用以便后续清理 # 创建唯一的事件动作标识符 $eventIdentifier = "DownloadComplete_$(Get-Random)" # 注册完成事件 $job = Register-ObjectEvent -InputObject $webClient ` -EventName DownloadFileCompleted ` -SourceIdentifier $eventIdentifier ` -Action { param($sender, $e) $global:completedCount++ Write-Host "后台下载完成一个文件。总计完成: $global:completedCount / $($global:totalCount)" -ForegroundColor DarkGray } -Forward $downloadJobs += @{ Client = $webClient EventId = $eventIdentifier Path = $outputPath } # 开始异步下载 Write-Host "启动下载: $fileName" $webClient.DownloadFileAsync($uri, $outputPath) } # 初始化计数器 $global:totalCount = $downloadJobs.Count $global:completedCount = 0 Write-Host "已启动 $totalCount 个异步下载任务。" -ForegroundColor Cyan # 等待所有异步任务完成(简单轮询) $allDone = $false while (-not $allDone) { $allDone = ($global:completedCount -ge $global:totalCount) if (-not $allDone) { Start-Sleep -Milliseconds 500 } } Write-Host "所有文件下载完成!" -ForegroundColor Green # 清理:取消事件注册并释放WebClient资源 foreach ($job in $downloadJobs) { Unregister-Event -SourceIdentifier $job.EventId -ErrorAction SilentlyContinue $job.Client.Dispose() } Write-Host "资源已清理。" -ForegroundColor Cyan核心环节解析:
- 并行化提升效率:通过循环为每个URL创建独立的
WebClient实例并调用.DownloadFileAsync,实现了多个文件同时下载,充分利用了网络带宽。 - 事件驱动通知:使用
Register-ObjectEvent为每个WebClient的DownloadFileCompleted事件注册处理动作。当单个文件下载完成时,全局计数器递增并输出日志。 - 资源管理:异步操作创建了大量对象和事件订阅。脚本最后通过循环进行统一的清理工作(
Unregister-Event和.Dispose()),这是防止内存泄漏和资源残留的关键步骤。 - 文件名处理:从URL中智能提取文件名,并处理可能提取失败的情况,增加了脚本的容错性。
5. 常见问题与排查技巧实录
在实际使用中,你肯定会遇到各种报错和意外情况。下面是我在多年实践中总结的一些典型问题及其解决方法。
5.1 证书验证失败错误
问题现象:使用Invoke-WebRequest或WebClient访问HTTPS站点时,出现类似“基础连接已经关闭: 发送时发生错误。”或“请求被中止: 未能创建 SSL/TLS 安全通道。”的错误。
原因分析:最常见的原因是系统不信任目标服务器的SSL证书(例如自签名证书、证书过期、根证书不在受信任存储中)。另外,在旧版PowerShell(如5.1)上访问要求TLS 1.2的现代网站时,也可能因默认协议版本过低而出错。
解决方案:
临时忽略证书错误(仅限测试环境):
# 方法1:为当前会话添加回调(对所有请求生效) add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy # 注意:此方法在 .NET Core 版本的 PowerShell 中可能无效。 # 方法2:使用 -SkipCertificateCheck 参数 (PowerShell 7.0 及以上) # Invoke-WebRequest -Uri $url -SkipCertificateCheck -OutFile ... # 这是最推荐的在PS Core中的临时方案。启用强加密协议(解决TLS版本问题):
# 在脚本开头执行,强制使用 TLS 1.2。这对访问GitHub、Azure DevOps等现代网站至关重要。 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # 也可以启用多个协议以增加兼容性 # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls永久解决方案(生产环境):将目标站点的根证书或自签名证书导入到运行脚本的计算机的“受信任的根证书颁发机构”存储中。这需要通过MMC控制台或
certutil命令完成,不属于脚本范畴。
5.2 下载速度慢或进度卡住
问题现象:下载进度条长时间不动,或者下载速度远低于网络带宽。
排查思路:
- 检查源服务器和网络链路:使用其他工具(如浏览器、curl)测试同一URL的速度,排除源站限速或网络本身的问题。
- 检查代理设置:企业网络可能要求代理。PowerShell命令默认使用系统代理设置。你可以为命令指定代理:
# Invoke-WebRequest 使用代理 $proxyAddress = "http://proxy.company.com:8080" $proxy = New-Object System.Net.WebProxy($proxyAddress, $true) # true 表示绕过本地地址 $webSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession $webSession.Proxy = $proxy Invoke-WebRequest -Uri $url -WebSession $webSession -OutFile ... # WebClient 使用代理 $wc = New-Object System.Net.WebClient $wc.Proxy = New-Object System.Net.WebProxy("http://proxy.company.com:8080", $true) - 尝试使用BitsTransfer:如前所述,BITS服务具有智能节流和恢复功能。在拥挤或不稳定的网络中,它可能比直接HTTP下载更稳定。尝试用
Start-BitsTransfer对比一下速度。 - 检查防病毒软件或安全策略:某些安全软件会实时扫描下载的文件,这可能严重拖慢下载速度,尤其是大量小文件时。尝试临时禁用实时扫描进行测试。
5.3 中文路径/文件名乱码问题
问题现象:下载的文件名中包含中文,保存后变成乱码,或者从某些网站下载的文本文件内容中文显示为乱码。
原因分析:这通常是由于字符编码不一致造成的。HTTP响应头中的Content-Disposition可能使用了非UTF-8编码(如GBK),而PowerShell或系统默认使用UTF-8或ANSI去解析,导致错误。
解决方案:
对于文件名乱码(Invoke-WebRequest):
Invoke-WebRequest会尝试从响应头解析文件名。如果解析错误,可以手动指定输出文件名。# 忽略服务器建议的文件名,自己指定 iwr -Uri $url -OutFile "C:\下载\正确文件名.pdf"更复杂的情况需要手动解析响应头。可以先用
-Headers参数查看原始头信息,或者用-SkipHttpErrorCheck和-StatusCodeVariable获取原始响应对象来分析。对于文件内容乱码(WebClient.DownloadString):明确设置
WebClient的编码。$wc = New-Object System.Net.WebClient # 假设网站使用GB2312编码 $wc.Encoding = [System.Text.Encoding]::GetEncoding("GB2312") $content = $wc.DownloadString($url) # 然后可以用正确的编码保存 [System.IO.File]::WriteAllText("output.txt", $content, [System.Text.Encoding]::UTF8)
5.4 BitsTransfer 任务状态异常与管理
问题现象:Get-BitsTransfer看到任务一直处于“Queued”、“Suspended”或“Error”状态,无法完成。
排查步骤:
- 查看详细错误信息:
$problemJob = Get-BitsTransfer | Where-Object {$_.JobState -eq 'Error'} $problemJob | Format-List * # 重点关注 LastErrorTime, LastErrorCode, LastErrorDescription 属性。 - 常见错误及处理:
- BG_E_ERROR_INFORMATION_UNAVAILABLE (0x80200045):通常表示网络问题。检查网络连接,尝试
Resume-BitsTransfer。 - BG_E_HTTP_ERROR_XXX:HTTP错误,如404、403。检查URL是否有效,是否有访问权限。需要修复源URL或凭据。
- 任务被策略挂起:检查BITS作业的
TransferPolicy。如果网络被标记为“计费网络”,低优先级的任务可能被自动挂起。可以尝试用-TransferPolicy Unrestricted重新创建任务,或在系统设置中调整网络类型。
- BG_E_ERROR_INFORMATION_UNAVAILABLE (0x80200045):通常表示网络问题。检查网络连接,尝试
- 清理卡住的任务:对于无法恢复的错误任务,直接删除。
Get-BitsTransfer | Where-Object {$_.JobState -in @('Error', 'Suspended')} | Remove-BitsTransfer - 重启BITS服务:作为终极手段,可以尝试重启后台智能传输服务。
Restart-Service -Name BITS -Force # 注意:这会暂停所有正在进行的BITS传输,重启后它们会自动恢复。
5.5 在PowerShell Core (v7+) 中的差异点
PowerShell Core(跨平台版本)与传统的Windows PowerShell在下载命令上有一些重要区别,混合使用时需特别注意。
Invoke-WebRequest别名变化:在PSCore中,curl和wget是Invoke-WebRequest的别名,但它们实际上是调用底层的原生curl或wget命令(如果系统存在),而不是PowerShell的cmdlet。这可能导致参数不兼容。建议在脚本中始终使用完整的Invoke-WebRequest或别名iwr。-UseBasicParsing参数已废弃:在PSCore中,Invoke-WebRequest不再内置IE引擎来解析HTML,因此-UseBasicParsing参数被废弃且无效。所有请求都使用“基础解析”模式,速度更快。-SkipCertificateCheck参数:这是PSCore中新增的非常有用的参数,用于在测试时忽略SSL证书错误,比修改[System.Net.ServicePointManager]::ServerCertificateValidationCallback更安全、更简单。WebClient的可用性:System.Net.WebClient在.NET Core/.NET 5+中已被标记为过时,虽然目前仍可用,但微软推荐使用更现代的System.Net.Http.HttpClient。在PSCore中,WebClient仍然工作,但长远看,对于新脚本,可以考虑学习HttpClient。BitsTransfer模块不可用:BitsTransfer是Windows特有的技术,因此在非Windows平台的PowerShell Core上不可用。编写跨平台脚本时,应避免依赖它。
我个人在实际操作中的体会是,没有一种方法是万能的。对于临时的、交互式的小文件下载,iwr的便捷性无与伦比。对于集成在脚本中的稳定下载逻辑,WebClient的简洁API让我感到可靠。而当面对GB级别的系统镜像或需要在糟糕网络环境下保证交付时,BitsTransfer是我唯一的选择。理解它们的差异,就像木匠选择不同的凿子,根据木料的纹理和要雕刻的细节,选用最趁手的那一把,才能事半功倍。最后分享一个小技巧,在编写需要下载功能的通用脚本时,可以在开头做一个简单的功能探测,例如尝试Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue,如果存在则优先使用BITS,否则降级到Invoke-WebRequest,这样能让你的脚本在更多环境中优雅运行。