「每个月把从核心系统导出的 CSV 用 Excel 打开,用数据透视表汇总,整理格式后发邮件」「把两个系统导出的名单相互核对,靠肉眼寻找差异」── 我们经常收到这类定型作业的咨询。哪怕每次只花 30 分钟,只要每周・每月・多人重复进行,一年下来消耗的时间也相当可观,而且手工复制粘贴必然会混入差错。
PowerShell 是与这个领域相性很好的工具。它是 Windows 标准自带的(Windows PowerShell 5.1),可以把 CSV 作为「对象的表格」读入,用管道把汇总・核对・输出串联起来。再加上社区制作的 ImportExcel 模块,不需要 Excel 本体也能生成 xlsx 报表。
另一方面,这个领域也存在日语环境特有的地雷。由于 Windows PowerShell 5.1 与 PowerShell 7 的默认字符编码完全不同,「在自己的电脑上明明能正常运行,换到其他环境却出现乱码」的情况屡见不鲜。本文将按照从字符编码陷阱开始的顺序,整理中小企业信息系统・业务负责人用 PowerShell 自动化 CSV・Excel 业务时的实务方案。
前提知识: 本文面向中小企业的信息系统・业务负责人,但并非 PowerShell 的入门文章。变量与管道、foreach 和 if 的写法、创建并执行脚本文件(.ps1)的步骤,都视为已知内容,不再赘述。如果对这些还没有把握,请先阅读《PowerShell 命令基础 ── 应先掌握的操作与安全使用方法》。文中也会出现哈希表(@{})、try/finally、COM 引用释放等中级以上的写法,但每次出现时都会附上「为什么要这样写」的说明。目标执行环境同时涵盖 Windows PowerShell 5.1 与 PowerShell 7,行为不同之处会逐一加以区分。
1. 先说结论
- 读写 CSV 时,请始终显式指定 -Encoding。因为 Windows PowerShell 5.1 的默认编码因命令而异,而 PowerShell 7 统一为不带 BOM 的 UTF-8,两者的默认值完全不同。1
- 5.1 中 Export-Csv 的默认编码是 ASCII。如果忘记加上 -Encoding,日语在保存时就会丢失。此外,5.1 的 Import-Csv 会把没有 BOM 的文件解释为 UTF-8,因此直接读取 Shift_JIS 的 CSV 会出现乱码。1
- 汇总的基本形式是 Group-Object(分组)与 Measure-Object(求和・平均・最大最小)的组合。每次在 Excel 数据透视表中做的许多汇总,都可以用这两个命令替代。23
- 核对两个 CSV 时,如果只看有无差异用 Compare-Object,如果还需要列的引用(合并)则用哈希表。Compare-Object 会用 SideIndicator 显示某一行只存在于哪一方。4
- 读写 xlsx 时,首选社区制作的 ImportExcel 模块。无需安装 Excel 本体,就能实现表格化、格式设置乃至数据透视表。5
- 对 Excel 本体进行 COM 操作是最后的手段。因为引用(RCW)释放不彻底容易导致 EXCEL.EXE 残留67,而且 Microsoft 本来就不推荐、也不支持在无人环境(服务或计划任务)中进行 Office 自动化。8
- 在放入任务计划程序之前,请明确固定编码・路径・执行环境这三点。交互执行时能正常工作的脚本,在定期执行中出问题的原因大多集中在这三者上。18
2. Import-Csv/Export-Csv 的基础 ── 最大的地雷是字符编码
Import-Csv 会把 CSV 读入为「一行=一个对象、一列=一个属性」的表格。标题行会成为列名,之后的处理都可以用属性名来书写。9 Export-Csv 则相反,会把对象的列写出为 CSV。10 到这里都很简单。问题在于字符编码。
正如官方文档中明确记载的那样,Windows PowerShell 5.1 的默认编码在各个命令之间并不一致。1 下表整理了会影响日语环境实务的部分。
| 操作 | Windows PowerShell 5.1 的默认值 | PowerShell 7 的默认值 |
|---|---|---|
| Export-Csv | ASCII(日语会丢失)1 | 不带 BOM 的 UTF-810 |
| Import-Csv(无 BOM 文件) | 解释为 UTF-81 | 不带 BOM 的 UTF-8 |
| Get-Content(无 BOM 文件) | ANSI=日语环境下为 Shift_JIS1 | 不带 BOM 的 UTF-8 |
Out-File・重定向(>) |
UTF-16LE(带 BOM)1 | 不带 BOM 的 UTF-8 |
也就是说,在 5.1 中,「用 Get-Content 读取时能按 Shift_JIS 正常读出,但用 Import-Csv 却会乱码」「用 Export-Csv 保存后日语全部变成了 ?」,这些都是按规范发生的现象。PowerShell 7 统一采用不带 BOM 的 UTF-8,行为一致1,但这次变成了按默认方式读取以 Shift_JIS 送来的核心系统 CSV 会出现乱码。结论只有一个:在读写两侧都要显式指定 -Encoding。
# 读取核心系统输出的 Shift_JIS CSV
# Windows PowerShell 5.1: Default = 系统的 ANSI 代码页(日语环境下为 Shift_JIS)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding Default
# 在 PowerShell 7 中可以用代码页编号指定(932 = Shift_JIS)
# $orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932
# 输出设为带 BOM 的 UTF-8,即使双击用 Excel 打开也不容易乱码
# PowerShell 7: UTF8 会变成不带 BOM,因此带 BOM 时要显式指定 utf8BOM
$orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding utf8BOM
# Windows PowerShell 5.1: 不存在 utf8BOM 这个值。指定 UTF8 即可得到带 BOM 的结果
# $orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding UTF8
PowerShell 6.2 及以后版本的 -Encoding 也可以用代码页编号(932)或注册名称来指定,7.4 及以后还可以使用 ansi 这个值。1 另外,-NoTypeInformation 是用来抑制 5.1 中开头会附加的 #TYPE 行的,PowerShell 6 以后默认已不再附加,因此不需要指定(加上也不会报错)。10 对于要同时在 5.1 和 7 上运行的脚本,加上它比较稳妥。
CSV 这种格式本身的陷阱(用 Excel 打开会丢失前导零、包含逗号或换行符的值、注入攻击防范)在《CSV 不是「纯文本」》中有详细说明。字符编码与换行符的基础知识请参考《Windows 的文字编码与换行符》。
为了让读者仅凭本文也能做出判断,先把委托给其他文章的要点简要写在这里。不要为了确认而用 Excel 双击打开 CSV(会出现前导零丢失、长数字被科学记数法表示的问题,明明是想确认,看到的却是被破坏后的值。要查看内容请用文本编辑器或 Import-Csv)。包含逗号・换行符・双引号的值需要用引号包围(Export-Csv 会自动完成这个处理,不要自行拼接字符串来组装 CSV)。以 = 开头的值可能会被电子表格软件解释为公式(不要不加防备地打开从外部接收到的 CSV)。至于换行符,从非 Windows 或旧设备送来的文件有时是 LF,Import-Csv 可以正常读取,但自己编写 -split 处理时,请不要以 CRLF 为前提。
3. 汇总 ── 用 Group-Object 和 Measure-Object 实现相当于数据透视表的效果
像「按部门统计件数与金额合计」这样的汇总,基本形式是先用 Group-Object 分组,再对各组用 Measure-Object 求和。23
首先确定输入内容。以下方案以这样一份 orders.csv(假设为核心系统的输出,字符编码为 Shift_JIS)为前提。
OrderNo,Date,Dept,Customer,Amount
1001,2026/07/01,销售一课,A公司,120000
1002,2026/07/01,销售二课,B公司,80000
1003,2026/07/02,销售一课,C公司,45000
1004,2026/07/03,管理部,A公司,15000
1005,2026/07/03,销售二课,D公司,230000
# 用 PowerShell 7 读取 Shift_JIS 的 CSV,因此显式指定代码页 932
# (7 的 -Encoding Default 意味着 UTF-8,会导致乱码。若在 5.1 上运行则指定 Default)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932
# 按部门统计件数与金额合计。
# Import-Csv 的值全部是字符串,因此转换为 [decimal] 后再求和是关键
# (Measure-Object -Sum 的 Sum 会是 Double 类型,因此金额保持 decimal 自行累加,
# 避免大额合计或小数精度受损)
$summary = $orders | Group-Object -Property Dept | ForEach-Object {
$total = [decimal]0
foreach ($row in $_.Group) { $total += [decimal]$row.Amount }
[pscustomobject]@{
Dept = $_.Name # 分组键的值
Count = $_.Count # 件数
Total = $total # 金额合计
}
}
$summary | Sort-Object -Property Total -Descending |
Export-Csv -LiteralPath 'C:\data\summary.csv' -NoTypeInformation -Encoding UTF8
针对上面这 5 行数据,$summary(排序后)所持有的值如下所示。summary.csv 中会带着标题行写出这 3 行。
| Dept | Count | Total |
|---|---|---|
| 销售二课 | 2 | 310000 |
| 销售一课 | 2 | 165000 |
| 管理部 | 1 | 15000 |
照抄代码进行验证时,请先确认是否输出了这 3 行。如果金额对不上・件数对不上,原因通常是部门名称的书写不一致(全角空格或写成「营业1课」这类),或者是接下来要讲的类型转换问题。
容易踩坑的地方在于CSV 的值全部是字符串。因为 Import-Csv 返回的是字符串属性的集合9,所以在把本来是数值的内容传给 Measure-Object -Sum 之前,要显式转换为 [decimal]。即使忘记转换,在 5.1 上有时也能勉强运行,容易变成金额位数出错之后才被发现的事故。
Measure-Object 除了 -Sum 之外,还可以同时获取 -Average、-Maximum、-Minimum。3 另外,使用 Group-Object -AsHashTable 可以直接得到「键 → 该组行数组」这样的哈希表,也可以应用到后面讲的核对处理中。2 这类单条命令的使用场景,整理在《PowerShell 实用命令合集》中。
4. 核对 ── Compare-Object 与哈希表合并的使用场景划分
4.1. 只看有无差异时用 Compare-Object
从昨天和今天的名单 CSV 中找出「新增的人・消失的人」,这种典型的核对场景用 Compare-Object 最快捷。指定 -Property 为键列后,就只按该列的值进行比较,通过结果中 SideIndicator 是 =>(只存在于差异一侧)还是 <=(只存在于基准一侧),就能判断出增减情况。4
# 之所以用 @() 括起来,是因为在 0 件或空文件时 Import-Csv 的结果可能会是 $null。
# 如果 ReferenceObject/DifferenceObject 是 $null,Compare-Object 会以终止错误停止
$yesterday = @(Import-Csv -LiteralPath '.\users_0716.csv' -Encoding UTF8)
$today = @(Import-Csv -LiteralPath '.\users_0717.csv' -Encoding UTF8)
# 只按员工编号比较。=> 表示只存在于今天的行(新增),<= 表示只存在于昨天的行(删除)
Compare-Object -ReferenceObject $yesterday -DifferenceObject $today -Property EmpNo |
Sort-Object -Property EmpNo |
Format-Table -Property EmpNo, SideIndicator
需要注意两点。第一,指定 -Property 后,结果中只会保留该列和 SideIndicator,如果还想查看姓名等其他列,需要用结果中的键重新引用原始数据。第二,如果基准侧・差异侧中有一方是 $null(不是 0 行,而是 null),就会因错误而停止。4 上面代码中把读取结果用 @() 括起来,正是为了应对这一点。即使是 0 件的 CSV,也会变成空数组,因此即便遇到「今天所有人都离职了(=全部行都以 <= 输出)」这种极端情况,也能作为差异结果正常接收,而不会变成格式错误。
4.2. 需要列的引用(合并)时用哈希表
「在明细 CSV 的员工编号上,从主数据 CSV 中引用姓名与部门」这种相当于 SQL 中 JOIN 的操作,定式做法是先把主数据一侧做成键 → 行的哈希表,再逐行引用。双重循环(明细 × 主数据的暴力枚举)在数千条 × 数千条的规模下会明显变慢,但用哈希表的话,即使有数万条记录也能以实用的速度处理完。
以下面这两份数据作为输入。
master.csv
EmpNo,Name,Dept
E001,张三,销售一课
E002,李四,管理部
details.csv
EmpNo,Amount
E001,120000
E003,45000
# 把主数据一侧做成「员工编号 → 行」的哈希表
# 如果键重复,会被后面的行覆盖,若可能存在重复请事先检查
$master = @{}
foreach ($row in (Import-Csv -LiteralPath '.\master.csv' -Encoding UTF8)) {
$master[$row.EmpNo] = $row
}
# 逐行引用明细。找不到的行不要悄悄丢弃,转存到单独的文件中
$unmatched = New-Object System.Collections.Generic.List[object]
$joined = foreach ($row in (Import-Csv -LiteralPath '.\details.csv' -Encoding UTF8)) {
$hit = $master[$row.EmpNo]
if ($null -eq $hit) {
$unmatched.Add($row)
continue
}
[pscustomobject]@{
EmpNo = $row.EmpNo
Name = $hit.Name
Dept = $hit.Dept
Amount = $row.Amount
}
}
$joined | Export-Csv -LiteralPath '.\joined.csv' -NoTypeInformation -Encoding UTF8
$unmatched | Export-Csv -LiteralPath '.\unmatched.csv' -NoTypeInformation -Encoding UTF8
以这份输入为例,joined.csv 中会输出主数据里存在的 E001 这一行(E001,张三,销售一课,120000),unmatched.csv 中会输出主数据里不存在的 E003 这一行(E003,45000)。
这份方案的关键在最后两行。不要悄悄丢弃引用不到键的行。核对业务的价值就在于「让人能够确认对不上的部分」,所以务必要输出 unmatched,并把件数记录到日志或标准输出中。以上面的例子来说,能否注意到 unmatched.csv 中出现了 E003,正是运行这套处理的意义所在。
4.3. 列名的差异与标题行的处理
实际业务中的 CSV,列名往往会在「社員番号」「社員No」「emp_no」之间摇摆不定。应对方法归结为一点:在读入之后立刻规范化为内部名称。只要之后的处理都只用内部名称来书写,即使格式发生变化,需要修改的地方也只集中在规范化这一处。
# 没有标题行的 CSV 用 -Header 赋予列名(第 1 行也会被当作数据读入)
# 字符编码的思路与第 2 章相同: Shift_JIS 的话,在 7 中指定 932,在 5.1 中指定 Default
$rows = Import-Csv -LiteralPath '.\no_header.csv' -Header 'EmpNo', 'Name', 'Dept' -Encoding 932
# 日语标题的 CSV,在读入之后立刻规范化为英文内部名称
$normalized = Import-Csv -LiteralPath '.\jinji.csv' -Encoding 932 |
Select-Object -Property @{ Name = 'EmpNo'; Expression = { $_.'社員番号' } },
@{ Name = 'Name'; Expression = { $_.'氏名' } },
@{ Name = 'Dept'; Expression = { $_.'所属部署' } }
-Header 用于没有标题行的文件,需要注意的是,一旦指定它,第 1 行也会被当作数据读入。9 另外,如果标题行中有空白,PowerShell 会自动分配类似 H1 这样的临时列名9,因此当无法用预期的列名引用属性时,首先应怀疑标题行本身。
5. 处理 xlsx ── 首选 ImportExcel,COM 是最后手段
5.1. ImportExcel 模块 ── 不需要 Excel 本体即可读写 xlsx
日本的职场经常要求「汇总结果不要用 CSV,而要用 Excel 文件,并且给表格上色」。此时的首选,就是发布在 PowerShell Gallery 上、由社区制作的 ImportExcel 模块。它无需安装 Excel 本体即可读写 xlsx,还能实现表格化、列宽调整乃至数据透视表。5
# 只需首次执行。从 PowerShell Gallery 面向当前用户安装(不需要管理员权限)
# 如果 Windows PowerShell 5.1 无法连接 Gallery,先启用 TLS 1.2 再执行
# [Net.ServicePointManager]::SecurityProtocol =
# [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-Module -Name ImportExcel -Scope CurrentUser
# 确认是否安装成功及其版本
Get-InstalledModule -Name ImportExcel | Select-Object -Property Name, Version
# 读取 xlsx(与 Import-Csv 的感觉相同,工作表中的表格会变成对象数组)
$budget = Import-Excel -Path 'C:\data\budget.xlsx' -WorksheetName '预算'
# 把第 3 章的汇总结果,以表格+列宽自动调整+带数据透视表的 xlsx 形式输出
$summary | Export-Excel -Path 'C:\data\monthly-report.xlsx' `
-WorksheetName '汇总' -TableName 'Summary' -AutoSize `
-IncludePivotTable -PivotRows Dept -PivotData @{ Total = 'Sum' }
在导入环节容易卡住的地方有两点。一是与 PowerShell Gallery 的连接:由于使用 Gallery 需要 TLS 1.2 以上,在 Windows PowerShell 5.1 中如果不像上面的注释那样在会话中显式启用,Install-Module 有时会失败(如果觉得每次都写很麻烦,可以把这一行放进配置文件脚本中)。11 二是版本确认:这个模块支持哪些 PowerShell 版本,在发布页面上并没有明确说明。5 如果要在 5.1 与 7 混用的公司内部使用,请先在实际运行所用的那个 PowerShell 上跑一遍 Export-Excel,确认文件能够正常打开之后再进行推广(如果要在任务计划程序中运行,要用与任务启动时相同的执行账户来确认。用 -Scope CurrentUser 安装的模块,只有该用户才能看到)。
既然是社区制作的模块,导入时请按照公司内部的软件引入规则进行确认(模块的获取来源是 PowerShell Gallery5)。即便如此,与「在服务器上安装 Excel 并用 COM 来运行」的方案相比,无论是许可证、稳定性还是维护性,都是更合理的选择。报表生成方式的比较(COM / Open XML / 模板)整理在《Excel 报表输出的实现方法》中。
5.2. Excel COM 操作 ── 要用就要把释放处理写全,且不要用于无人执行
只有在需要触发既有 xls 的宏、或者需要 Excel 本身的功能(重新计算、打印、解析名称定义等)时,才用 COM 来操作 Excel 本体。从 PowerShell 可以用 New-Object -ComObject 启动,但有一个有名的问题就是EXCEL.EXE 的进程残留。如果对 COM 对象的引用(RCW)没有释放,即使调用 Quit(),进程也会残留下来。6 .NET 侧的应对方法是通过 Marshal.ReleaseComObject 显式释放。7
# COM 是最后的手段。用 finally 保证「打开的东西必须关闭、引用必须释放」
# Excel 启动后的所有 COM 调用都放在 try 中(这样即使启动后的第一行就抛出异常,后续处理也会执行)
$excel = New-Object -ComObject Excel.Application
$book = $null
try {
$excel.Visible = $false
$excel.DisplayAlerts = $false # 避免被确认对话框卡住
$book = $excel.Workbooks.Open('C:\data\template.xlsx')
$sheet = $book.Worksheets.Item(1)
$sheet.Range('B2').Value2 = 12345
$book.SaveAs('C:\data\output.xlsx')
$book.Close($false)
}
finally {
# 即使 Quit 失败(比如 Excel 无响应),释放和 GC 也必须执行
try {
$excel.Quit()
}
finally {
# 接触过的 COM 对象如果不显式释放 RCW,EXCEL.EXE 就容易残留
if ($sheet) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($sheet) }
if ($book) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($book) }
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
}
麻烦的是,像 $excel.Workbooks.Open(...) 这样仅仅用点号连接,就会产生对中间对象(这个例子中是 Workbooks 集合)的引用,而它同样属于需要释放的对象。上面的代码严格来说也残留着对 Workbooks 的引用,如果要做到万无一失,中间对象也应该接到变量中再释放。这种「把引用管理写全的难度」本身,正是把 COM 定位为最后手段的原因。机制的细节与替换判断,在《C# 操作 Excel 时 EXCEL.EXE 残留的问题》中有说明;从 PowerShell 调用 COM/.NET 的整体内容,则在同期发布的《从 PowerShell 调用 COM 与 .NET 的实践》中讨论。
而且还有一项决定性的限制。Microsoft 不推荐、也不支持从无人・非交互式客户端(服务、计划执行等)进行 Office 自动化。官方明确说明,Office 是以存在交互用户为前提设计的,在无人环境中可能会出现不稳定的行为或死锁。8 SSIS 的文档中也建议,在无人执行环境中不要使用 Excel 连接,而应替换为 CSV 或 Open XML 系的方式。12 「每晚用任务计划程序打开 Excel 制作报表」这种方案,即便看起来能正常运行,也是建在沙地上的楼阁。想要定期执行的处理,请转向 ImportExcel 或 Open XML 系的方式。
6. 放入任务计划程序定期执行前的核对清单
把手边能正常运行的脚本原封不动地放进任务计划程序,大多会因为以下几点之一而出问题。
- 字符编码: 交互执行与计划执行的行为本身不会改变,但「碰巧在手边的 7 上能正常运行」的脚本,因为任务侧的启动命令是
powershell.exe(=5.1),导致默认编码发生变化而出现乱码,是常见的事故。正如第 2 章所述,只要在读写两侧都显式指定-Encoding,无论用哪一个启动,结果都会保持一致。1 - 路径: 不要使用依赖当前目录的相对路径,应使用绝对路径或以
$PSScriptRoot为基准来书写。网络驱动器(如 Z: 等)在计划执行的会话中是不可见的,因此要改用 UNC 路径。 - 执行环境: 要在启动命令中明确指定用哪个 PowerShell 来运行(powershell.exe 还是 pwsh.exe)。5.1 与 7 的差异及使用场景划分,请参考同期发布的《Windows PowerShell 5.1 与 PowerShell 7 的区别》。
- 不要放入包含 Excel COM 的处理: 正如前一章所述,无人执行不受支持。8
任务计划程序执行时日志・证据的留存方法,在《PowerShell 脚本进阶 ── 安全地实现日志排查、归档与报表自动化》中有详细说明。
7. 实务定式(判断表)
| 论点 | 选项 | 判断标准 |
|---|---|---|
| CSV 的字符编码 | 交给默认值 / 显式指定 -Encoding | 始终显式指定。因为 5.1 与 7 的默认值不同,从结构上防止「环境一变就乱码」1 |
| 汇总 | 用 Excel 手工处理 / Group-Object+Measure-Object | 每周・每月重复的话就脚本化。步骤以代码形式保留,可以重现23 |
| 两个 CSV 的核对 | Compare-Object / 哈希表合并 | 只看有无差异用 Compare-Object。需要引用其他列用哈希表。不一致的行务必单独输出4 |
| xlsx 输出 | 用 CSV 应付 / ImportExcel / COM | 需要格式・数据透视表用 ImportExcel(无需 Excel 本体)。COM 仅限于需要执行宏等 Excel 本身功能的场合5 |
| Excel COM 的执行形态 | 在任务计划程序中无人执行 / 仅限手动・交互执行 | 无人环境下的 Office 自动化不受支持。想要无人化的处理应转向不需要 Excel 本体的方式8 |
| 定期执行 | 每次手动执行 / 任务计划程序 | 先明确 -Encoding・绝对路径・所启动的 PowerShell 这三点,再将其纳入任务 |
8. 总结
- CSV 自动化中最大的地雷是字符编码。5.1 的默认值因命令而异(Export-Csv 是 ASCII),7 统一为不带 BOM 的 UTF-8。只要在读写两侧都显式指定 -Encoding,就能消除环境差异。
- 汇总的基本形式是 Group-Object+Measure-Object。CSV 的值全部是字符串,所以要先转换为数值再汇总。
- 核对方面,只看有无差异用 Compare-Object,需要合并则用哈希表。无法引用成功的行务必输出到单独的文件中,供人工确认。
- 列名的差异通过读入后立即规范化来吸收,之后的处理只用内部名称来书写。
- xlsx 可以用 ImportExcel 模块在没有 Excel 本体的情况下读写。COM 是在能够把释放处理写全时才使用的最后手段,不要用于无人执行。
- 放入任务计划程序之前,请明确固定编码・路径・所启动的 PowerShell。
相关文章
- CSV 不是「纯文本」──C# 业务应用中 CSV 实务(字符编码・Excel 兼容性・注入攻击防范)
- PowerShell 实用命令合集 —— 积累日常工作中常用的小工具
- Excel 报表输出的实现方法 - COM / Open XML / 模板
- C# 操作 Excel 时 EXCEL.EXE 残留的问题 ── COM 引用释放模式与替换判断
- Windows 的文字编码与换行符 - 乱码与 CRLF/LF 的基础
- Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南
相关咨询领域
合同会社小村软件承接通过 CSV・Excel 进行的定型业务自动化脚本编写、摆脱依赖 Excel COM 的报表处理(替换为 ImportExcel/Open XML),以及「只在特定环境下出现乱码」「EXCEL.EXE 残留」等故障的调查。
参考链接
-
Microsoft Learn,about_Character_Encoding。关于 Windows PowerShell 5.1 的默认编码因命令而异(Export-Csv 是 ASCII,Set-Content/Get-Content 是 ANSI 的 Default,Out-File 与重定向是 UTF-16LE,无 BOM 文件的 Import-Csv 按 UTF-8 解释)、PowerShell 6 以后统一以不带 BOM 的 UTF-8 为默认值、6.2 以后可以用代码页编号・注册名称指定 -Encoding、7.4 的 ansi 值等内容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12
-
Microsoft Learn,Group-Object。关于 Group-Object 会按指定属性的值对对象分组,并返回各组的件数与元素、用 -AsHashTable 可以得到键 → 组的哈希表等内容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Measure-Object。关于用 Measure-Object 除了件数之外,还可以计算 Sum・Average・Maximum・Minimum(以及标准差)等内容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Compare-Object。关于 Compare-Object 会比较两个对象集合,并用 SideIndicator(<= / => / ==)显示只存在于哪一侧、可以只比较用 -Property 指定的列、当参照侧・差异侧为 null 时会发生终止错误等内容。 ↩ ↩2 ↩3 ↩4
-
PowerShell Gallery,ImportExcel。社区制作的 ImportExcel 模块的发布来源。关于这是一个无需安装 Excel 本体即可进行 xlsx 读写・数据透视表・格式设置等操作的模块、用 Install-Module 安装的方法、发布页面上没有记载最低要求的 PowerShell 版本等内容。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,An active Excel process continues to run after using a VBA macro to programmatically quit Excel。关于即使关闭工作簿、调用 Quit 并清除引用,只要 Excel 或其成员的引用仍被全局持有,EXCEL.EXE 进程就会持续残留等内容。 ↩ ↩2
-
Microsoft Learn,Marshal.ReleaseComObject(Object) Method。关于 ReleaseComObject 用于减少与 COM 对象关联的 RCW(运行时可调用包装器)的引用计数,从而显式控制 COM 对象的生命周期、访问已释放的对象会引发异常因此使用时需要注意等内容。 ↩ ↩2
-
Microsoft Learn,Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment。关于 Microsoft 不推荐、也不支持从无人・非交互式客户端(ASP、DCOM、NT 服务等)进行 Office 应用程序自动化、在无人环境中 Office 可能出现不稳定行为或死锁、推荐使用 Open XML 等替代方案等内容。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,Import-Csv。关于 Import-Csv 会从 CSV 创建表格形式的自定义对象、第 1 行会被解释为标题行、用 -Header 可以为没有标题行的文件赋予列名、标题的空白处会被分配以 H 开头的临时列名、值会被读入为字符串等内容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Export-Csv。关于 Export-Csv 会把对象的各个属性写出为 CSV 的列、PowerShell 7 中的默认编码是 UTF8NoBOM、PowerShell 6.0 及以后默认不再输出 #TYPE 行、NoTypeInformation 隐式生效等内容。 ↩ ↩2 ↩3
-
Microsoft Learn,Install a package manager for PowerShell。关于访问 PowerShell Gallery 需要传输层安全性(TLS)1.2 以上、在会话中启用 TLS 1.2 的命令,以及把它写进配置文件脚本的方法等内容。 ↩
-
Microsoft Learn,Import data from Excel or export data to Excel with SQL Server Integration Services (SSIS)。关于在无人・非交互式环境中使用 Excel 组件不受支持、在生产环境的自动化处理中建议替换为平面文件(CSV)或基于 Open XML 的方式等内容。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
从 PowerShell 调用 COM 与 .NET 的实践 ── 一举拓宽脚本触达的范围
本文从实务角度讲解从 PowerShell 调用 .NET 类的方法、通过 Add-Type 嵌入 C# 与 Win32 API、COM 操作、Excel 进程残留与后续处理、Office 无人执行不受支持的原因,以及 5.1 与 7 之间的差异。
PowerShell 脚本的参数设计与模块化 ── 从「能运行的脚本」到「可以交给别人的脚本」
本文整理把 PowerShell 脚本提升到可以交给他人使用的品质的具体步骤,讲解 param 块与 [CmdletBinding()]、输入验证、管道输入、-WhatIf 支持、.psm1 模块化,直至内部共享与 Git 管理的要点。
Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南
整理 Windows PowerShell 5.1 与 PowerShell 7 的关系(共存与 pwsh.exe)、5.1 不再新增功能的官方方针、编码差异导致的乱码、用 #Requires 进行防御,直至任务计划程序更新的迁移步骤。
PowerShell 实用命令合集 —— 积累日常工作中常用的小工具
本文整理 PowerShell 日常工作中常用的实用命令,说明 Measure-Object、Group-Object、Select-String、Compare-Object、Tee-Object、Start-Transcript 等命令的使用场景和时机。
用 winget + PowerShell 自动化 PC 装机 ── 让操作手册可执行
本文整理了让新员工电脑的初始设置具备可复现性的方法,涵盖通过 winget 进行应用安装与 export/import、WinGet Configuration 的声明式配置、用 PowerShell 补充的设置,直至无人值守执行时的注意事项。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
既有资产活用 & 迁移支持
在持续活用 COM / ActiveX / OCX 资产、原生代码与 32 位依赖的同时,协助规划阶段性的迁移。
常见问题
汇总了咨询这一主题时常见的问题。
- 为什么用 PowerShell 读写 CSV 会出现乱码?
- 这是因为 Windows PowerShell 5.1 与 PowerShell 7 的默认字符编码不同。5.1 的默认值因命令而异:Export-Csv 是 ASCII(日语会丢失),无 BOM 文件的 Import-Csv 按 UTF-8 解释,Get-Content 是 ANSI(日语环境下为 Shift_JIS)。7 统一以不带 BOM 的 UTF-8 为默认值。唯一安全的做法是在读写两侧都必须显式指定 -Encoding。
- 该如何核对(检查差异)两个 CSV?
- 如果只想看「有无差异」,用 Compare-Object 配合 -Property 指定键列最为简便。可以通过 SideIndicator 分辨新增的行(=>)与消失的行(<=)。如果还想从另一个 CSV 中引用姓名或部门等列并进行合并,定式做法是先把主数据一侧做成键→行的哈希表,再逐行引用,即使有数万条记录也能高速处理。找不到对应键的行不要悄悄丢弃,应输出到单独的文件中供人工确认。
- 用 PowerShell 创建 Excel 文件(xlsx)需要安装 Excel 本体吗?
- 不需要。使用社区制作的 ImportExcel 模块,即使在没有安装 Excel 的机器上,也能实现 xlsx 的读写、表格化、格式设置乃至数据透视表的创建。通过 PowerShell Gallery 用 Install-Module 安装。用 COM 操作 Excel 本体的方式,应当定位为只在需要执行宏等「Excel 本身功能」时才使用的最后手段,这样比较安全。
- 为什么用 PowerShell 对 Excel 进行 COM 操作会导致 EXCEL.EXE 残留?
- 这是因为如果对 COM 对象的引用(RCW)一直没有释放,即使调用 Quit,Excel 的进程也不会终止。每次接触工作簿或单元格区域,都会增加对中间对象的引用,因此用完之后要用 Marshal.ReleaseComObject 显式释放,并用 GC.Collect 促使其回收。此外,由于 Microsoft 不推荐、也不支持在任务计划程序等无人环境中进行 Office 自动化,想要定期执行的处理应该转向 ImportExcel 等不需要 Excel 本体的方式。
- CSV 的汇总应该用 Excel 的数据透视表,还是用 PowerShell 来做?
- 如果只是一次性的分析,用 Excel 就足够了。如果每周・每月都要重复相同的步骤,那就值得用 Group-Object 和 Measure-Object 把它脚本化。因为步骤会以代码而不是文档的形式保留下来,即使负责人更换也能重现相同的结果,还能接入任务计划程序进行定期执行。把汇总结果用 ImportExcel 输出为 xlsx,接收方也可以像平常一样当作 Excel 文件来处理。