PowerShellからCOMと.NETを呼ぶ実践 ── スクリプトの届く範囲を一気に広げる

· 更新日: · · PowerShell, Windows, COM, .NET, 自動化, スクリプト, 既存資産活用, 業務効率化

更新履歴(3件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
STA(シングルスレッドアパートメント)の説明と、なぜOfficeが並列実行に向かないかを追加し、既存のSTA/MTAの記事へリンクしました。「2ドットルール」の一言説明、無人ジョブ向けのイベントログ出力の代替コード例、三重の`try`/`finally`の設計意図の説明を追加しました。`ZipArchive`の2GB上限については、クラスのリファレンス本文に数値の記載が無いことも脚注に明記しています。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21590176)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「PowerShellからCOMと.NETを呼ぶ実践 ── スクリプトの届く範囲を一気に広げる」合同会社小村ソフト. https://comcomponent.com/blog/powershell-com-dotnet-interop/

DOI(登録済みアーカイブ)
10.5281/zenodo.21590176
DOI(前回登録した版)
10.5281/zenodo.21733117

「コマンドレットを探したが、やりたい処理が見つからない」── ショートカットの一括作成、ZIPを展開せずに中身だけ確認する処理、ウィンドウ操作、Excelファイルの読み書き。PowerShellを使い込むと、標準のコマンドレットだけでは届かない仕事が出てきます。

そのときの選択肢が、.NETクラスの直接呼び出し、Add-TypeによるC#・Win32 APIの組み込み、COMオブジェクトの操作です。PowerShellは.NETの上に構築されており、VBScriptやVBAで使ってきたWindowsの自動化部品にも手が届きます。123

ただし、「呼べること」と「その実行環境で使ってよいこと」は別です。 特にExcelでは、終了後にEXCEL.EXEが残る問題と、無人・非対話実行が非推奨・非サポートである問題を分けて考える必要があります。この記事では、呼び出し方、後始末、5.1と7の違い、C#ツール化の判断を順に整理します。45

1. まず結論

呼び出し先を選ぶ: .NET、Add-Type、COM

コマンドレットに無い処理は、まず.NETクラスライブラリを探します。 静的メソッドは [型]::メソッド()、インスタンスの作成は New-Object またはPowerShell 5.0以降の [型]::new() です。コンストラクタの一覧を調べるときは、丸かっこを付けずに [型]::new と書きます。6

.NETの直接呼び出しだけでは足りなければ、Add-Type でC#を組み込めます。DllImportのシグネチャを渡せば、Win32 APIのP/Invoke呼び出しも可能です。一方、WScript.ShellやExcelなど既存のCOM部品を使う入口は New-Object -ComObject <ProgId> です。237

実行前に決める: 人が操作するのか、無人で動かすのか

COMの参照は.NET側のRCW(Runtime Callable Wrapper)が保持します。参照が残るとExcelなどのプロセスが終了しないため、呼び出しと後始末を一組で設計します。ただし、Marshal.ReleaseComObject の乱用も事故の原因になるので、明示解放は本当に必要な場合に限ります。48

サービス・タスクスケジューラの無人ジョブ・サーバーサイドからのExcel COM操作は、動いているように見えても非サポート構成です。 無人処理はOpen XML系ライブラリやCSV連携など、Office本体を起動しない方式へ置き換えます。5

配布前に確かめる: 5.1と7の差、スクリプトの保守限界

Windows PowerShell 5.1は.NET Framework、PowerShell 7は.NET(旧.NET Core)を使うため、使える型・メソッド・アセンブリの読み込み方に差があります。両対応のスクリプトは両方でテストします。12

また、Add-Type で追加した型はセッション内で消せず、同名の型を定義し直せません。この制約や配布先でのコンパイルが負担になり、埋め込むC#が大きくなったら、C#ツール化を検討する段階です。2

目的から読む

やりたいこと・困っていること 読むところ 先に確認すること
.NETのメソッドを呼びたい、引数を調べたい 2章: 型とインスタンス 静的メソッドの :: とインスタンスの . を分ける
ZIPの一覧を見たい、確認してから展開したい 2.3: ZIPの読み取りと展開 一覧取得とファイルの書き出しは別の操作
Win32 APIを呼びたい、Add-Typeの修正が反映されない 3章: Add-TypeとP/Invoke ダイアログは対話実行専用。型の修正は新しいセッションで試す
CSVからショートカットを作りたい 4章: COMとWSH 実行した端末の公開デスクトップへ書き込む例
Excel操作後にEXCEL.EXEが残る 5.1: COMの後始末 Quitと参照の解放を分ける。Officeの無人実行には使わない
夜間ジョブでExcelファイルを処理したい 5.2: 無人Officeを避ける 後始末の工夫では、非サポートという前提は変わらない
5.1と7で結果が違う 6章: 実行環境の差 土台の.NETとオーバーロード、削除されたコマンドを確認する
スクリプトを育てるか、C#へ移すか迷う 7章: 実務の判断表 型の再定義、コード量、配布と保守の負担を見る

この記事のCOM・Win32 APIの例はWindows向けです。各コードは独立したレシピであり、記事の先頭から順にすべて実行する手順ではありません。例示パスや入力ファイルを確認し、特に書き込み・保存を含む例はテスト用のデータで試してください。3

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. PowerShellは.NETの上に載っている ── [型]::メソッドとNew-Object

2.1. 型から呼ぶのか、作ったオブジェクトから呼ぶのか

PowerShellのコマンドレットは、.NETクラスライブラリの一部を「運用作業向けに使いやすく包んだもの」です。包まれていない機能も、型名を角かっこで書けば直接呼べます。

静的メソッドは ::、インスタンスのメソッドやプロパティは . です。 次の例では、前半のPathとMathは型から直接呼び、後半のListはインスタンスを作ってから Add を呼びます。最後のStreamWriterの行はオブジェクトを作る操作ではなく、コンストラクタの一覧を調べる操作です。6

# .NETの静的メソッドを直接呼ぶ。コマンドレット不要、追加インストールも不要
[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

2.2. 作成方法が違うと、同じ型でもプロパティが違うことがある

[型]::new() はPowerShell 5.0で追加された書き方で、New-Object より高速です。また、上の例のように ::new を丸かっこなしで指定すれば、コンストラクタのオーバーロード一覧をその場で確認できます。6

一方、Get-Item などのコマンドレットが返すオブジェクトには、PowerShellが追加のプロパティ(NoteProperty)を足していることがあります。::new() で直接作った同じ型のオブジェクトとは、メンバー構成が一致しない場合があるということです。6 「コマンドレット経由ではあったプロパティが無い」ときは、取得・作成の経路を確認してください。

2.3. ZIPは「一覧を読む」と「展開する」を分ける

実務レシピを一つ。ZIP処理には Compress-Archive / Expand-Archive がありますが、「展開せずに中身の一覧だけ確認したい」といった細かい操作には、.NETの ZipFile クラスが使えます。9

ファイルサイズにも注意が必要です。圧縮側・展開側の両方の公式ドキュメントは、基盤の System.IO.Compression.ZipArchive APIに最大ファイルサイズ2GBの制限があると説明し、クラスのリファレンスを参照先に挙げています。1011 以下は一覧確認と展開の使い分けを示す例であり、この制限を回避するための例ではありません。

準備・読み取り・書き出しの3段階で読む

最初にアセンブリを読み込み、OpenRead から finally までで中身を一覧にします。読み取りが終わったら Dispose() でファイルハンドルを返します。末尾の $dest と ExtractToDirectory は、ファイルを実際に書き出す別の段階です。

このコードには、一覧を見て承認するための停止処理は入っていません。 一覧だけ確認したいときは finally までを実行し、内容を確認してから末尾の展開処理へ進んでください。2引数版の ExtractToDirectory は既存ファイルを上書きせず、同名ファイルがあると途中まで展開した状態で例外になります。毎回新しいフォルダーへ展開してから切り替える方針です。

# 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)

5.1と7でアセンブリの読み込み方が違う

.NET Frameworkで ZipFile を使うには、System.IO.Compression.FileSystem アセンブリへの参照が必要です。これはPowerShell 5.1でも同じです。9 PowerShell 7では同梱アセンブリが要求時に自動ロードされるため、この例の Add-Type は無くても動きます。書いておけば両方で動くので、共用スクリプトでは明示するのが無難です。2

3. Add-Typeで自作C#とWin32 API(P/Invoke)を組み込む

.NETにある機能 の次は .NETにない機能 です。Add-Type にC#のソースコードを渡すと、その場でコンパイルされてセッションに型が追加されます。-MemberDefinition にDllImport付きのシグネチャを渡せば、Win32 APIもP/Invokeで呼べます。公式ドキュメントにも掲載されている使い方です。2

3.1. ダイアログでの完了通知は、人がいるデスクトップ専用

次は user32.dll の MessageBoxW を呼ぶ例です。シグネチャを文字列で用意し、Add-Type で型を作ってから、その型の静的メソッドを呼びます。2

モーダルダイアログは、自分がデスクトップの前にいる対話的な実行専用です。 タスクスケジューラの無人実行やWindowsサービスのような非対話セッションでは、誰も閉じられないダイアログで処理が止まったままになることがあります。無人ジョブは次の3.2のように、人の応答を待たない通知へ分けてください。

# 手元で流した長時間処理の完了をダイアログで知らせる(user32.dllのMessageBoxWをP/Invokeで呼ぶ)
$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)

3.2. 無人ジョブの通知は、ログへ残す

同じ「処理の完了を知らせる」を、無人ジョブ向けにすると次の形になります。ダイアログの代わりにイベントログへ1件書き、ログファイルにも残します。この例は通知部分だけで、バックアップ処理そのものを実行するコードではありません。

実行前に、バージョン・イベントソース・保存先を確認する

New-EventLog / Write-EventLog はWindows PowerShell 5.1のコマンドレットで、PowerShell 7では削除されています。以下は5.1向けです。1

イベントソースは、コメントに示した New-EventLog で導入時に一度だけ、管理者権限で登録しておきます。夜間ジョブ本体は登録済みを前提に書き込むだけにします。例示パス C:\logs\nightly.log の親フォルダーも用意し、実行アカウントに書き込み権限があることを確認してください。

# 無人ジョブ版の通知: 応答を待たず、後から追跡できる場所に残す
$source  = 'KomuraSoft.NightlyJob'   # イベントのソース名(任意の文字列)
$logPath = 'C:\logs\nightly.log'

# ソースの登録には管理者権限が必要。導入時に1回だけ実行しておき、
# 夜間ジョブ本体では登録済み前提で書き込むだけにする
#   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) バックアップ処理が完了しました。"

記録の確認と、PowerShell 7での選択肢

イベントビューアー > Windows ログ > Application で、ソース名 KomuraSoft.NightlyJob を絞り込めば履歴を追えます。監視ツールを入れている環境なら、このイベントIDを拾って通知に回せます。

PowerShell 7の夜間ジョブなら、通知部分だけ powershell.exe に渡すか、ログファイルへの記録と、それを監視する側の通知へ寄せます。タスクスケジューラのイベント起動はイベントログをきっかけにする方式なので、通常のテキストファイルへの追記を直接検知する機能とは分けてください。12 Power Automateなどを使う場合も、記録先に合う監視方法を選びます。

3.3. 型を修正したいときは、新しいセッションで試す

Add-Type には運用上の重要な制約が3つあります。2

  • 追加した型はそのセッション限りです。 別のセッションやリモートでは、再度 Add-Type が必要です。
  • 同名の型は定義し直せません。 シグネチャを間違えて修正したいときは、名前を変えるか新しいセッションを起動します。試行錯誤は使い捨てのコンソールで行ってください。
  • PowerShell 7では、同名の型が既に存在するとコンパイル自体をスキップします。 「直したはずのコードが反映されない」ときは、既に追加した型を使っていないか確認します。

もう一つ、P/Invoke共通の注意です。シグネチャ(引数の型、文字セット、呼び出し規約)を誤ると、例外で済まずプロセスごと落ちることがあります。文字列やハンドルのマーシャリングの落とし穴は「C#からWin32 APIを安全に呼ぶ ── P/Invoke実務ガイド(DllImport / LibraryImport / CsWin32)」で詳しく整理しています。Add-Typeで本格的にWin32 APIを使う前に確認してください。

4. COMを呼ぶ ── New-Object -ComObjectとWSHの道具箱

4.1. ProgIDを指定して、既存の部品を作る

.NETより古くからWindowsに積まれている部品群がCOMです。New-Object -ComObject <ProgId> で作成でき、VBScriptの Set objShell = CreateObject("Shell.Application") は $objShell = New-Object -ComObject Shell.Application に対応します。ここで指定するProgIDは、使いたいCOM部品を表す名前です。3

WScript.Shell、WScript.Network、Scripting.FileSystemObjectといったWSH(Windows Script Host)由来のオブジェクトも同様に使えます。7 COMとは何かという土台の話は「COM / ActiveX / OCX とは何か - 違いと関係をまとめて解説」を参照してください。

4.2. CSVから公開デスクトップへショートカットを作る

代表的なレシピが、ショートカットの一括作成です。ショートカット(.lnk)を作る標準コマンドレットはなく、公式ドキュメントも「ショートカット作成のような一部のタスクはWSHクラスを使う方が簡単」としてWScript.Shellの例を載せています。7

入力は Name、Target、Args の3列を持つCSVです。各行から .lnk の名前、リンク先、引数を取り出して保存します。書き込み先は、このスクリプトを実行した端末の公開デスクトップです。全端末へ遠隔配布する処理は含んでいないため、全端末への配布では各端末で実行する仕組みを別に用意します。

C:\Users\Public\Desktop へ書き込める権限で実行し、CSVの内容を確認してください。同名の .lnk は上書き対象になります。

# 共有フォルダ上のツールへのショートカットを、全端末の公開デスクトップに配る想定
$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"
}

4.3. 呼べるメンバーと、終了時の動作を確認する

COMオブジェクトのメンバーは $wsh | Get-Member で調べられます。7 Outlookでのメール下書き作成、Shell.Applicationでの特殊フォルダー操作などにも応用できます。

ただし、Excel.ApplicationのようなActiveX実行可能ファイル(別プロセスで起動するCOMサーバー)には、次章の後始末問題が付いて回ります。公式ドキュメントも、参照を手放したときにプロセスが終了するかは相手のアプリケーション次第であり、使う前に終了動作をテストすべきだと注意しています。7

5. COMの後始末 ── EXCEL.EXEが残る問題と「無人Officeは非サポート」

この章では、対話的なExcel操作の後始末と、無人ジョブでOfficeを使わないという設計判断を分けます。夜間バッチやサービスを作る方は、コードを流用する前に5.2を読んでください。後始末を工夫しても、非対話でのOffice自動化がサポート構成になるわけではありません。5

5.1. なぜプロセスが残るのか

Quitと、COMへの参照を手放すことは別

COMオブジェクトの寿命は参照カウントで管理されます。.NET(=PowerShell)からCOMを触ると、RCW(Runtime Callable Wrapper)というプロキシがCOM側の参照を保持します。RCWはガベージコレクションで回収されるとき、その参照を解放します。4

$excel.Quit() を呼んでも、参照が残っていればEXCEL.EXEが終了しないことがあります。注意したいのは、$excel.Workbooks.Open(...) の途中にある Workbooks のように、変数へ受け取っていない中間オブジェクトにもRCWが作られることです。

入れ子のfinallyで、後始末の失敗に備える

次の例は try/finally が三重になっています。読み順は 「本体の処理 → ブックを閉じる → Excelを終了する → 参照を解放する」 です。

  • 外側の try でExcelを操作し、finally で後始末へ進みます。
  • 最初の後始末はブックの Close です。保存処理で例外が出ても閉じる処理へ進めますが、Close 自体も失敗し得ます。
  • 次の finally に Quit を置き、Close の例外で終了要求が飛ばされないようにします。
  • さらに内側の finally に参照の解放とGCを置き、Quit の例外でもその段階へ進めるようにします。

つまり、三重にする理由は「後始末そのものが失敗し得るから」です。Close も Quit も失敗しない前提なら1つの finally で足りますが、その前提を置かずに段階を分けています。これは、呼び出しが応答しなくなった場合や、参照の解放自体が失敗した場合まで必ず処理が完了する、という保証ではありません。

Excelの操作例: 実ファイルを保存するので、複製で試す

以下は、人が操作できるWindowsデスクトップでExcelを使う例です。 指定したブックの先頭シートのセル(1, 1)を書き換え、Save() で保存します。検証用の複製にパスを変更し、新しいPowerShellプロセスで実行して、前回のCOM変数を引き継がないようにしてください。

この例は中間オブジェクトを変数に受ける方法と、残りのRCWの回収をGCへ委ねる方法を併用しています。ReleaseComObject は必要な場合だけ使い、解放したオブジェクトへ再びアクセスしないでください。 不適切な解放は例外やアクセス違反を招きます。8

$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) }
            }
            # 変数に受けそこねたRCWをGCに回収させる保険
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
            [System.GC]::Collect()
        }
    }
}

明示解放と「2ドットルール」の読み方

Marshal.ReleaseComObject はRCWの参照カウントを減らし、0になった時点でCOM側の参照を解放します。公式ドキュメントは「絶対に必要な場合にのみ使うこと」と強く注意しています。8

中間オブジェクトを変数に受ける考え方は、俗に「2ドットルール」と呼ばれます。たとえば $excel.Workbooks.Open(...) を一度に書くと、途中の Workbooks のRCWを明示的に解放するための変数がありません。そこでCOMの操作を分け、間のオブジェクトも変数へ受け取る、という作法です。

ただし、上の例はすべてを1ドットに分けてはいません。$book.Worksheets.Item(1) や $sheet.Cells.Item(1, 1) には変数へ受けていない中間オブジェクトがあり、末尾のGCはその回収を促す意図です。GCは参照が残っているオブジェクトを強制的に解放するものではなく、この例だけでEXCEL.EXEの終了を保証するものでもありません。 正常終了と例外時の両方で、実際の終了動作を確認してください。47

解放パターンの流儀(ReleaseComObject派とGC派)、2ドットルールの詳細、それでも残ったプロセスの特定方法は「C#のExcel操作でEXCEL.EXEが残る問題 ── COM参照の解放パターンと置き換えの判断」で詳述しています。C#の記事ですが、RCWの仕組みはPowerShellでも同一です。

5.2. そもそも無人実行でOfficeを使わない

動くことと、サポートされることを分ける

Microsoftは、無人・非対話のクライアントアプリケーションやコンポーネント(ASP、ASP.NET、DCOM、NTサービスを含む)からのOffice自動化を推奨せず、サポートもしないと明言しています。Officeは対話利用を前提に設計されており、無人環境では不安定な動作やデッドロックを示すことがあるためです。5

予期しないダイアログで処理が止まる、STAベースで多重実行に制約がある、といった問題も列挙されています。DisplayAlerts = $false としたり、5.1節の後始末を入れたりするだけで、無人処理に適した設計へ変わるわけではありません。5

STAは、Officeの並列実行にも関わる

STAはシングルスレッドアパートメント(Single-Threaded Apartment)の略で、COMのスレッドモデルの1つです。オブジェクトへの呼び出しを、そのオブジェクトを作ったスレッドへ集約して1本ずつ処理する仕組みで、Officeアプリケーションはこの前提で作られています。

そのため、「1台のサーバーで同じ処理を並列に何本も走らせる」使い方には構造的に向きません。5 COMのスレッドモデルそのものは「COM STA/MTA の基礎知識 - スレッドモデルとハングを避ける考え方」で詳しく扱っています。

夜間ジョブは、Office本体を起動しない方式へ

タスクスケジューラの無人ジョブやサービスからExcel COMを呼ぶ構成は、動いているうちから非サポートです。無人処理では、Open XML系のファイル直接編集やCSV連携などへ置き換えます。Officeファイルを直接編集する方式は、公式資料でも推奨されています。5

PowerShellでの具体的な代替レシピは「PowerShellでのExcel・CSV業務処理レシピ」にまとめています。この記事では、Excel COMを使うのは「人がログオンしているデスクトップで、人の作業を補助する」用途までと線を引きます。

6. 5.1と7で「使える.NET」は違う

6.1. 同じ型名でも、呼べるメソッドが違う

Windows PowerShell 5.1は.NET Framework 4.5系、PowerShell 7は.NET(旧.NET Core)の上に構築されています。1 コマンドレットだけ使っているときよりも、.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 での明示ロードが必要な場面が多い2 同梱アセンブリは要求時に自動ロード2
使えなくなったもの ─ *-EventLog などWindows専用コマンドレットの一部が削除1

6.2. 型・読み込み・コマンドの3点で確認する

「5.1で動いたのに7で動かない」「その逆」のどちらも起こり得ます。同じ Split('pq') でも選ばれるオーバーロードが違う例があるので、エラーが出ないだけで同じ動作と判断しないでください。1

確認するのは、呼んでいる型とメソッド、必要なアセンブリの読み込み、使っているコマンドレットの有無です。この記事では、ZIPのアセンブリ読み込み(2.3)と、5.1専用のイベントログ通知(3.2)が具体例です。両対応が必要なスクリプトは、必ず両方の環境でテストしてください。

移行判断の全体像は「Windows PowerShell 5.1とPowerShell 7の違いと移行」で扱っています。

7. 実務の定石(判断表)

最後に、手段を選ぶときの判断をまとめます。「推奨」は右端の条件と一組です。 Excel COMの後始末は5.1節の注意を前提とし、すべてのCOM参照へ一律に ReleaseComObject を呼ぶという意味ではありません。また、インスタンスを作る ::new() と、一覧を確認する ::new は区別してください。

論点 選択肢(推奨に「推奨」を明記) 判断の目安
やりたい処理がコマンドレットにない あきらめる / .NETクラスを直接呼ぶ(推奨) まず [型]::メソッド を探す。System.IO、System.Text、System.IO.Compressionあたりで大半の「あと一歩」は埋まる1
インスタンスの作成 New-Object / [型]::new()(推奨) 5.0以降だけなら ::new()。コンストラクタ一覧が見え、速い。COMだけはNew-Object -ComObject一択63
Win32 APIが必要 手作業で我慢 / Add-TypeでP/Invoke(推奨) 数個のAPIならAdd-Typeで十分。シグネチャの誤りはプロセスごと落とすので、使い捨てセッションで検証2
ショートカット作成などWSH由来の操作 COM(WScript.Shell)(推奨) コマンドレットが無い領域はCOMが今も現役。公式サンプルもこの方式7
人の作業を補助するExcel操作 COM + 確実な後始末(推奨) finallyでQuit・ReleaseComObject・GCまでをセットで。中間オブジェクトも変数に受ける84
無人ジョブでのExcel/Office処理 COM / Officeを起動しない方式(推奨) 無人のOffice自動化は非サポート。Open XML系・CSVへ置き換える5
スクリプトが育ちすぎた PowerShellで粘る / C#ツール化(推奨) Add-Typeのコードが数百行になった、型の再定義制約が開発を邪魔する、配布先で毎回コンパイルさせたくない ── のいずれかが出たら移行のサイン

C#ツール化は、すべてを書き直すことではない

Add-Type に埋め込むC#が数百行になった、型の再定義制約が開発を邪魔する、配布先で毎回コンパイルさせたくない。このいずれかが出たら、C#プロジェクトへ移すサインです。

埋め込むC#が肥大化した状態は、実質的に「C#プログラムをPowerShellから動かしている」状態です。Visual Studioの型チェックとデバッガー、NuGetパッケージ、単体テストが使えるC#プロジェクトへ移した方が、開発も保守も進めやすくなります。

C#側からPowerShellの資産を呼び戻す方法もあるので、移行は全部書き直しではありません。「C#(CSharp)でPowerShellを実行して、オブジェクトとして受け取る方法」で橋渡しのパターンを紹介しています。

8. まとめ

コマンドレットに無い処理は、まず.NETを探す。 型から直接呼ぶなら [型]::メソッド()、インスタンスが必要なら New-Object または [型]::new() を使います。C#やWin32 APIの組み込みには Add-Type、既存のCOM部品には New-Object -ComObject が入口になります。

呼び出し方を決めたら、実行環境と後始末を決める。 ZIPのハンドルは Dispose()、Excelではブックを閉じる処理、Quit、COM参照の解放を分けて考えます。明示解放は必要な場合だけ使い、参照やプロセスが残らないかを実際に確認します。無人ジョブのOffice処理は、後始末の工夫ではなく、Officeを起動しない方式への置き換えが基本です。85

配布前には5.1と7の差を確かめ、保守が重くなったらC#ツール化する。 Add-Typeの型はセッション限りで、同名の再定義はできません。両対応のテストや型の修正・配布が負担になってきたときは、PowerShell資産を活かしながらC#へ移す選択肢があります。

関連記事

関連する相談領域

合同会社小村ソフトでは、PowerShellによる社内業務の自動化、COM資産(Excel連携・レガシー部品)を含むスクリプトの設計・改修、「スクリプトが育ちすぎた」状態からのC#ツール化を扱っています。EXCEL.EXEの残留のような不具合調査も対応します。

参考リンク

  1. 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で削除されたコマンドレットについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11

  2. 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 ↩9 ↩10 ↩11

  3. Microsoft Learn, New-Object. New-Objectが.NETまたはCOMオブジェクトのインスタンスを作成すること、-ComObjectパラメーターにProgIdを指定すること、VBScriptのCreateObject(“Shell.Application”)がNew-Object -ComObject “Shell.Application”に対応することについて。 ↩ ↩2 ↩3 ↩4 ↩5

  4. Microsoft Learn, Runtime Callable Wrapper. .NETがCOMオブジェクトをRCWというプロキシ経由で公開すること、COMオブジェクトごとに1つのRCWが作られること、RCWがガベージコレクションで回収される際にCOMオブジェクトへの参照を解放することについて。 ↩ ↩2 ↩3 ↩4 ↩5

  5. 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 ↩6 ↩7 ↩8 ↩9

  6. Microsoft Learn, about_Object_Creation. PowerShell 5.0で全.NET型に追加された静的new()メソッド、::newと打つことでコンストラクタのオーバーロード一覧を確認できること、コマンドレット経由で取得したオブジェクトにはPowerShellがNotePropertyを追加するため::new()で作ったオブジェクトとメンバーが一致しない場合があることについて。 ↩ ↩2 ↩3 ↩4 ↩5

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

  8. Microsoft Learn, Marshal.ReleaseComObject(Object) Method. ReleaseComObjectがRCWの参照カウントを減らし0で基盤のCOMオブジェクトを解放すること、解放後のRCW使用が例外やアクセス違反を招くこと、「絶対に必要な場合にのみ使う」という公式の注意、FinalReleaseComObjectとの関係について。 ↩ ↩2 ↩3 ↩4 ↩5

  9. Microsoft Learn, ZipFile Class. ZipFileクラスがZIPの作成・展開・オープンの静的メソッドを提供すること、.NET Frameworkで使うにはSystem.IO.Compression.FileSystemアセンブリへの参照が必要なこと、CreateFromDirectory/ExtractToDirectory/OpenReadの用途について。 ↩ ↩2

  10. Microsoft Learn, Expand-Archive. Expand-ArchiveがSystem.IO.Compression.ZipArchive APIを使用すること、このAPIに最大ファイルサイズ2GBの制限があること、この.NET APIがPKWARE社の公式ZIPファイル形式仕様に準拠したファイルを扱うことについて。 ↩

  11. 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という数値の記載はないため、数値の一次情報はこのコマンドレット側のページになります)。 ↩

  12. Microsoft Learn, EventTrigger object. タスクスケジューラのイベントトリガーがシステムのイベントログへのイベント記録をきっかけにタスクを開始することについて。 ↩

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

PowerShellから.NETのクラスを直接呼べますか?
呼べます。PowerShellは.NETの上に構築されているため、[System.IO.Path]::GetFileNameWithoutExtension() のように角かっこで型名を書き、:: で静的メソッドを呼び出せます。インスタンスが必要なら New-Object か、PowerShell 5.0以降で使える [型]::new() で作成します。コマンドレットが用意されていない処理でも、.NETクラスライブラリにあれば追加インストールなしでスクリプトから使えます。
New-Objectと[型]::new()はどちらを使うべきですか?
どちらでもオブジェクトは作れますが、[型]::new と丸かっこを付けずに打つと、その型のコンストラクタ一覧が表示されるため、引数を調べながら書くのに向いています。パフォーマンスも [型]::new() の方が有利です。一方、COMオブジェクトの作成は New-Object -ComObject にしかできません。PowerShell 5.0より古い環境を考慮する場合も New-Object を使います。
PowerShellでExcelをCOM操作するとEXCEL.EXEが残るのはなぜですか?
COMオブジェクトは参照カウントで寿命が管理され、.NET側ではRCW(Runtime Callable Wrapper)がその参照を握ります。$excel.Workbooks.Open() のように書くと、途中のWorkbooksオブジェクトにも見えないRCWが作られ、変数に入れていない参照が残ります。Quit()を呼んでも参照が残っているとプロセスは終了しません。使い終わったらMarshal.ReleaseComObjectで明示的に解放するか、変数をnullにしてGC.Collect()とWaitForPendingFinalizers()で回収させる後始末が必要です。
サーバーや夜間バッチでExcelをCOM操作してよいですか?
推奨されません。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ではGACのアセンブリをAdd-Typeでロードする必要がある場面が多いのに対し、7では同梱アセンブリが必要時に自動ロードされます。両方で動かすスクリプトは、どちらの環境でもテストしてください。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る