PowerShell 呼叫 ClosedXML 彙整製作 Excel 報表已是我的日常,無奈學藝不精在一個簡單 Excel 範本套表任務踩坑,搞到三更半夜。最後發現是低級錯誤,深深上了一課。

重現問題程式如下,大家能一眼看出哪裡有問題嗎?

$ErrorActionPreference = 'Continue'

Add-Type -Path $PSScriptRoot\ClosedXML.dll
Add-Type -Path $PSScriptRoot\DocumentFormat.OpenXml.dll
Add-Type -Path $PSScriptRoot\ExcelNumberFormat.dll

$wb = New-Object ClosedXML.Excel.XLWorkbook((Resolve-Path '.\sample.xlsx'))
$ws = $wb.Worksheets[0]
$ws.Cell(1, 1).Style.Font.FontColor = [ClosedXML.Excel.XLColor]::Red
$ws.Cell(1, 1).Value = 'Hello, World!'
Write-Host $ws.Cell(1, 1).Value

程式開啟 sample.xlsx,取得第一個 Worksheet,設定第一列第一欄文字顏色並修改內容,最後列印出來。

結果挺離奇,.Cell(1, 1).Sytle.Font.FontColor 設定顏色冒出 The property 'FontColor' cannot be found on this object. Verify that the property exists and can be set.,而修改 .Cell(1, 1).Value 則噴出 The property 'Value' cannot be found on this object. Verify that the property exists and can be set.

而讀取 .Cell(1, 1).Value 可得到結果,但結果並不是 Sheet1 A1 "ABC",而是 "ABC\n123",串接了 Sheet2 A1 的內容。

鬼打牆好一陣子,一度懷疑 Excel 有鬼內含了某種特殊設定,再不然是 ClosedXML 元件太舊有 Bug,最後事實證明,小丑通常是我自己。

使用 VSCode Debug 檢查會發現 $ws = $wb.Worksheets[0] 其實是個 XLWorksheets 物件,而非第一個 Worksheet,包含兩個元素,咦?但 XLWorksheets[0] 為什麼還是 XLWorksheets?

改用 .NET 檢查,發現錯得離譜,.Worksheets 的型別是 XLWorksheets,它實作了 IEnumerable<XLWorksheet>,用 .First() 取第一筆 OK,但沒有 [0] 這種寫法,在 C# 根本編譯不過。

BUT! 在 PowerShell 對沒實作 Indexer 的非內建型別使用 [0]、[1] 並不會出錯,依實測結果,[0] 將傳回自身,[1]、[2]... 則會傳回 $null。

而在 Worksheets 案例中,可發現 $ws.Cell(1, 1)、$ws.Cell(1, 1).Value 傳回的是 Object[],而實際用 ForEach-Objet 檢查其型別,會發現它會對 IEnumerable<XLWorksheet> 跑迴圈,對兩個 XLWorksheet 執行 .Cell(1, 1) 及 .Cell(1, 1).Value 傳回。

而當 Excel 只有一個 Worksheet 且只讀取不修改,並不會發現這個寫法有錯。這也是為什麼先前文章裡雖然錯寫為 Worksheets[0] (現已更正),實際上線也用了好幾個月,直到處理某個多工作表時才炸開。

至於傳回 Object[] 的行為,我想起先前在 PowerShell FAQ - 函數傳回集合物件注意事項也遇過,這回查到進一步線索,該行為應與 Member Access Enumeration 有關 - 當使用成員存取運算符來存取「集合中不存在的成員」時,PowerShell 會自動列舉集合中的專案,並嘗試存取每個專案上的指定成員。

如此便可解釋為什麼讀取 $ws.Cell(1, 1).Value 可以,但設定 $ws.Cell(1, 1).Value 屬性會出錯。

【心得】粗心如我,還是很需要強型別語言嚴格的編譯器當保姆,PowerShell 太自由容錯度太高,我容易誤入歧途耗費太多時間在低級錯誤上。而未來在 PowerShell 整合 .NET 程式庫卡關時我會速速改用 C# 驗證,應可節省一些射茶包時間。

This blog post explores a common error encountered when using PowerShell to manipulate Excel files with the ClosedXML library. The issue arises from incorrect indexing of worksheets, leading to unexpected results and errors. The solution involves understanding the correct usage of the XLWorksheets type and avoiding PowerShell’s member access enumeration pitfalls.


Comments

Be the first to post a comment

Post a comment