0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

`GetActiveObject`でExcelの二重起動とPERSONAL.XLSBロック競合を防ぐ

0
Posted at

背景

VSCodeマクロからOfficeファイルをPowerShell + COM経由で開く機能を実装する際、単純にNew-Object -ComObject Excel.Applicationを使うと、Excelが既に起動していても新しいインスタンスを作ってしまい、個人用マクロブック(PERSONAL.XLSB)のロック競合やセキュリティ通知が発生する問題がありました。

1. GetActiveObjectで既存インスタンスを優先取得

try {
    $excel = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
} catch {
    $excel = New-Object -ComObject Excel.Application
}
$excel.Visible = $true
$excel.Workbooks.Open('C:\path\to\file.xlsx', 0, $true)

GetActiveObjectは実行中のCOMインスタンスへの参照を取得するAPIです。Excelが未起動の場合は例外を投げるため、try/catchで「起動済みなら再利用、未起動なら新規作成」という分岐を実現できます。この1パターンを覚えておくと、Word・PowerPointでも同じ考え方が使えます。

2. Officeバージョンに依存しないパス解決

# Office 2016/2019/Microsoft 365でパスが異なる
C:\Program Files\Microsoft Office\Office16\EXCEL.EXE
C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE

実行ファイルの絶対パスを直接指定する方式は、Officeのバージョンやインストール形態によってパスが変わるため保守性が低くなります。COMオブジェクト(Excel.ApplicationのようなProgID)経由で起動すれば、レジストリに登録された実際のパスをWindowsが解決してくれるため、バージョン非依存になります。

3. Excel/Word/PowerPointでReadOnly引数の型が全部違う

# Excel: 第3引数 ReadOnly = $true(Boolean)
$excel.Workbooks.Open('${path}', 0, $true)

# Word: 第3引数 ReadOnly = -1($trueではなく数値)
$word.Documents.Open('${path}', 0, -1, 0)

# PowerPoint: 第2引数 ReadOnly = 1(MsoTriState型)
$ppt.Presentations.Open('${path}', 1, 0, 1)

同じ「読み取り専用で開く」操作でも、COM APIの引数の型と位置がアプリケーションごとに異なります。Wordは$trueではなく-1を渡す必要がある点、PowerPointはMsoTriState型(1=True)を使う点は、ドキュメントを読まないと気づきにくいハマりどころでした。

4. フォーカスを奪わない起動方法の使い分け

// テキスト系: ShellExecuteのSW_SHOWNOACTIVATE(4)でVSCodeのフォーカスを維持
(New-Object -ComObject Shell.Application).ShellExecute(filePath, '', '', 'open', 4)

// Office系: COMで開けば、そもそもフォーカス権が付与されない
$excel.Workbooks.Open(...)

ShellExecuteは通常フォーカスを奪いますが、SW_SHOWNOACTIVATE(定数4)を指定すると表示のみでフォーカスは移りません。一方Office系はCOM経由で開けばこの指定をしなくても元々フォーカスが移らないため、テキスト系とOffice系で異なる起動方法を使い分けています。

5. PowerShellへのパス埋め込みはシングルクォートのエスケープのみ

const escapedPath = filePath.replace(/'/g, "''");

PowerShellのシングルクォート文字列内では、Windowsパスの\はエスケープ不要です。シングルクォート自体だけを''に二重化すればOKで、他言語のバックスラッシュエスケープの感覚で書くと余計な変換をしてしまいがちな点に注意が必要でした。

まとめ

GetActiveObjectによる既存インスタンス再利用と、Office3製品それぞれで異なるReadOnly引数の型、この2点がCOM経由でのOffice操作実装の勘所でした。連続起動時のカーソル自動移動を含む完全なコードは元記事にまとめています。

カーソル下のパスを開くVSCodeマクロ(ブログ)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?