Excel全ウィンドウをウルトラワイドモニター中央に一括配置する
ウルトラワイドモニターでExcelを使っていると、ブックが画面の左端や右端に散らばる。8つのブックを開いた状態でモニター中央(1920x1080、座標4600,180)に全部並べ直す作業を毎日やっていた。Stream Deckのボタン一発で片付けたい――そう思ってスクリプトを書き始めたが、ここから4段階の試行錯誤が始まった。
Step 1: バッチファイルにPowerShellを埋め込む → 行結合で壊れる
Stream Deckから呼ぶ最もシンプルな方法として、.batにPowerShellのhere-stringを埋め込んだ。
powershell -Command "$code = @'
Add-Type ...
'@; ..."
実行するとhere-stringがバッチの行結合で壊れ、構文エラーが出た。バッチファイルのエスケープルールとPowerShellのhere-stringは相性が悪い。
修正: .ps1を分離して、.batから powershell -ExecutionPolicy Bypass -File move-excel.ps1 で呼ぶ構成に切り替えた。構文エラーが消えて、スクリプト本体の開発に集中できるようになった。
Step 2: 1ウィンドウしか動かない → 全プロセスをループに変更
最初のPowerShellスクリプトではGet-Process EXCEL | Select -First 1で1つだけ取得していた。当然、8つのうち1つしか動かない。
修正: Select -First 1を外してGet-Process EXCELの全結果をループで回した。
Get-Process EXCEL | ForEach-Object {
[Win32]::MoveWindow($_.MainWindowHandle, 4600, 180, 1920, 1080, $true)
}
ところが結果は変わらず、1つのウィンドウしか移動しない。ここで手が止まった。
Step 3: Excelは1プロセスで複数ブックを持つ → EnumWindowsで直接列挙
原因を調べて腑に落ちた。Excelは複数ブックを開いていてもOSのプロセスは1つしか立たない。Get-Processで列挙してもMainWindowHandleは1個だけ返る。
修正: Win32 APIのEnumWindowsを使い、ウィンドウクラス名XLMAINを持つウィンドウを直接列挙する方式に切り替えた。
# EnumWindowsのコールバックでXLMAINクラスを収集
[Win32]::EnumWindows({
param($hwnd, $lParam)
$cls = New-Object Text.StringBuilder 256
[Win32]::GetClassName($hwnd, $cls, 256)
if ($cls.ToString() -eq "XLMAIN") {
# $hwndをリストに追加
}
return $true
}, [IntPtr]::Zero)
これで8つのExcelウィンドウハンドルが全て取得できた。MoveWindowをループで回すと、8つ全てが指定座標に移動した――が、サイズがおかしい。
Step 4: リサイズが効かない → SW_SHOWNORMALに変更
最大化や最小化されたウィンドウに対してMoveWindowを呼んでも、位置は変わるがサイズが元に戻らない。最初はShowWindow($hwnd, 9)(SW_RESTORE)を呼んでからMoveWindowしていたが、最大化状態のウィンドウは復元後に直前のサイズに戻るだけで、こちらが指定したサイズにはならなかった。
修正: ShowWindow($hwnd, 1)(SW_SHOWNORMAL)に変更した。SW_SHOWNORMALは最大化・最小化フラグを完全にクリアしてから通常状態に戻す。その後にMoveWindowを呼ぶと、指定サイズが正しく適用された。
[Win32]::ShowWindow($hwnd, 1) # SW_SHOWNORMAL
[Win32]::MoveWindow($hwnd, 4600, 180, 1920, 1080, $true)
8つのExcelウィンドウが全て1920x1080でモニター中央に並んだ。
最終構成
StreamDeck → move-excel.bat → move-excel.ps1
- move-excel.bat:
powershell -ExecutionPolicy Bypass -File move-excel.ps1 - move-excel.ps1:
Add-TypeでWin32 API定義 →EnumWindowsでXLMAINクラス列挙 →ShowWindow(1)+MoveWindow
学んだこと
- Excelのプロセスモデル: 複数ブックを開いても
Get-Processは1行しか返さない。ウィンドウ単位で操作するにはEnumWindows+ クラス名フィルタが必要 - SW_RESTOREとSW_SHOWNORMALの違い:
SW_RESTORE(9)は「直前の状態に戻す」、SW_SHOWNORMAL(1)は「最大化/最小化フラグをクリアして通常表示」。リサイズを効かせたいなら後者 - バッチとPowerShellの分離: here-stringの埋め込みは行結合で壊れる。最初から
.ps1を分離した方が速い - デバッグ手順: 「動かない」の切り分けは、ハンドルが取れているか → 取れたハンドルの数は正しいか → API呼び出しの戻り値は何か、の順に追う