「找了 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 的簽章,也能透過 P/Invoke 呼叫 Win32 API。新增的型別無法在工作階段中刪除,同名的型別也無法重新定義。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 - Microsoft 明確表示對無人執行下的 Office 自動化「不建議也不支援」。從服務、工作排程器、伺服器端進行 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() # 確保釋放檔案控制代碼
}
# 內容沒問題的話就展開。展開目的地若有同名檔案,這個2個引數版本
# 會在展開到一半時擲出例外(不會覆寫)。應避免直接展開到既有資料夾,
# 每次都展開到新資料夾後再切換過去比較安全
$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 中沒有的功能」。把 C# 原始碼傳給 Add-Type,就會當場編譯,並把型別新增到工作階段中。此外,只要把帶有 DllImport 的簽章傳給 -MemberDefinition,就能透過 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 個維運上重要的限制。3
- 新增的型別僅限於該工作階段。在其他工作階段或遠端執行時,需要再次執行
Add-Type。 - 同名的型別無法重新定義。若簽章寫錯想要修正,需要換個名稱或啟動新的工作階段。請在用完即丟的主控台中進行嘗試錯誤。
- 在 PowerShell 7 中,若同名的型別已經存在,會直接略過編譯本身。當「明明已經修好的程式碼卻沒有反映出來」時,請懷疑這一點。
還有一點,是 P/Invoke 共通的注意事項。簽章(引數型別、字元集、呼叫慣例)如果寫錯,有時不只是丟出例外,甚至會連處理程序一起當掉。字串與控制代碼封送處理(Marshaling)的陷阱,在「在 C# 中安全呼叫 Win32 API — P/Invoke 實務指南(DllImport / LibraryImport / CsWin32)」中有詳細整理,建議在用 Add-Type 正式使用 Win32 API 之前先讀一遍。
4. 呼叫 COM ── New-Object -ComObject 與 WSH 的工具箱
COM 是比 .NET 更早內建在 Windows 中的一群元件。可以用 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 這3個欄位
$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 的主要處理。無論這裡發生什麼失敗,之後的後續處理都一定會執行。 - 第一層
finally(關閉活頁簿):即使儲存失敗,也要去關閉已開啟的活頁簿。不過Close本身也可能失敗。 - 第二層
finally(執行Quit):所以在不受Close失敗牽連的位置呼叫Quit。若 Excel 已經無回應,Quit也可能失敗。 - 第三層
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 |
| 組件載入 | GAC 中的組件在許多情況下需要用 Add-Type 明確載入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 -ComObject 這一種選擇24 |
| 需要 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 與雜湊表進行比對,...
Windows PowerShell 5.1 與 PowerShell 7 的差異 ── 公司內部腳本遷移實務指南
本文整理 Windows PowerShell 5.1 與 PowerShell 7 的關係(共存與 pwsh.exe)、5.1 不再新增功能的官方方針、編碼差異造成的亂碼問題、以 #Requires 進行防禦,直到更新工作排程器為止的遷移步驟。
在 C#(CSharp)中執行 PowerShell 並以物件形式接收結果
本文從實務角度整理如何從 C# 啟動 PowerShell,並以 PSObject 而非字串來接收結果,涵蓋 PowerShell SDK、AddCommand、AddParameter、BaseObject、Properties 到錯誤處理。
PowerShell 實用指令集錦 ── 累積日常工作常用的小工具
本文整理 PowerShell 日常工作中常用的實用指令,說明 Measure-Object、Group-Object、Select-String、Compare-Object、Tee-Object、Start-Transcript 等指令的使用場景與時機。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
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 中,隨附的組件會在需要時自動載入。若腳本要在兩者上都能執行,請務必在兩種環境下都測試。