「找遍了 cmdlet,也没找到想要的处理方式」── 只要把 PowerShell 用到一定深度,就必然会撞上这堵墙。批量创建快捷方式、不解压就查看 ZIP 内容、窗口操作、Excel 文件的读写。仅凭标准 cmdlet 无法触及的需求,中小企业的信息系统与运维负责人几乎每天都会来咨询。
其实这堵墙的另一侧,正是 PowerShell 擅长的领域。由于 PowerShell 构建在 .NET 之上,可以在不额外安装的情况下直接调用 .NET 类库中的类型。此外,还能用 Add-Type 当场嵌入 C# 代码或 Win32 API(P/Invoke),并用 New-Object -ComObject 操作 WScript.Shell、Excel 等 COM 对象。也就是说,过去用 VBScript 或 VBA 编写的「Windows 自动化」工具箱,几乎可以原样搬过来使用。
不过,这份力量也伴随着相应的后续处理规范。尤其是 Excel 的 COM 操作,是「脚本结束后 EXCEL.EXE 仍然残留」这类经典问题的高发地带,而 Microsoft 本身就不推荐在无人执行环境中自动化 Office。本文将整理 PowerShell 调用 .NET・Add-Type・COM 操作的实务模式与陷阱,直至「脚本该坚持到什么程度、从哪里开始该改用 C# 工具化」的判断标准。
1. 先说结论
- PowerShell 建立在 .NET 之上。Windows PowerShell 5.1 构建在 .NET Framework 之上,PowerShell 7 构建在 .NET(原 .NET Core)之上,可以像
[System.IO.Path]::GetFileNameWithoutExtension()这样直接调用 .NET 的静态方法。1 - 创建实例可以用
New-Object,PowerShell 5.0 及以后也可以用[类型]::new()。只要打出[类型]::new(不加括号)就能确认构造函数列表,可以一边查阅参数一边编写。2 - 使用
Add-Type可以当场编译 C# 源代码并嵌入到会话中。传入带 DllImport 的签名,还能实现 Win32 API 的 P/Invoke 调用。添加的类型无法在会话内清除,同名类型也无法重新定义。3 - COM 对象通过
New-Object -ComObject <ProgId>创建。VBScript 的CreateObject("Shell.Application")直接对应New-Object -ComObject Shell.Application,WScript.Shell 创建快捷方式等 WSH 时代的工具,都可以在 PowerShell 中使用。45 - COM 对象的寿命通过引用计数管理,在 .NET 一侧由 RCW(Runtime Callable Wrapper)握有该引用。只要引用还残留着,Excel 等进程就不会结束。要明确释放可以使用
Marshal.ReleaseComObject,但滥用会引发另一种事故,官方指南是「只在真正必要时使用」。67 - 对于无人执行环境下的 Office 自动化,Microsoft 明确表示「不推荐,也不支持」。从服务、任务计划程序、服务器端发起的 Excel COM 操作,即使看起来能正常运行,也属于不受支持的架构。无人处理应替换为 Open XML 系列库或 CSV 联动。8
- 5.1 与 7 中可用的类型・方法并不相同。存在像 String.Split 重载差异那样同一段代码行为不同的例子,在 5.1 中也有些场景需要用
Add-Type显式加载 GAC 中的程序集。13 - 当直接调用 .NET 的场景越来越多时,就是该考虑改用 C# 工具化的分界线。当 Add-Type 无法重新定义类型的限制、或分发问题开始变得显眼时,就是脚本已接近极限的信号。
2. PowerShell 建立在 .NET 之上 ── [类型]::方法 与 New-Object
PowerShell 的 cmdlet,是把 .NET 类库的一部分「为运维工作包装得便于使用」而形成的。没有被包装的功能,也可以用方括号写出类型名直接调用。静态方法用 ::,实例的方法与属性用 .。
# .NET的静态方法直接调用。不需要 cmdlet,也不需要额外安装
[System.IO.Path]::GetFileNameWithoutExtension('C:\data\订单_20260718.csv') # -> 订单_20260718
[System.IO.Path]::Combine('C:\data', 'archive', '2026-07') # -> C:\data\archive\2026-07
[System.Math]::Round(123.456, 1) # -> 123.5
# 需要实例的话,用 New-Object 或 [类型]::new() 创建
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('server01')
# ::new 不加括号打出,会返回构造函数的列表(作为查阅参数的方法很方便)
[System.IO.StreamWriter]::new
[类型]::new() 是 PowerShell 5.0 新增的写法,比 New-Object 更快,还能像上面的例子那样当场查看构造函数的重载列表。2 另一方面也有需要注意的地方。Get-Item 等 cmdlet 返回的对象,PowerShell 有时会为其追加属性(NoteProperty),因此可能与 ::new() 直接创建的同类型对象在成员构成上不一致。2 如果遇到「通过 cmdlet 获取时明明有的属性却不见了」而感到困惑,请想起这个机制。
分享一个实务技巧。ZIP 处理有 Compress-Archive / Expand-Archive 这两个 cmdlet,但它们所依赖的底层 ZipArchive API 存在 2GB 的文件大小限制。这一限制在压缩与解压两侧的官方文档中均有记载,两者都明确写明「上限来自底层的 System.IO.Compression.ZipArchive API」,并列出该类的参考文档作为出处。910 另外,像「不解压、只想确认内容列表」这类细致的操作,cmdlet 中并不存在。这时就轮到 .NET 的 ZipFile 类登场了。11
# Windows PowerShell 5.1 中,需要显式加载 GAC 中的程序集
# (PowerShell 7 中随附程序集会在需要时自动加载,因此没有这一行也能运行)
Add-Type -AssemblyName System.IO.Compression.FileSystem
# 先从读取开始 ── 不解压,确认 ZIP 的内容
$archive = [System.IO.Compression.ZipFile]::OpenRead('C:\deploy\release.zip')
try {
$archive.Entries | Select-Object FullName, Length, LastWriteTime
}
finally {
$archive.Dispose() # 确保文件句柄被归还
}
# 内容没问题的话就展开。如果目标目录中存在同名文件,这个双参数版本
# 会在展开到一半时抛出异常(不会覆盖)。应避免直接展开到已有文件夹,
# 更安全的做法是每次都展开到新文件夹后再切换
$dest = "C:\apps\web_$(Get-Date -Format yyyyMMddHHmmss)"
[System.IO.Compression.ZipFile]::ExtractToDirectory('C:\deploy\release.zip', $dest)
在 .NET Framework 中使用 ZipFile 类需要引用 System.IO.Compression.FileSystem 程序集,这一点在 PowerShell 5.1 中也一样。11 在 7 中,随附程序集会在需要时自动加载,因此不写 Add-Type 也能运行,但只要写上就能两边通用,共用脚本中显式声明会更稳妥。3
3. 用 Add-Type 嵌入自定义 C# 与 Win32 API(P/Invoke)
在「.NET 已有的功能」之后,就轮到「.NET 没有的功能」了。向 Add-Type 传入 C# 源代码,就会当场编译并把类型添加到会话中。此外,向 -MemberDefinition 传入带 DllImport 的签名,就能通过 P/Invoke 调用 Win32 API。这种用法在官方文档中也作为示例刊载,是正当的手段。3
# 用对话框通知手动运行的长时间处理已完成(通过 P/Invoke 调用 user32.dll 的 MessageBoxW)
$signature = @'
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
'@
$native = Add-Type -MemberDefinition $signature -Name 'NativeMethods' `
-Namespace 'Win32' -PassThru
# 附加 MB_ICONINFORMATION(0x40) 后显示
[void]$native::MessageBoxW([IntPtr]::Zero, '备份处理已完成。', '长时间处理', 0x40)
有一点需要注意。模态对话框只适用于自己坐在桌面前的交互式执行场景。在任务计划程序的无人执行、Windows 服务等非交互式会话中,处理会卡在一个谁也无法关闭的对话框前,再也不会返回。无人任务的通知应该使用日志文件、事件日志、发送邮件等不需要等待响应的手段。
把同样是「通知处理完成」这件事,改写成面向无人任务的版本,就会是下面这样。用向事件日志写入一条记录来代替对话框,同时也保留一份日志文件。
# 无人任务版的通知: 不等待响应,留在事后可以追踪的地方
$source = 'KomuraSoft.NightlyJob' # 事件的源名称(任意字符串)
$logPath = 'C:\logs\nightly.log'
# 注册源需要管理员权限。在部署时执行一次即可,
# 夜间任务本体只在源已注册的前提下写入
# New-EventLog -LogName Application -Source 'KomuraSoft.NightlyJob'
Write-EventLog -LogName Application -Source $source -EntryType Information `
-EventId 1000 -Message '备份处理已完成。'
Add-Content -LiteralPath $logPath -Value "$(Get-Date -Format o) 备份处理已完成。"
在事件查看器 > Windows 日志 > Application 中,按源名称 KomuraSoft.NightlyJob 筛选即可追溯历史。如果环境中部署了监控工具,可以让它捕获这个事件 ID 并转发通知。另外需要说明,New-EventLog / Write-EventLog 是 Windows PowerShell 5.1 的 cmdlet,在 PowerShell 7 中已被删除(参见第 6 章的表格)。如果夜间任务运行在 PowerShell 7 上,比较直接的做法是把通知部分单独交给 powershell.exe 执行,或者干脆只记录到日志文件,把通知交给文件监控一侧处理(例如任务计划程序的事件触发或 Power Automate 等)。
Add-Type 在运维上有三个重要的限制。3
- 添加的类型仅限于该会话有效。在其他会话或远程环境中,需要再次执行
Add-Type。 - 同名类型无法重新定义。当签名写错想要修正时,需要更换名称或启动新的会话。反复试错请在可随时丢弃的控制台中进行。
- 在 PowerShell 7 中,如果同名类型已经存在,整个编译过程会被跳过。如果出现「明明改过了代码却没有生效」的情况,请怀疑这一点。
还有一点,是 P/Invoke 共通的注意事项。如果签名(参数类型、字符集、调用约定)写错,不仅会抛出异常,甚至可能导致整个进程崩溃。字符串与句柄封送处理的陷阱,已经在《在 C# 中安全调用 Win32 API —— P/Invoke 实务指南(DllImport / LibraryImport / CsWin32)》中详细整理,建议在用 Add-Type 正式使用 Win32 API 之前先读一遍。
4. 调用 COM ── New-Object -ComObject 与 WSH 工具箱
比 .NET 更早就搭载在 Windows 中的一整套部件就是 COM。可以用 New-Object -ComObject <ProgId> 创建,VBScript 的 Set objShell = CreateObject("Shell.Application") 直接对应 $objShell = New-Object -ComObject Shell.Application。4 WScript.Shell、WScript.Network、Scripting.FileSystemObject 等源自 WSH(Windows Script Host)的对象,同样可以这样使用。5 关于 COM 是什么这一基础话题,请参考《COM / ActiveX / OCX 是什么 - 区别与关系整理》。
一个具有代表性的实务技巧,就是批量创建快捷方式。快捷方式(.lnk)的创建并没有对应的 cmdlet,官方文档也提到「像创建快捷方式这类部分任务,使用 WSH 类会更简单」,并附上了 WScript.Shell 的示例。5
# 假设要把指向共享文件夹上工具的快捷方式,分发到所有终端的公共桌面
$shortcuts = Import-Csv 'C:\deploy\shortcuts.csv' # Name, Target, Args 三列
$wsh = New-Object -ComObject WScript.Shell
foreach ($item in $shortcuts) {
$lnkPath = Join-Path 'C:\Users\Public\Desktop' "$($item.Name).lnk"
$lnk = $wsh.CreateShortcut($lnkPath) # 如果已存在同名 .lnk,将成为覆盖对象
$lnk.TargetPath = $item.Target
$lnk.Arguments = $item.Args
$lnk.Save()
Write-Host "已创建: $lnkPath"
}
COM 对象的成员可以通过 $wsh | Get-Member 查看。5 应用范围十分广泛,例如在 Outlook 中创建邮件草稿、通过 Shell.Application 操作特殊文件夹等。不过,像 Excel.Application 这样的 ActiveX 可执行文件(在独立进程中启动的 COM 服务器),会伴随下一章要讲的后续处理问题。官方文档也提醒,释放引用后进程是否会终止取决于对方的应用程序,使用前应先测试终止行为。5
5. COM 的后续处理 ── EXCEL.EXE 残留问题与「无人 Office 不受支持」
5.1. 为什么进程会残留
COM 对象的寿命通过引用计数管理。从 .NET(也就是 PowerShell)操作 COM 时,每个 COM 对象都会创建一个名为 RCW(Runtime Callable Wrapper)的代理,只要 RCW 存活,COM 一侧的引用就不会被释放。RCW 的回收交由垃圾回收负责。6 也就是说,即使调用了 $excel.Quit(),只要还有引用残留在某处(其中也包括像 $excel.Workbooks.Open(...) 这样没有存入变量的中间对象的 RCW),EXCEL.EXE 就不会终止。
接下来的代码把 try/finally 嵌套了三层,初次阅读时结构应该不太好理解。先说明为什么要这样写。说白了就是「越靠后的处理越绝对不能跳过」,因此按照不想跳过的优先顺序把它们逐层放到内侧而已。
- 最外层的
try:操作 Excel 的主体处理。无论这里发生什么失败,都一定会进入后续的后续处理。 - 第 1 层
finally(关闭工作簿):即使保存失败,也要去关闭已打开的工作簿。不过Close本身也可能失败。 - 第 2 层
finally(执行Quit):所以要在不会被Close的失败牵连的位置调用Quit。如果 Excel 已进入无响应状态,Quit也可能失败。 - 第 3 层
finally(释放 RCW 并触发 GC):所以无论Quit成功与否,都要保证引用的释放一定被执行。这一步一旦被跳过,进程就会残留。
也就是说,写成三层并不是一种形式上的讲究,而是逐层消解「后续处理本身也可能失败」这一前提所得到的结果。反过来说,如果可以假设 Close 和 Quit 都不会失败,那么一个 finally 就够用了。
$excel = New-Object -ComObject Excel.Application
try {
$excel.DisplayAlerts = $false
$books = $excel.Workbooks # 中间对象也接到变量里,以便之后能够释放
$book = $books.Open('C:\work\月度销售.xlsx')
$sheet = $book.Worksheets.Item(1)
$sheet.Cells.Item(1, 1).Value2 = "更新: $(Get-Date -Format 'yyyy-MM-dd')"
$book.Save()
}
finally {
# Close 失败的路径恰恰是 EXCEL.EXE 最容易残留的地方,
# 因此用嵌套的 finally 确保 Quit 与释放一定会被执行
try {
if ($book) { $book.Close($false) }
}
finally {
# 即使 Quit 失败(Excel 无响应、COM 服务器断开等),也一定要执行 RCW 的释放
try {
if ($excel) { $excel.Quit() }
}
finally {
# 明确减少 RCW 的引用计数(按子到父的顺序)
foreach ($obj in @($sheet, $book, $books, $excel)) {
if ($obj) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($obj) }
}
# 让 GC 回收未能接入变量的 RCW 的保险措施
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.GC]::Collect()
}
}
}
Marshal.ReleaseComObject 会减少 RCW 的引用计数,在归零的那一刻释放 COM 一侧的引用。不过官方文档强烈提醒,访问已释放的 RCW 会导致异常或访问违规,因此「只应在绝对必要时使用」。7
上面的代码之所以把中间对象逐一接到变量里,是遵循了俗称「两个点规则」的做法。如果像 $excel.Workbooks.Open(...) 这样连续使用两个以上的点,中间 Workbooks 对应的 RCW 就会在没有存入任何变量的情况下生成,从而失去释放手段。所以这条规则的内容就是每一行最多只用一个点,中间的对象一定要用变量接住。释放模式的流派(ReleaseComObject 派与 GC 派)、两个点规则的细节,以及即便如此仍然残留的进程的定位方法,已在《C# 操作 Excel 时 EXCEL.EXE 残留的问题 ── COM 引用释放模式与替换判断》中详细说明。虽然那是一篇 C# 的文章,但 RCW 的机制在 PowerShell 中同样适用。
5.2. 从根本上:不在无人执行中使用 Office
还有一个更根本的判断。Microsoft 官方明确表示,「目前不推荐,也不支持从无人值守、非交互式的客户端应用程序或组件(包括 ASP、ASP.NET、DCOM、NT 服务)自动化 Office」。这是因为 Office 的设计前提是交互式使用,在无人环境中可能表现出不稳定行为或死锁。8 官方还列举了诸如因意外弹出的对话框导致处理停止、基于 STA 而无法承受多重执行等具体问题。8 这里所说的 STA 是单线程单元(Single-Threaded Apartment)的缩写,是 COM 线程模型的一种。它把对某个对象的调用集中到创建该对象的那个线程上,逐一处理,Office 应用程序正是基于这一前提构建的。所以「在一台服务器上并行运行多份相同处理」这种用法,在结构上就是不合适的。COM 线程模型本身在《COM STA/MTA 基础 - 线程模型与避免 Hang 的思路》中有详细说明。
也就是说,从任务计划程序的夜间任务或服务中调用 Excel COM 的架构,从一开始「能运行」的那一刻起就已经是不受支持的。对于无人处理,官方推荐替换为不启动 Office 软件本体的 Open XML 系列文件直接编辑或 CSV 联动方案。8 PowerShell 中具体的替代技巧,已汇总在同时发布的《用 PowerShell 自动化 Excel・CSV 业务处理 ── 汇总・核对・报表输出的实务技巧》中。请把 Excel COM 的使用边界划定在「有人登录的桌面上,用于辅助人的工作」这一范围以内。
6. 5.1 与 7 中「可用的 .NET」并不相同
Windows PowerShell 5.1 构建在 .NET Framework 4.5 系列之上,PowerShell 7 构建在 .NET(原 .NET Core)之上。1 只要仅使用 cmdlet,需要意识到这种差异的场景有限,但一旦像本文这样开始直接调用 .NET,差异就会浮出水面。
| 论点 | Windows PowerShell 5.1 | PowerShell 7 |
|---|---|---|
| 底层基础 | .NET Framework 4.5 系列1 | .NET(7.4 对应 .NET 8.0 等,随版本更新)1 |
| 方法的重载 | 较少(例如: String.Split 有 6 种)1 | 较多(官方展示了同样调用 Split('pq') 结果不同的例子)1 |
| 程序集加载 | 许多场景需要用 Add-Type 显式加载 GAC 中的程序集3 |
随附程序集会在需要时自动加载3 |
| 已不可用的功能 | ─ | *-EventLog 等部分 Windows 专用 cmdlet 已被删除1 |
「在 5.1 中能运行、在 7 中却不行」以及反过来的情况都可能发生。需要同时兼容两者的脚本,请务必在两种环境中分别测试。迁移判断的整体思路,已在同时发布的《Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南》中说明。
7. 实务定式(判断表)
| 论点 | 选项(推荐项标注「推荐」) | 判断标准 |
|---|---|---|
| 想做的处理没有对应的 cmdlet | 放弃 / 直接调用 .NET 类(推荐) | 首先查找 [类型]::方法。System.IO、System.Text、System.IO.Compression 附近可以填补大部分「差一步」的需求1 |
| 实例的创建 | New-Object / [类型]::new()(推荐) | 只需支持 5.0 及以后的话用 ::new()。可以看到构造函数列表,而且更快。只有 COM 只能用 New-Object -ComObject24 |
| 需要 Win32 API | 手动忍耐 / 用 Add-Type 做 P/Invoke(推荐) | 只有几个 API 的话 Add-Type 就足够了。签名写错会导致整个进程崩溃,所以要在可丢弃的会话中验证3 |
| 创建快捷方式等 WSH 来源的操作 | COM(WScript.Shell)(推荐) | 没有 cmdlet 的领域,COM 至今仍然现役。官方示例也采用这种方式5 |
| 辅助人工操作的 Excel 处理 | COM + 确实的后续处理(推荐) | 在 finally 中把 Quit・ReleaseComObject・GC 打包成一套。中间对象也要接到变量里76 |
| 无人任务中的 Excel/Office 处理 | COM / 不启动 Office 的方式(推荐) | 无人的 Office 自动化不受支持。应替换为 Open XML 系列或 CSV8 |
| 脚本长得太大 | 继续用 PowerShell 硬撑 / 改用 C# 工具化(推荐) | 如果 Add-Type 的代码已达数百行、类型无法重新定义的限制妨碍了开发、或不想让分发目标每次都重新编译 ── 出现任意一种情况,就是该迁移的信号 |
最后一行,是本文的总结性判断。当用 Add-Type 嵌入的 C# 代码开始膨胀时,那就已经是「用 PowerShell 的外皮包裹着一个 C# 程序」的状态了。转移到能使用 Visual Studio 的类型检查与调试器、NuGet 包、单元测试的 C# 项目中,开发与维护都会更快。由于也存在从 C# 一侧调回 PowerShell 资产的方法,迁移并不意味着全部重写。《在 C#(CSharp)中执行 PowerShell 并以对象形式接收结果》介绍了桥接的模式。
8. 总结
- PowerShell 构建在 .NET 之上,可以通过
[类型]::静态方法・New-Object・[类型]::new()直接调用 .NET 类库。当 cmdlet 中没有所需功能时,.NET 是首选方案。 - 用
Add-Type可以当场编译 C# 代码,传入 DllImport 签名后还能通过 P/Invoke 调用 Win32 API。类型仅限于该会话有效,无法重新定义同名类型。 - COM 通过
New-Object -ComObject操作。创建快捷方式等源自 WSH 的工具至今仍然现役。 - COM 的寿命通过引用计数 + RCW 管理,只要引用残留,EXCEL.EXE 等进程就会残留。要在 finally 中编写包含 Quit・ReleaseComObject・GC 的完整后续处理。
- Microsoft 明确表示,不推荐也不支持无人执行环境下的 Office 自动化。夜间任务应替换为不启动 Office 的方式。
- 5.1 与 7 底层的 .NET 不同,可用的类型・重载也不同。需要同时兼容两者的脚本,必须在两边都进行测试。
- Add-Type 一旦膨胀,就是该改用 C# 工具化的信号。脚本与可执行文件之间的桥接已有现成的模式可用。
相关文章
- C# 操作 Excel 时 EXCEL.EXE 残留的问题 ── COM 引用释放模式与替换判断
- 在 C# 中安全调用 Win32 API —— P/Invoke 实务指南(DllImport / LibraryImport / CsWin32)
- COM / ActiveX / OCX 是什么 - 区别与关系整理
- COM STA/MTA 基础 - 线程模型与避免 Hang 的思路
- 在 C#(CSharp)中执行 PowerShell 并以对象形式接收结果
- 用 PowerShell 自动化 Excel・CSV 业务处理 ── 汇总・核对・报表输出的实务方案
- Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南
相关咨询领域
合同会社小村软件承接基于 PowerShell 的公司内部业务自动化、包含 COM 资产(Excel 联动・遗留部件)在内的脚本设计与改造,以及从「脚本长得太大」状态迁移到 C# 工具化的服务。也承接 EXCEL.EXE 残留这类故障排查工作。
参考链接
-
Microsoft Learn,Differences between Windows PowerShell 5.1 and PowerShell 7.x。关于 Windows PowerShell 5.1 构建在 .NET Framework 4.5 之上、PowerShell 6.0 及以后构建在 .NET Core(现 .NET)之上、各版本所依赖的 .NET 版本一览、因 String.Split 重载差异导致同一段代码结果不同的例子,以及在 7 中被删除的 cmdlet。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn,about_Object_Creation。关于 PowerShell 5.0 为所有 .NET 类型新增的静态 new() 方法、通过输入 ::new 可以确认构造函数的重载列表,以及通过 cmdlet 获取的对象因 PowerShell 会追加 NoteProperty,可能与 ::new() 创建的对象在成员上不一致。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Add-Type。关于 Add-Type 会编译 C# 源代码并向会话添加类型、通过 MemberDefinition 实现 P/Invoke(调用 user32.dll 的 ShowWindowAsync)的官方示例、添加的类型仅限于该会话且同名类型无法重新定义、PowerShell 7 中若同名类型已存在则会跳过编译,以及 5.1 中加载 GAC 程序集需要 Add-Type 而 6 及以后会自动加载。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn,New-Object。关于 New-Object 会创建 .NET 或 COM 对象的实例、在 -ComObject 参数中指定 ProgId,以及 VBScript 的 CreateObject(“Shell.Application”) 对应 New-Object -ComObject “Shell.Application”。 ↩ ↩2 ↩3
-
Microsoft Learn,Creating .NET and COM objects (New-Object)。关于创建 WScript.Shell 等 WSH 对象、文档中提到创建快捷方式使用 WSH 类会更简单以及 CreateShortcut 的实例、对 COM 对象应用 Get-Member、ActiveX 可执行文件的终止行为取决于应用程序需要事先测试,以及 New-Object 使用 .NET 的 RCW。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn,Runtime Callable Wrapper。关于 .NET 通过名为 RCW 的代理公开 COM 对象、每个 COM 对象会创建一个对应的 RCW,以及 RCW 在被垃圾回收时会释放对该 COM 对象的引用。 ↩ ↩2 ↩3
-
Microsoft Learn,Marshal.ReleaseComObject(Object) Method。关于 ReleaseComObject 会减少 RCW 的引用计数,并在归零时释放底层的 COM 对象、释放后访问 RCW 会导致异常或访问违规、「只在绝对必要时使用」的官方提醒,以及与 FinalReleaseComObject 的关系。 ↩ ↩2 ↩3
-
Microsoft Learn,Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment。关于 Microsoft 不推荐也不支持从无人值守、非交互式的客户端应用或组件(包括 ASP、ASP.NET、DCOM、NT 服务)自动化 Office、无人环境下不稳定行为与死锁的可能性、因对话框导致停止・源自 STA 的多重执行限制等具体问题,以及直接编辑 Open XML 文件格式作为推荐替代方案。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,Expand-Archive。关于 Expand-Archive 使用 System.IO.Compression.ZipArchive API、该 API 存在最大 2GB 的文件大小限制,以及该 .NET API 处理的是符合 PKWARE 公司官方 ZIP 文件格式规范的文件。 ↩
-
Microsoft Learn,Compress-Archive。关于 Compress-Archive 使用 System.IO.Compression.ZipArchive API 进行压缩,文档以「The API limits the maximum file size to 2GB」明确写明 2GB 上限是底层 API 一侧的限制,并列出 ZipArchive 类 作为参考出处(由于 ZipArchive 类的参考文档正文中并未记载 2GB 这一具体数值,因此数值的第一手信息来源是这个 cmdlet 一侧的页面)。 ↩
-
Microsoft Learn,ZipFile Class。关于 ZipFile 类提供 ZIP 创建・展开・打开的静态方法、在 .NET Framework 中使用需要引用 System.IO.Compression.FileSystem 程序集,以及 CreateFromDirectory/ExtractToDirectory/OpenRead 各自的用途。 ↩ ↩2
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
PowerShell 脚本的参数设计与模块化 ── 从「能运行的脚本」到「可以交给别人的脚本」
本文整理把 PowerShell 脚本提升到可以交给他人使用的品质的具体步骤,讲解 param 块与 [CmdletBinding()]、输入验证、管道输入、-WhatIf 支持、.psm1 模块化,直至内部共享与 Git 管理的要点。
用 PowerShell 自动化 Excel・CSV 业务处理 ── 汇总・核对・报表输出的实务方案
用 PowerShell 自动化 CSV 汇总、核对与 Excel 报表输出的实务方案。讲解 Import-Csv/Export-Csv 的默认字符编码(5.1 与 7 的差异)、用 Group-Object 汇总、用 Compare-Object 与哈希表核对,以及 Im...
用 winget + PowerShell 自动化 PC 装机 ── 让操作手册可执行
本文整理了让新员工电脑的初始设置具备可复现性的方法,涵盖通过 winget 进行应用安装与 export/import、WinGet Configuration 的声明式配置、用 PowerShell 补充的设置,直至无人值守执行时的注意事项。
PowerShell脚本运行慢时该看哪里 ── 数组・管道・匹配的诀窍
整理 PowerShell 脚本变慢的常见原因。从数组 += 导致 O(n^2) 的原理、管道与 foreach 的差异、匹配的哈希表化、文件 I/O 的改善,到正确的测量方法,从实务角度进行讲解。
不再使用 Write-Host ── PowerShell 的输出流与日志设计
本文整理 PowerShell 六个输出流的用法区分、Write-Host 存在的问题与正确的使用场景、函数返回值被污染的原因、通过 -Verbose 与 -InformationVariable 实现调用方控制,以及结构化日志的留存方法。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
ActiveX 迁移
整理保留、包装或替换 COM / ActiveX / OCX 资产的阶段性判断的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
既有资产活用 & 迁移支持
在持续活用 COM / ActiveX / OCX 资产、原生代码与 32 位依赖的同时,协助规划阶段性的迁移。
常见问题
汇总了咨询这一主题时常见的问题。
- PowerShell 能直接调用 .NET 的类吗?
- 可以。PowerShell 构建在 .NET 之上,因此可以像 [System.IO.Path]::GetFileNameWithoutExtension() 这样用方括号写出类型名,用 :: 调用静态方法。如果需要实例,可以用 New-Object,或者在 PowerShell 5.0 及以后使用 [类型]::new() 创建。即使某个操作没有对应的 cmdlet,只要 .NET 类库中存在,就能在不额外安装的情况下从脚本中直接调用。
- New-Object 与 [类型]::new() 应该用哪一个?
- 两者都能创建对象,但 [类型]::new() 不带参数输入时会显示该类型的构造函数列表,适合一边查阅参数一边编写代码。在性能上 [类型]::new() 也更占优势。不过创建 COM 对象只能用 New-Object -ComObject。如果要兼顾比 PowerShell 5.0 更旧的环境,同样应使用 New-Object。
- 在 PowerShell 中通过 COM 操作 Excel,为什么 EXCEL.EXE 会残留?
- COM 对象的寿命通过引用计数管理,在 .NET 一侧则由 RCW(Runtime Callable Wrapper)握有该引用。像 $excel.Workbooks.Open() 这样书写时,中间的 Workbooks 对象也会生成一个看不见的 RCW,留下没有存入变量的引用。即使调用了 Quit(),只要引用还残留着,进程就不会终止。使用结束后需要用 Marshal.ReleaseComObject 明确释放,或者把变量设为 null,再用 GC.Collect() 与 WaitForPendingFinalizers() 进行回收,做好后续处理。
- 可以在服务器或夜间批处理中通过 COM 操作 Excel 吗?
- 不推荐这样做。Microsoft 官方明确表示,不推荐也不支持从无人值守、非交互式的客户端应用或组件中自动化 Office 应用程序。Office 的设计前提是交互式的桌面使用,在无人执行环境下可能出现不稳定行为或死锁。无人处理的标准做法是替换为 Open XML 系列库或 CSV 联动等不启动 Office 软件本体的方式。
- Add-Type 能调用 Win32 API 吗?
- 可以。向 Add-Type 的 -MemberDefinition 传入带 DllImport 的 C# 签名,就会当场编译,并可通过 P/Invoke 调用 Win32 API。官方文档中也刊载了调用 user32.dll 的 ShowWindowAsync 的示例。不过添加的类型会一直留在该会话中,同名类型无法重新定义,因此反复试错时请在新会话中重新执行。签名写错时甚至会导致整个进程崩溃,因此建议在可随时丢弃的控制台中进行验证。
- Windows PowerShell 5.1 与 PowerShell 7 在调用 .NET 时有区别吗?
- 有区别。5.1 构建在 .NET Framework 4.5 系列之上,PowerShell 7 构建在 .NET(原 .NET Core)之上,可用的类型与方法重载并不相同。例如 String.Split 的重载在 7 中更多,官方也介绍了同一段代码结果不同的例子。此外,在 5.1 中许多场景需要用 Add-Type 显式加载 GAC 中的程序集,而在 7 中随附的程序集会在需要时自动加载。需要在两个环境中都运行的脚本,请务必在两边都进行测试。