「在服务器迁移的时候,翻出了一大堆用了 20 年的批处理文件。这些是不是应该全部改写成 PowerShell?」── 在遗留资产的咨询中,这个问题堪称经典。复制与备份、应用启动的包装脚本、夜间联动作业。cmd 的批处理(bat)如今依然在 Windows 运维现场默默支撑着一切。
先说结论:没有必要全部改写。bat 目前没有停止运行的计划,而改写正常运行中的东西,本身就是一种故障风险。但另一方面,「原样不动」很危险的 bat 也确实存在——调用了 VBScript 的、把错误吞掉的、即将有变更的。不做这种判断,无论是「全部保留」还是「全部迁移」,都会埋下事故的种子。
本文面向中小企业的信息系统与运维负责人,梳理 bat 资产的盘点与迁移判断标准、bat 特有的陷阱、过渡期让 bat 与 PowerShell 共存的写法,直至改写对照表。
1. 先说结论
- cmd.exe 与 bat 目前没有废止计划,但 Microsoft 已明确表示,Windows 自动化推荐使用 PowerShell。「新写就用 PowerShell」是官方给出的方向。1
- VBScript 已于 2023 年 10 月正式宣布弃用,官方公开了先变为按需安装功能(Feature on Demand)、再从操作系统中删除的分阶段计划。从 bat 中用
cscript/wscript调用 VBScript 的资产,是迁移必要度最高的一类。23 官方也明确将 PowerShell 作为替代方案加以介绍。4 - 迁移判断不是「全部」,而是分类。稳定运行中、无变更计划 → 保留。有变更、需要错误处理、需要日志 → 迁移。依赖 VBScript → 优先迁移。详见第 4 章的判断表。
- bat 的结构性弱点在于:错误不会中止、
%ERRORLEVEL%的陷阱,以及字符编码。if errorlevel 1是「1 以上」的判断5,%errorlevel%一旦定义了同名环境变量就会失效。5 - 迁移到 PowerShell 能获得的是对象管道、通过 try/catch 实现的错误处理6、通过 -WhatIf 进行的预演7,以及通过 Pester 进行的测试。「能发现失败・能安全试运行・能自动测试」才是迁移的本质价值。
- 过渡期的现实解法是:从 bat 用
-File调用 powershell.exe / pwsh。脚本exit返回的退出代码会原样传递到 bat 一侧的%ERRORLEVEL%,因此可以在保留现有作业管理的同时替换内部实现。89 - 像 robocopy 这样优秀的外部命令不要改写,直接从 PowerShell 调用即可。其退出代码体系比较特殊,0 到 8 以上,8 以上为失败,因此只需正确写好基于
$LASTEXITCODE的判断。1011 - 分发时应把执行策略纳入设计之中。在 Windows PowerShell 5.1 的客户端操作系统上,默认值是 Restricted(禁止执行脚本);在 Windows Server 与 PowerShell 7 上则是 RemoteSigned。即便是 RemoteSigned,带有「来自网络」标记的未签名脚本也会被阻止。12
2. 为什么是现在 ── cmd 会留下,VBScript 会消失
首先梳理一下前提事实。Windows 的命令行 shell 有 cmd(命令行外壳)与 PowerShell 两个系列,Microsoft 官方文档明确写道:「对于最稳健、最新的 Windows 自动化,推荐使用 PowerShell,而非 Windows 命令或 Windows Script Host」。1 不过推荐终究只是推荐,cmd 本身并未列入 Windows 的非推荐功能清单。截至目前,还没有让现有 bat 无法运行的计划。
与之形成对比的是 VBScript。2023 年 10 月,VBScript 的弃用(deprecation)被正式宣布。官方公开的计划是:今后的 Windows 发行版会以按需安装功能(Feature on Demand)的形式提供,随后再从操作系统中删除。2 由于最初仍会以预装状态提供,因此不会在一两天内就无法使用3,但「删除」这个方向已经确定。Windows Server 2025 同样将其 FoD 化,官方也明确引导用户迁移到 PowerShell 作为替代方案。4
关于时间线,这里准确地写一下。截至 2026 年 7 月,Microsoft 官方文档中写明的只是「先以 FoD 形式提供,再在未来的 Windows 发行版中删除」这个顺序,并未给出删除的具体年月。23 也就是说,「还有几年」这个问题官方目前无法回答。请不要把这理解为「反正还早,放着不管也行」,而应理解为:一旦公布了期限才开始排查,往往就来不及了。只要提前完成盘点(第 7 章),等删除时间一旦公布,就能立刻回答出「我们受影响的就是这 3 个」。
这里重要的一点是:VBScript 常常隐藏在 bat 资产之中。像 cscript //nologo convert.vbs 这样从 bat 调用 VBScript 的结构,是 2000 年代的经典模式。这种结构的 bat 并不是「因为是 bat 所以安全」,而是「与 VBScript 命运与共」。在盘点时,除了 bat 文件本身之外,请务必确认 bat 所调用的目标(第 7 章会给出扫描脚本)。企业内部 VBScript・VBA 资产的全面排查,在《为 VBScript 弃用做准备:VBA·企业内部工具排查指南》中有详细说明。
3. bat 的陷阱,以及 PowerShell 带来的收获
作为迁移判断的素材,我们具体看一下 bat 的结构性弱点。下面这个批处理,是备份处理中常见的写法。
@echo off
rem 夜间备份(常见的危险批处理写法)
xcopy C:\data \\backup01\share\data /E /Y
echo 备份完成 >> C:\logs\backup.log
这个批处理中,潜藏着 3 个不易察觉的问题。
- 即使出错也不会停止。即便 xcopy 因为无法到达共享目标而失败,cmd 默认也会继续执行下一行,日志里照样会记录「备份完成」。要检测失败,每次都需要自己手写
if errorlevel判断。 %ERRORLEVEL%存在陷阱。if errorlevel 1判断的不是「终止代码等于 1」,而是「1 以上」。5 而且%errorlevel%这种写法的规范是「如果没有定义名为 ERRORLEVEL 的环境变量,就展开为当前的终止代码」,所以一旦有人写下set ERRORLEVEL=0,之后所有的判断都会失效。5 再加上exit /b 数值返回终止代码的规范13,要正确地写好这些判断,需要相应的知识储备。- 字符编码引发的事故。日语环境下的 cmd 传统上属于 Shift_JIS 系的世界,把 bat 重新保存为 UTF-8 后出现乱码、路径中含有符号导致误动作,这类事故至今仍在发生。关于这个问题的全貌,请参阅《Windows 的文字编码与换行符 - 乱码与 CRLF/LF 的基础》。
用 PowerShell 写同样的处理,对失败的处理方式会发生结构性的变化。
# 夜间备份(PowerShell 版骨架)
$ErrorActionPreference = 'Stop' # 让错误偏向"中止"的一侧
try {
# 复制源要指定为表示"文件夹内容"的 C:\data\*。如果传入 'C:\data',
# 当目标文件夹已存在时(第二次及以后),就会嵌套成 data\data
Copy-Item -Path 'C:\data\*' -Destination '\\backup01\share\data' -Recurse -Force
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) 备份完成"
exit 0
}
catch {
# 可以把"什么在哪里失败了"以结构化信息的形式记录下来
Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) 失败: $($_.Exception.Message)"
exit 1
}
try/catch 能捕获终止语句执行的错误,善后处理可以写在 finally 中。6 此外,PowerShell 还提供了通用机制 -WhatIf,它不会真正执行破坏性操作,只显示「原本会发生什么」7,让改写后的脚本可以在生产对象上安全地预演。
光看文字说明可能不太有实感,这里给出实际效果。在官方文档的示例中,执行 Remove-Item Date.csv -WhatIf 并不会删除文件,只会显示下面这一行。7
What if: Performing operation "Remove File" on Target "C:\ps-test\date.csv".
每个对象都会显示一行,所以即便像 Remove-Item C:\logs\*.log -WhatIf 这样用通配符书写,即将被删除的文件也会在执行前全部列出。也就是说,可以在不破坏生产数据的前提下确认「按这个条件,是否真的只会命中想要的目标」(显示消息使用的语言取决于环境设置)。bat 没有与之对应的机制,只能依靠用 echo 拼出命令再肉眼检查这种手工方式。
再加上如果用 Pester 编写测试,就能持续自动确认「应该能正常运行」这件事(参见《用 Pester 完善 PowerShell 测试 ── 让运维脚本不易损坏的实务方法》)。错误处理与重新执行设计本身,在同时公开的《PowerShell 的错误处理与重试设计》中有深入探讨。
也就是说,迁移带来的并非语法上的新颖,而是能发现失败、能安全试运行、能靠测试来守护这种运维品质。反过来说,对于失败了也能立刻察觉的简单启动包装脚本这类 bat,这种价值起不了太大作用。所以才需要分类。
4. 不要全部迁移 ── 分类判断表
| 论点 | 选项(推荐项标注「推荐」) | 判断标准 |
|---|---|---|
| 稳定运行中・无变更计划・逻辑简单 | 原样保留(推荐) / 迁移 | 应用启动包装脚本或几行复制可以保留。改写本身就是新的风险 |
| 调用了 VBScript(cscript/wscript) | 保留 / 优先迁移(推荐) | VBScript 弃用→FoD→删除的计划已官方公布。放任不管,终会等到废止那天2 |
| 即将有规格变更・功能追加 | 保持 bat 不变直接改造 / 先迁移再改造(推荐) | 变更的时机正是迁移的好机会。改造与完善测试可以同时进行 |
| 需要失败检测・日志・重试 | 在 bat 中补充判断 / 迁移(推荐) | 全面覆盖 if errorlevel 难以维护。这正是 try/catch 与结构化日志能发挥作用的领域6 |
| 夜间作业(由任务计划程序启动) | 维持现状 / 考虑迁移(推荐) | 无人值守运行时,失败处理方式尤为关键。执行账户以及「以 0x1 结束」的问题(第 7 章详述)也应一并复查 |
| 以 robocopy 等外部命令为主 | 用 cmdlet 重新实现 / 保留外部命令并调用(推荐) | 重新实现已有成熟表现的命令得不偿失。只把调用与判断部分改为 PowerShell10 |
| 编写者本人已离职・规格不明 | 不要触碰 / 先记录行为再判断(推荐) | 先把当前的输入输出与调度文档化。不要在不明确的情况下改写 |
拿不准的时候,有两条原则。第一,迁移应按「能产生价值的顺序」进行,而不是「按年代从旧到新」。第二,要动手的话,先从只读操作(盘点与记录)开始。
5. 过渡期的现实解法 ── 从 bat 调用 PowerShell 并传递退出代码
分类的结果是,很多现场会进入「一部分保持 bat、一部分改为 PowerShell」的过渡期。这时候有用的做法是:把 bat 保留为入口(与作业管理的接口),只把内部实现换成 PowerShell。由于作业调度器和运维手册看到的始终是 bat 的路径,因此可以在不破坏周边环境的前提下让内部实现现代化。
@echo off
rem 入口 bat: 调用同一文件夹下的 job.ps1,并继承退出代码
rem %~dp0 是这个 bat 自身所在的文件夹(官方文档中也记载的定式写法)
rem 如果残留着有人 set ERRORLEVEL=... 设置过的环境变量,%ERRORLEVEL%
rem 就会遮住真正的退出代码,因此在调用前先清除它,恢复为动态值
rem 执行策略不要在这里覆盖。指定 -ExecutionPolicy 会成为优先级更高的
rem Process 作用域,从而在不知不觉间削弱管理员配置的 AllSigned 等策略
set "ERRORLEVEL="
powershell.exe -NoProfile -File "%~dp0job.ps1"
exit /b %ERRORLEVEL%
不依赖当前目录、用 %~dp0 解析脚本位置的写法,正是官方文档中作为从 bat 调用的示例给出的方法。9 如果要用 PowerShell 7 运行,只需把 powershell.exe 换成 pwsh 即可(5.1 与 7 是分别安装、可以共存的。使用场景划分请参见同时公开的《Windows PowerShell 5.1 与 PowerShell 7 的区别与迁移》)。
退出代码的传递方式可以整理如下。
- 脚本以
exit 4结束时,进程的退出代码会变为 4,可以在 bat 一侧通过%ERRORLEVEL%接收到。这种联动在官方文档中附有实例记载。8 - 用
-File调用时,如果脚本因未处理的异常而终止,退出代码为 1;正常结束则为 0。为了避免「明明失败了却是 0」的情况,脚本一侧应判断成败后显式exit,这样更安全。914 - 从 PowerShell 调用外部命令的结果,会存入自动变量
$LASTEXITCODE。11
以 robocopy 为主角的 bat,正是这种形式的好例子。robocopy 是一款拥有重试(默认竟高达 100 万次・等待 30 秒)、镜像、日志功能的成熟命令10,没有理由用 Copy-Item 重新实现。只是它的退出代码体系比较特殊:0 表示「没有需要复制的对象」,1 表示「正常复制」,8 以上为失败。10 如果按「非 0 即异常」这种一般规则来判断,就会把正常的复制误判为异常。
# robocopy 直接照常使用,只在 PowerShell 一侧正确地做判断
# /r:2 /w:5 ── 默认重试 100 万次在无人值守作业中实际上等同于挂起,必须收紧
robocopy 'C:\data' '\\backup01\share\data' /MIR /R:2 /W:5 /NP /LOG+:'C:\logs\robocopy.log'
if ($LASTEXITCODE -ge 8) {
Write-Error "robocopy 失败了(退出代码:$LASTEXITCODE)"
exit 1
}
Write-Host "同步完成(退出代码:$LASTEXITCODE)" # 0〜7 为成功系
exit 0
6. 改写对照表 ── 把 bat 的词汇换成 PowerShell
下面是实际改写时的对照表。请不要把它当作机械式替换表,而要当作「同样的意图该如何表达」的对照表来使用。
| bat 的写法 | PowerShell 的定式 | 补充 |
|---|---|---|
copy / move / del / md |
Copy-Item / Move-Item / Remove-Item / New-Item |
破坏性操作先加上 -WhatIf 进行预演7 |
xcopy / robocopy |
直接调用 robocopy | 不要重新实现。用 $LASTEXITCODE 判断 8 以上为失败1011 |
for %%f in (*.csv) do ... |
Get-ChildItem *.csv \| ForEach-Object { ... } |
流转的是对象而非文件名字符串 |
if errorlevel 1 goto :error |
try/catch + $ErrorActionPreference = 'Stop' |
外部命令依旧按退出代码判断6 |
set VAR=value |
$var = 'value'(环境变量用 $env:VAR) |
可以区分进程环境变量与普通变量 |
call :sub / goto |
function |
可以为参数添加类型与校验 |
findstr |
Select-String |
匹配行以对象形式返回,可在后续加工处理 |
>> log.txt |
Start-Transcript / Add-Content |
想整体采集执行记录时用 Transcript 更方便 |
rem |
# |
─ |
PowerShell 一侧的基础词汇(cmdlet 的查找方法、管道、确认的做法)已经整理在《PowerShell 命令基础 ── 应先掌握的操作与安全使用方法》中。
7. 分阶段迁移的步骤 ── 盘点→分类→试点→并行运行
最后,把推进方式整理为 4 个阶段。
(1) 盘点。首先从只读调查开始。机械化地筛查出 bat 的所在位置,以及是否依赖 VBScript。
# bat 资产盘点: 所在位置清单,以及 VBScript 调用检测(仅读取的安全调查)
# 未能枚举到的位置会成为盘点的漏洞,因此用 -ErrorVariable 记录下来,之后再公开
$targets = Get-ChildItem -Path 'C:\', 'D:\jobs', '\\fileserver\scripts' -Recurse `
-Include '*.bat', '*.cmd' -File -ErrorAction SilentlyContinue -ErrorVariable enumErrors
$readErrors = @()
$report = foreach ($file in $targets) {
# 如果存在 cscript/wscript/.vbs 的调用,就标记为「依赖 VBScript」的需关注对象
# 已枚举到的路径用 -LiteralPath 传入(把 [2026]job.bat 这类带方括号的
# 文件名传给 -Path,会被解释为通配符从而看到别的文件)
# 因加锁等原因无法读取内容的文件,也一并记录到 $readErrors 中
$vbs = Select-String -LiteralPath $file.FullName -Pattern 'cscript|wscript|\.vbs' -Quiet `
-ErrorAction SilentlyContinue -ErrorVariable +readErrors
[pscustomobject]@{
Path = $file.FullName
LastWriteTime = $file.LastWriteTime
UsesVBScript = $vbs
}
}
$report | Sort-Object UsesVBScript -Descending | Export-Csv 'C:\audit\bat-inventory.csv' -NoTypeInformation -Encoding UTF8
# 把未能调查到的位置留存到清单中(不为空则说明盘点尚不完整)
# 同时包含枚举失败的位置,以及枚举到了但内容读取失败的文件
@($enumErrors) + @($readErrors) | ForEach-Object { $_.TargetObject } |
Set-Content -Path 'C:\audit\bat-uninspected.txt'
生成的 bat-inventory.csv 是只有 3 列的朴素表格。UsesVBScript 为 True 的行是优先迁移的候选,通过 Sort-Object 会被排到前面。内容大致如下(路径和日期均为示例)。
| Path | LastWriteTime | UsesVBScript |
|---|---|---|
\\fileserver\scripts\nightly\convert.bat |
2009/04/13 18:22:31 | True |
D:\jobs\eod\export_shipping.bat |
2014/11/07 9:41:02 | True |
D:\jobs\backup\copy_master.bat |
2021/06/02 14:05:47 | (空白) |
C:\tools\launch_viewer.bat |
2018/02/19 11:30:15 | (空白) |
UsesVBScript 列出现空白并不是故障。因为 Select-String 的 -Quiet 规范是:匹配成功时返回 $true,不匹配时并非返回 $false,而是返回 $null。15 按「True 还是空白」排序在实用上没有问题,但如果想明确显示为 False,请像 $vbs = [bool](Select-String ...) 这样先转换成布尔值再存入(日期时间的显示格式取决于运行环境的区域设置)。
仅凭这 4 行,判断就能推进不少。前两个属于第 4 章的「优先迁移」,第 3 个属于「如果有变更计划就迁移」,最后一个启动包装脚本属于「原样保留」。在实务中,可以在此基础上手动添加启动方(任务计划程序/作业管理工具/人工)和负责人这两列,直接作为迁移计划的台账使用。LastWriteTime 在 10 年以上之前的,可以作为「编写者本人已经不在了」的候选,提前留出解读时间会更安全。
(2) 风险分类。把盘点结果套入第 4 章的判断表,分类为「保留」「迁移」「优先迁移(依赖 VBScript)」。同时记录每个 bat 的启动方(任务计划程序、作业管理工具、人工)以及失败时的影响范围。这里要特别留意的是「以 0x1 结束」的问题。任务的「上次运行结果」中出现的 0x1,其含义是并非任务计划程序自身的错误,而是被启动的程序返回了退出代码 1,原因几乎都出在「手动执行与定期执行之间的环境差异」上。典型情况是:由于任务一侧没有指定「起始于(可选)」,导致当前目录变成了 C:\Windows\System32,从而破坏了以相对路径为前提的 bat。除此之外,还有源自配置文件的环境变量、映射的网络驱动器在无人会话中不存在,以及执行策略不同等原因。排查步骤请参见《任务计划程序的任务不执行、以 0x1 结束 ── 原因排查与安全的运维设计》。
(3) 试点。选择一个影响较小的 bat,用第 5 章的入口 bat 方式进行改写。这时应把执行策略纳入设计。Windows 的默认值是 RemoteSigned,本地创建的脚本可以运行,但带有「来自网络」标记的未签名脚本会被阻止。12 通过共享文件夹分发时遇阻的排查方法,以及包含签名运维在内的完整设计思路,已整理在同时公开的《PowerShell 的执行策略与脚本签名》中。
(4) 并行运行。让旧 bat 与新脚本并行运行一段时间。分开写入目标以便比对输出、新脚本一开始只用 -WhatIf 或仅输出日志的方式运行、回退方案只需把作业指向重新改回 bat 即可 ── 做好这些准备之后再让旧 bat 退役,迁移本身基本就不会变成事故。
8. 总结
- cmd 与 bat 目前没有废止计划,但官方推荐使用 PowerShell。另一方面,VBScript 弃用→FoD→删除的计划已经公布,从 bat 中被调用的 VBScript 是最优先的迁移对象。
- 迁移不是「全部」,而是分类。稳定运行、无变更的 bat 予以保留,从有变更、需要错误处理和日志、依赖 VBScript 的开始迁移。
- bat 遇错不会中止,
if errorlevel 1是 1 以上的判断,%errorlevel%会因同名环境变量而失效。PowerShell 通过 try/catch・-WhatIf・Pester 提供「能发现・能试运行・能守护」。 - 过渡期的现实解法是:从入口 bat 调用
powershell.exe -File(或pwsh -File),通过exit与%ERRORLEVEL%传递退出代码。 - 像 robocopy 这样优秀的外部命令不要改写,只需正确写好
$LASTEXITCODE的判断(8 以上为失败)。 - 步骤是盘点(从只读开始)→风险分类→试点→并行运行。也别忘了执行策略与分发方面的设计。
相关文章
- 为 VBScript 弃用做准备:VBA·企业内部工具排查指南
- PowerShell 命令基础 ── 应先掌握的操作与安全使用方法
- 任务计划程序的任务不执行、以 0x1 结束 ── 原因排查与安全的运维设计
- Windows 的文字编码与换行符 - 乱码与 CRLF/LF 的基础
- PowerShell 的执行策略与脚本签名 ── 从「用 Bypass 蒙混过关」的运维方式毕业的实务指南
- PowerShell 的错误处理与重试设计 ── 从 try/catch 失效的陷阱到 exit code、重试的实务定式
相关咨询领域
合同会社小村软件承接 bat・VBScript・PowerShell 混合的遗留资产盘点与迁移计划制定、夜间作业的 PowerShell 化与运维设计、迁移过程中出现故障的排查。即使是从解读「编写者已经不在了」的批处理开始,也欢迎垂询。
参考链接
-
Microsoft Learn, Windows Commands。关于 Windows 拥有命令行外壳(cmd)与 PowerShell 两种 shell、以及官方所述「对最稳健、最新的 Windows 自动化,推荐使用 PowerShell 而非 Windows 命令或 Windows Script Host」的说明。 ↩ ↩2
-
Microsoft Learn, Deprecated features for Windows client。关于 VBScript 的弃用已于 2023 年 10 月发布、今后的 Windows 发行版将以 Feature on Demand 形式提供后再从操作系统中删除这一计划的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Resources for deprecated features。关于 VBScript 的 Feature on Demand 最初会以预装形式提供、在退役准备期间可不间断使用的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, Features removed or no longer developed in Windows Server。关于 Windows Server 2025 中 VBScript 以 FoD 形式提供、并将在后续发行版中删除,以及官方引导将 PowerShell 作为任务自动化与脚本的替代方案这一说明。 ↩ ↩2
-
Microsoft Learn, if。关于 if errorlevel
是「上一个程序的退出代码大于等于 number 时为真」这一以上判断、%errorlevel% 是以不存在名为 ERRORLEVEL 的环境变量为前提的展开、若定义了同名环境变量则会返回该值的说明。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, about_Try_Catch_Finally。关于 try/catch/finally 块可以处理终止语句的错误与终止脚本的错误、catch 中可以指定异常类型、finally 无论是否发生错误都会执行、可用于善后处理的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_CommonParameters。关于会对系统或数据进行更改的 cmdlet 所提供的风险缓解参数(-WhatIf/-Confirm)、-WhatIf 不执行命令、只显示影响说明的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Language_Keywords。关于 exit 关键字会设置 $LASTEXITCODE、在 cmd.exe 一侧对应 %ERRORLEVEL%、用 pwsh -File 执行的脚本中 exit 4 会在调用方的 %ERRORLEVEL% 中被观测为 4 的实例、没有 exit 语句时正常结束为 0・未处理异常为 1 的说明。 ↩ ↩2
-
Microsoft Learn, about_Pwsh。关于 pwsh 的 -File 参数用法、从批处理脚本调用时用 %~dp0 表示执行目录的官方示例、用 -File 执行时如果发生脚本终止错误则退出代码为 1、用 exit 命令结束时则以该数值作为退出代码的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, robocopy。关于 robocopy 的退出代码体系(0=无需复制的对象、1=全部文件正常复制、8 以上=有 1 项及以上失败)、重试默认值为 /r:1000000(100 万次)・等待默认值为 /w:30 秒、镜像与日志等选项的说明。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Automatic_Variables。关于自动变量 $LASTEXITCODE 中会存放原生程序或脚本的退出代码、用 -File 执行脚本时该值的确定方式(异常时为 1、exit 指定值、正常结束为 0)的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Execution_Policies。关于 Windows 的默认执行策略为 RemoteSigned、RemoteSigned 会要求来自互联网的脚本具有受信任发布者的签名,而不要求本地创建的脚本签名、各项策略(Restricted/AllSigned/Bypass 等)含义的说明。 ↩ ↩2
-
Microsoft Learn, exit。关于 exit /b
会结束批处理脚本并将指定值设置到 ERRORLEVEL 环境变量、若用于结束 cmd 本身则会被设置为进程退出代码的说明。 ↩ -
Microsoft Learn, about_PowerShell_exe。关于 Windows PowerShell 5.1 的 powershell.exe 中 -File 参数的规范、从 cmd.exe 传递环境变量时的 %windir% 语法、退出代码处理方式的说明。 ↩
-
Microsoft Learn, Select-String。关于 Select-String 是用正则表达式搜索文本的 cmdlet、指定 -Quiet 时的返回值不是 MatchInfo 对象,而是找到模式时为 $true・未找到时为 $null 的说明。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南
整理 Windows PowerShell 5.1 与 PowerShell 7 的关系(共存与 pwsh.exe)、5.1 不再新增功能的官方方针、编码差异导致的乱码、用 #Requires 进行防御,直至任务计划程序更新的迁移步骤。
PowerShell脚本运行慢时该看哪里 ── 数组・管道・匹配的诀窍
整理 PowerShell 脚本变慢的常见原因。从数组 += 导致 O(n^2) 的原理、管道与 foreach 的差异、匹配的哈希表化、文件 I/O 的改善,到正确的测量方法,从实务角度进行讲解。
不再使用 Write-Host ── PowerShell 的输出流与日志设计
本文整理 PowerShell 六个输出流的用法区分、Write-Host 存在的问题与正确的使用场景、函数返回值被污染的原因、通过 -Verbose 与 -InformationVariable 实现调用方控制,以及结构化日志的留存方法。
PowerShell 的并行处理 ── ForEach-Object -Parallel 与作业(Job)的选用之道
本文从实务角度整理 ForEach-Object -Parallel、Start-ThreadJob、Start-Job 的区别与选用之道,$using: 与线程安全性,ThrottleLimit 的确定方法,以及反而会变慢的情形。
从 PowerShell 正确调用外部 exe ── 参数引用、退出代码与乱码的陷阱
从 PowerShell 调用 robocopy 或公司内部 EXE 时,参数会损坏、拿不到退出代码、输出还会出现乱码。本文从实务角度整理 PowerShell 7.3 的参数传递变更、停止解析令牌 --%、以及 Start-Process 的使用区分。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- 企业内部的批处理文件是否都应该迁移到 PowerShell?
- 不建议全部迁移。已经稳定运行多年且没有变更计划的批处理,改写本身就会带来新的故障风险。迁移优先度高的是:即将有变更的、需要错误处理或日志的,以及调用了 VBScript(cscript/wscript)的批处理。用判断表进行分类,从价值高的开始分阶段迁移,才是实务上的定式。
- cmd.exe 或批处理文件会被废止吗?
- cmd.exe 并未列入 Windows 的非推荐功能清单,也没有废止的公告。不过 Microsoft 明确表示,Windows 自动化应使用 PowerShell,而非命令或 WSH。另一方面,VBScript 已于 2023 年 10 月正式宣布弃用,官方公开的计划是:今后的 Windows 会先将其变为按需安装功能(Feature on Demand),随后再从操作系统中删除。bat 本身会继续运行,但 bat 中调用的 VBScript 迟早会有无法运行的一天。
- 如何从 bat 文件中调用 PowerShell 脚本?
- 把脚本通过 -File 参数传给 powershell.exe(或 PowerShell 7 的 pwsh.exe)。如果脚本与 bat 位于同一文件夹,用 %~dp0 解析所在位置——比如「powershell.exe -NoProfile -File "%~dp0job.ps1"」——是官方文档中也给出的写法。需要注意的是,-ExecutionPolicy 指定会成为优先级更高的 Process 作用域,从而削弱管理员配置的策略,因此 bat 一侧不要附加该参数,而应遵循环境侧设计好的执行策略。只要脚本一侧用 exit 数值 返回结果,就会原样传递到 bat 一侧的 %ERRORLEVEL%,因此可以在保留现有作业管理机制的同时,只把内部实现替换为 PowerShell。
- bat 的 %ERRORLEVEL% 有哪些陷阱?
- 最典型的是「if errorlevel 1」是「终止代码大于等于 1 即为真」的以上判断(并非等值判断)。此外 %errorlevel% 是以「未定义同名环境变量 ERRORLEVEL」为前提的动态展开,一旦像 set ERRORLEVEL=0 这样自行定义了它,之后就会一直返回该值。而且 bat 在命令失败后默认会继续执行下一行,因此忘记写判断的失败会被悄悄吞掉。这类陷阱的稀少,正是迁移到具备错误处理能力的 PowerShell 的动机所在。
- 使用了 robocopy 的批处理,在 PowerShell 中应该怎么改写?
- 定式是不改写。robocopy 是一款拥有重试、镜像、日志等成熟功能的优秀外部命令,可以在 PowerShell 中直接调用。用 Copy-Item 重新实现既费工夫,可靠性反而更低。需要注意的是终止代码的解读:robocopy 返回 0 到 8 以上的数值,8 以上为失败,1 表示正常复制完成。在 PowerShell 一侧应查看 $LASTEXITCODE,按「8 以上即为异常」来判断。
- 分发 PowerShell 脚本时遇到了执行策略的阻拦,该怎么办?
- 执行策略的默认值(RemoteSigned)会要求带有「来自互联网」标记的脚本必须签名。如果通过共享文件夹分发被阻拦,首先应确认原因,长久之计是采用签名运维或通过组策略统一设置。在批处理中调用时加上 -ExecutionPolicy Bypass 虽然简便,但请先确认这是否符合组织策略设计的本意,再决定是否使用。