ネットで断片的な情報しか見つからなくて苦戦していたのですが、解決したので記事にしました。好きなウィンドウのタイトルを指定して位置とサイズを設定します。
目次
- スクリプト
- 使い方
- スクリプトの解説
スクリプト
先にスクリプト全体を示します。Set-WindowPosition.ps1という名前でファイルを作成して下のスクリプトをコピペしてください。
param (
[int32]$x,
[int32]$y,
[int32]$width,
[Int32]$hight,
[string]$windowNames
)
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public const int SW_SHOWNORMAL = 1;
}
"@
$windowNameArray = $windowNames -split '\s+'
$enumCallback = {
param ($hWnd, $lParam)
$sb = New-Object System.Text.StringBuilder 256
$wndText = [User32]::GetWindowText($hWnd, $sb, $sb.Capacity)
if ($wndText -gt 0) {
$windowTitle = $sb.ToString()
foreach ($windowName in $windowNameArray) {
if ($windowTitle -like "*$windowName*") {
Write-Host "Window name: $windowTitle"
Write-Host "Position: ($x, $y)"
Write-Host "Size: ($width, $hight)"
[User32]::ShowWindow($hWnd, [User32]::SW_SHOWNORMAL) | Out-Null
[User32]::SetForegroundWindow($hWnd) | Out-Null
[User32]::SetWindowPos($hWnd, [IntPtr]::Zero, $x, $y, $width, $hight, 0) | Out-Null
break
}
}
}
return $true
}
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero) | Out-Null
使い方
Windows PowerShellを開いて、Set-WindowPosition.ps1が置いてあるディレクトリに移動し、下のコマンドを打ち込めば完了です($
はプロンプトなので打たない)。こちらの例では、スクリーン左上から座標 (x, y) = (100, 100) の位置に縦横 500x500 の大きさで、「Chrome」をタイトルに含むウィンドウを配置します。
$ & ".\Start-WindowAtPosition.ps1" -x 100 -y 100 -width 500 -hight 500 -windowNames Chrome
実行結果(標準出力)
Window name: 新しいタブ - Google Chrome
Position: (100, 100)
Size: (500, 500)
ウィンドウのタイトルはタスクバーのアイコンにマウスポインタをかざしたときに浮き出てくる文字列のことです。Google Chromeの新しいウィンドウを開いた時の名前は「新しいタブ - Google Chrome」になります。-windowNames
には半角スペース区切りで複数指定できます。
ウィンドウタイトル指定時の注意点
ウィンドウが読み込まれるまではタイトルが決まらないため、ウィンドウのロードが終わらないうちに上のスクリプトを実行してしまうと上手くいかない場合があります。
例えばGoogle Chromeで https://google.com を開いた時のタイトルの変遷は、筆者の環境だと
0.000s : なし
0.182s : 無題 - Google Chrome
0.506s : Google - Google Chrome
のようになりました。タイトルが変わってしまう対策として複数タイトルを指定しておくといいです。
スクリプトの解説
はじめに載せたスクリプトを少しずつ組み立てるつもりで解説していきます。
画面上のすべてのウィンドウを列挙
Win32 APIからEnumWindows
を読み込みます。
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
}
"@
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero)
EnumWindows
は画面上のすべてのウィンドウを列挙します。列挙されたウィンドウに行う処理を第1引数にコールバック関数として渡します。第2引数で追加のデータを渡すことができますが、今回は使わないのでNULLを渡しています。
目的のウィンドウを見つけたら位置とサイズを設定する
EnumWindows
に渡す$enumCallback
を書いて、各ウィンドウになんの処理をするか指定します。今回は指定したタイトルのウィンドウを見つけた場合に位置とサイズを設定するという処理を行います。そのため、ウィンドウタイトルを取得するGetWindowText
とウィンドウの位置とサイズを指定するSetWindowPos
を読み込みます。
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
}
"@
$enumCallback = {
param ($hWnd, $lParam)
$sb = New-Object System.Text.StringBuilder 256
$wndText = [User32]::GetWindowText($hWnd, $sb, $sb.Capacity)
if ($wndText -gt 0) {
$windowTitle = $sb.ToString()
if ($windowTitle -like "*新しいタブ*") {
[User32]::SetWindowPos($hWnd, [IntPtr]::Zero, 100, 100, 500, 500, 0)
}
}
return $true
}
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero)
$enumCallback
はウィンドウハンドル$hWnd
と追加で渡すデータ$lParam
(今回は使いません)を引数にとります。$sb
はタイトルを格納するための256文字の容量を持つオブジェクトです。$hWnd
と$sb
をGetWindowText
に渡してウィンドウタイトルの文字数$wndText
を取得します。文字数が0より大きい場合、if文の中身が実行されます。if文では、最初に$windowTitle
を取得します。次のif文の中身は、$windowTitle
に「新しいタブ」が含まれるときに実行されます。前後の*
は任意の文字列を表します。「新しいタブ」が任意の文字列に囲まれることを表します。if文の中で位置とサイズを指定するSetWindowPos
を実行します。引数はそれぞれ、ウィンドウハンドル、他のウィンドウとの前後関係の指定(今回は使わないのでNULL)、x座標、y座標、幅、高さ、その他のオプション(今回は使わないので0)を指定します。最後のreturn $true
で次のウィンドウの処理に移ります。
スクリプトの外から値を渡せるようにする
param (
[int32]$x,
[int32]$y,
[int32]$width,
[Int32]$hight,
[string]$windowName
)
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
}
"@
$enumCallback = {
param ($hWnd, $lParam)
$sb = New-Object System.Text.StringBuilder 256
$wndText = [User32]::GetWindowText($hWnd, $sb, $sb.Capacity)
if ($wndText -gt 0) {
$windowTitle = $sb.ToString()
if ($windowTitle -like "*$windowName*") {
[User32]::SetWindowPos($hWnd, [IntPtr]::Zero, $x, $y, $width, $hight, 0)
}
}
return $true
}
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero)
画面最大化、最小化時も動くようにする
今のままでも動きますが、実は画面を最大化や最小化していると位置とサイズの設定がされません。そこで、最大化や最小化を解除するためにShowWindow
を追加します。ついでに画面を最前面に持ってくるSetForegroundWindow
も追加します。
param (
[int32]$x,
[int32]$y,
[int32]$width,
[Int32]$hight,
[string]$windowName
)
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public const int SW_SHOWNORMAL = 1;
}
"@
$enumCallback = {
param ($hWnd, $lParam)
$sb = New-Object System.Text.StringBuilder 256
$wndText = [User32]::GetWindowText($hWnd, $sb, $sb.Capacity)
if ($wndText -gt 0) {
$windowTitle = $sb.ToString()
if ($windowTitle -like "*$windowName*") {
[User32]::ShowWindow($hWnd, [User32]::SW_SHOWNORMAL)
[User32]::SetForegroundWindow($hWnd)
[User32]::SetWindowPos($hWnd, [IntPtr]::Zero, $x, $y, $width, $hight, 0)
}
}
return $true
}
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero)
ShowWindow
の第2引数に1を指定すると最大化や最小化が解除されます。しかし、意味が分かりやすいように、SW_SHOWNORMAL
という定数を導入しています。
仕上げ
もうほとんど完成ですが、複数のタイトルを指定できるようにします。以下は最初に示したのと同じスクリプトです。
param (
[int32]$x,
[int32]$y,
[int32]$width,
[Int32]$hight,
[string]$windowNames
)
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class User32 {
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public const int SW_SHOWNORMAL = 1;
}
"@
$windowNameArray = $windowNames -split '\s+'
$enumCallback = {
param ($hWnd, $lParam)
$sb = New-Object System.Text.StringBuilder 256
$wndText = [User32]::GetWindowText($hWnd, $sb, $sb.Capacity)
if ($wndText -gt 0) {
$windowTitle = $sb.ToString()
foreach ($windowName in $windowNameArray) {
if ($windowTitle -like "*$windowName*") {
Write-Host "Window name: $windowTitle"
Write-Host "Position: ($x, $y)"
Write-Host "Size: ($width, $hight)"
[User32]::ShowWindow($hWnd, [User32]::SW_SHOWNORMAL) | Out-Null
[User32]::SetForegroundWindow($hWnd) | Out-Null
[User32]::SetWindowPos($hWnd, [IntPtr]::Zero, $x, $y, $width, $hight, 0) | Out-Null
break
}
}
}
return $true
}
[User32]::EnumWindows($enumCallback, [IntPtr]::Zero) | Out-Null
半角スペース区切りでタイトルを指定するので、$windowName
を$windowNames
に書き換えて、それを配列に分割したのが$windowNameArrayです。foreachでそれらの要素全部に処理を行います。最後に、標準出力に情報を表示するために Write-Host
を使って完成です。実行結果が標準出力に表示されてほしくない行に| Out-Null
をつけています。