「每個月,把從核心系統匯出的 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,営業1課,株式会社A,120000
1002,2026/07/01,営業2課,株式会社B,80000
1003,2026/07/02,営業1課,株式会社C,45000
1004,2026/07/03,管理部,株式会社A,15000
1005,2026/07/03,営業2課,株式会社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課 | 2 | 310000 |
| 営業1課 | 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 行),就會出錯中止。4 上面的程式碼把讀取結果用 @() 包起來,就是為了應對這個問題。因為 0 筆的 CSV 也會變成空陣列,即使遇到「今天全員都離職了(=所有行都以 <= 出現)」這種極端狀況,也能以差異結果的形式接收,而不是格式錯誤。
4.2. 若還需要引用欄位(合併)就用雜湊表
「把明細 CSV 的員工編號,對照主檔 CSV 引用姓名和部門」這種相當於 SQL JOIN 的處理,慣用做法是先把主檔那一方轉成鍵值 → 行的雜湊表,再逐行查找。雙層迴圈(明細 × 主檔的全比對)在數千筆 × 數千筆的規模下會明顯變慢,但用雜湊表的話,即使是數萬筆也能以實用的速度處理。
輸入設定為以下兩份檔案。
master.csv
EmpNo,Name,Dept
E001,山田 太郎,営業1課
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,山田 太郎,営業1課,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 本身的功能(重新計算、列印、解析已定義名稱等)──只有在這種情況下,才對 Excel 本體做 COM 操作。從 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 的字元編碼與換行符 - Shift_JIS / UTF-8 / UTF-16、亂碼、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 建立表格式自訂物件、第一行會被解讀為標題、可用 -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 配置 ── 讓操作手冊變得可執行
本文整理讓新進員工 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 檔案處理。