用 PowerShell 自动化 Excel・CSV 业务处理 ── 汇总・核对・报表输出的实务方案

· · PowerShell, Windows, CSV, Excel, 自动化, 业务效率化, 脚本, 现有资产利用

「每个月把从核心系统导出的 CSV 用 Excel 打开,用数据透视表汇总,整理格式后发邮件」「把两个系统导出的名单相互核对,靠肉眼寻找差异」── 我们经常收到这类定型作业的咨询。哪怕每次只花 30 分钟,只要每周・每月・多人重复进行,一年下来消耗的时间也相当可观,而且手工复制粘贴必然会混入差错。

PowerShell 是与这个领域相性很好的工具。它是 Windows 标准自带的(Windows PowerShell 5.1),可以把 CSV 作为「对象的表格」读入,用管道把汇总・核对・输出串联起来。再加上社区制作的 ImportExcel 模块,不需要 Excel 本体也能生成 xlsx 报表。

另一方面,这个领域也存在日语环境特有的地雷。由于 Windows PowerShell 5.1 与 PowerShell 7 的默认字符编码完全不同,「在自己的电脑上明明能正常运行,换到其他环境却出现乱码」的情况屡见不鲜。本文将按照从字符编码陷阱开始的顺序,整理中小企业信息系统・业务负责人用 PowerShell 自动化 CSV・Excel 业务时的实务方案。

前提知识: 本文面向中小企业的信息系统・业务负责人,但并非 PowerShell 的入门文章。变量与管道、foreachif 的写法、创建并执行脚本文件(.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-Minimum3 另外,使用 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・Excel 进行的定型业务自动化脚本编写、摆脱依赖 Excel COM 的报表处理(替换为 ImportExcel/Open XML),以及「只在特定环境下出现乱码」「EXCEL.EXE 残留」等故障的调查。

参考链接

  1. 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

  2. Microsoft Learn,Group-Object。关于 Group-Object 会按指定属性的值对对象分组,并返回各组的件数与元素、用 -AsHashTable 可以得到键 → 组的哈希表等内容。  2 3 4

  3. Microsoft Learn,Measure-Object。关于用 Measure-Object 除了件数之外,还可以计算 Sum・Average・Maximum・Minimum(以及标准差)等内容。  2 3 4

  4. Microsoft Learn,Compare-Object。关于 Compare-Object 会比较两个对象集合,并用 SideIndicator(<= / => / ==)显示只存在于哪一侧、可以只比较用 -Property 指定的列、当参照侧・差异侧为 null 时会发生终止错误等内容。  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

  6. Microsoft Learn,Marshal.ReleaseComObject(Object) Method。关于 ReleaseComObject 用于减少与 COM 对象关联的 RCW(运行时可调用包装器)的引用计数,从而显式控制 COM 对象的生命周期、访问已释放的对象会引发异常因此使用时需要注意等内容。  2

  7. 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

  8. Microsoft Learn,Import-Csv。关于 Import-Csv 会从 CSV 创建表格形式的自定义对象、第 1 行会被解释为标题行、用 -Header 可以为没有标题行的文件赋予列名、标题的空白处会被分配以 H 开头的临时列名、值会被读入为字符串等内容。  2 3 4

  9. Microsoft Learn,Export-Csv。关于 Export-Csv 会把对象的各个属性写出为 CSV 的列、PowerShell 7 中的默认编码是 UTF8NoBOM、PowerShell 6.0 及以后默认不再输出 #TYPE 行、NoTypeInformation 隐式生效等内容。  2 3

  10. Microsoft Learn,Install a package manager for PowerShell。关于访问 PowerShell Gallery 需要传输层安全性(TLS)1.2 以上、在会话中启用 TLS 1.2 的命令,以及把它写进配置文件脚本的方法等内容。 

  11. Microsoft Learn,Import data from Excel or export data to Excel with SQL Server Integration Services (SSIS)。关于在无人・非交互式环境中使用 Excel 组件不受支持、在生产环境的自动化处理中建议替换为平面文件(CSV)或基于 Open XML 的方式等内容。 

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

为什么用 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 文件来处理。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表