前回作成したスクリプトで、実際にアプリケーションを起動して欲しい場所に自動的に配置する方法を紹介した。前回作成したスクリプト「deploy_001.ps1」は次の通りだ。

#!/usr/bin/env pwsh

#====================================================================
# アプリケーション起動とデプロイ
#====================================================================

# マイナビニュース 企業IT 新着記事一覧
$cmd='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$url='https://news.mynavi.jp/list/headline/business/enterprise/'
& $cmd --new-window $url

Sleep 1
window_deploy "*" "新着記事.*" 100 0 1200 800

# マイナビニュース IT Search+
$cmd='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$url='https://news.mynavi.jp/itsearch/'
& $cmd --new-window $url

Sleep 1
window_deploy "*" "TECH+.*" 100 800 1200 600

# Windows Terminal
wt
Sleep 1
window_deploy WindowsTerminal ".*" -50 0 1200 1100

# メモ帳
notepad
Sleep 1
window_deploy notepad ".*" -50 -40 1200 300

使うディスプレイが1つの場合は、これで十分だ。しかし、テレワーク時代の今の働き方を考えると、ちょっと不便なところがある。テレワークでは、会社や自宅、そして移動中など複数の場所でノートPCを使って作業することになるだろう。会社では外部ディスプレイにつなぎ、自宅では別の外部ディスプレイにつなぎ、それ以外の場所ではノートPCのディスプレイで作業する、といった具合だ。それぞれ解像度の異なる環境で利用するということになると、上記処理では不十分なのだ。指定しているウインドウの幅や高さが絶対値なので、異なるサイズのディスプレイに対応することができないのである。

これに対処する方法はいくつか考えられる。最も細かく丁寧に対応させるなら、ディスプレイごとに最適な配置を行うようにスクリプトに分岐処理を加えるか、もしくは個別にスクリプトを作成するか、といったところだ。しかし、これは少々面倒くさい。まずはもっと汎用的に使える方法で、複数の異なるサイズのディスプレイに対応させてしまおう。

スクリーンサイズに対する相対値で指定できるようにして対処

最も簡単で汎用的な方法は、window_deploy.ps1に指定する座標とサイズを、ピクセルベースの絶対数ではなく、スクリーンサイズに対する割合として指定できるようにすることだ。これならディスプレイのサイズが変わっても、それに合わせて指定される数値は自動的に計算されるので、いちいちデプロイスクリプトを作り変える必要がない。それが自分にとって最適な配置になっているかどうかは別問題だが、最初に実装する解決策としては悪くない。

スクリーンサイズに対する比率でデプロイできるようにしたwindow_deploy.ps1は、次の通りだ。

#!/usr/bin/env pwsh

#====================================================================
# ウインドウ配置スクリプト
#====================================================================

#====================================================================
# 引数を処理
#====================================================================
Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
    [Int32]$X="0",          # ウインドウ左上X座標
    [Int32]$Y="0",          # ウインドウ左上Y座標
    [Int32]$Width="0",      # ウインドウ幅
    [Int32]$Height="0",     # ウインドウ高
    [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
    [Double]$WidthRatio="-1",   # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$HeightRatio="-1",  # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
)

#====================================================================
# ウインドウプロセスを一覧表示
#====================================================================
if ($WindowProcessList) {
    Get-Process                         |
    ? {$_.MainWindowHandle -ne 0 }              |
    Format-Table -Property Id,ProcessName,MainWindowTitle
    exit
}

#====================================================================
# Win32 APIのインポート
#====================================================================
Add-Type @"
using System;
using System.Runtime.InteropServices;

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

public class WinAPI
{
    // ウインドウの現在の座標データを取得する関数
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    // ウインドウの座標を変更する関数
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    // スクリーンサイズを取得する関数
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int nIndex);
}
"@

#====================================================================
# ウインドウを配置する関数 Deploy-Window
#====================================================================
function Deploy-Window {
    param (
        $wh  # ウインドウハンドラ
    )

    # ウインドウ座標データ構造体
    $rc = New-Object RECT

    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Xの値を算出
        $X = $screenWidth + $X - $Width
    }
    if ($Y -lt 0) {
        # スクリーン高さを取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Yの値を算出
        $Y = $screenHeight + $Y - $Height
    }

    # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Xの値を算出
        $X = $screenWidth * $XRatio
    }
    if ($YRatio -ge 0) {
        # スクリーン高さを取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Yの値を算出
        $Y = $screenHeight * $YRatio
    }

    # 幅が0~1の割合で指定されている場合にWidthの値へ変換
    if ($WidthRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Widthの値を算出
        $Width = $screenWidth * $WidthRatio
    }
    # 幅指定がないため、取得した座標データからウインドウの現在の幅を設定
    elseif ($Width -eq 0) {
        $Width = $rc.Right - $rc.Left;
    }

    # 高が0~1の割合で指定されている場合にHeightの値へ変換
    if ($HeightRatio -ge 0) {
        # スクリーン高を取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Heightの値を算出
        $Height = $screenHeight * $HeightRatio
    }
    # 高指定がないため、取得した座標データからウインドウの現在の高を設定
    elseif ($Height -eq 0) {
        $Height = $rc.Bottom - $rc.Top;
    }

    # ウインドウを指定された座標に指定されたサイズで配置する
    [WinAPI]::MoveWindow($wh, $X, $Y, $Width, $Height, $true) > $null
}

#====================================================================
# 対象となるウインドウを選択し、サイズを変更
#====================================================================
Get-Process -Name $processName |
    ? { $_.MainWindowHandle -ne 0 } |
    ? { $_.MainWindowTitle -match "$windowTitle" } |
    % {
        # ウインドウを配置
        Deploy-Window($_.MainWindowHandle);
}

前回作成したスクリプトとの違いは次の通りだ。

--- ".\\window_deploy.ps1.old"  2021-09-30 18:34:39.983436500 +0900
+++ ".\\window_deploy.ps1"  2021-09-30 13:26:05.160958800 +0900
@@ -14,6 +14,10 @@
    [Int32]$Y="0",          # ウインドウ左上Y座標
    [Int32]$Width="0",      # ウインドウ幅
    [Int32]$Height="0",     # ウインドウ高
+   [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
+   [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
+   [Double]$WidthRatio="-1",   # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
+   [Double]$HeightRatio="-1",  # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
 )

@@ -72,16 +76,6 @@
    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

-   # 幅の指定がない場合、取得した座標データからウインドウの幅を計算
-   if ($Width -eq 0) {
-       $Width = $rc.Right - $rc.Left;
-   }
-
-   # 高さの指定がない場合、取得した座標データからウインドウの高さを計算
-   if ($Height -eq 0) {
-       $Height = $rc.Bottom - $rc.Top;
-   }
-
    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        # スクリーン幅を取得
@@ -96,6 +90,44 @@
        $Y = $screenHeight + $Y - $Height
    }

+   # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
+   if ($XRatio -ge 0) {
+       # スクリーン幅を取得
+       $screenWidth = [WinAPI]::GetSystemMetrics(0);
+       # Xの値を算出
+       $X = $screenWidth * $XRatio
+   }
+   if ($YRatio -ge 0) {
+       # スクリーン高さを取得
+       $screenHeight = [WinAPI]::GetSystemMetrics(1);
+       # Yの値を算出
+       $Y = $screenHeight * $YRatio
+   }
+
+   # 幅が0~1の割合で指定されている場合にWidthの値へ変換
+   if ($WidthRatio -ge 0) {
+       # スクリーン幅を取得
+       $screenWidth = [WinAPI]::GetSystemMetrics(0);
+       # Widthの値を算出
+       $Width = $screenWidth * $WidthRatio
+   }
+   # 幅指定がないため、取得した座標データからウインドウの現在の幅を設定
+   elseif ($Width -eq 0) {
+       $Width = $rc.Right - $rc.Left;
+   }
+
+   # 高が0~1の割合で指定されている場合にHeightの値へ変換
+   if ($HeightRatio -ge 0) {
+       # スクリーン高を取得
+       $screenHeight = [WinAPI]::GetSystemMetrics(1);
+       # Heightの値を算出
+       $Height = $screenHeight * $HeightRatio
+   }
+   # 高指定がないため、取得した座標データからウインドウの現在の高を設定
+   elseif ($Height -eq 0) {
+       $Height = $rc.Bottom - $rc.Top;
+   }
+
    # ウインドウを指定された座標に指定されたサイズで配置する
    [WinAPI]::MoveWindow($wh, $X, $Y, $Width, $Height, $true) > $null
 }

基本的に前回作成したスクリプトの機能は全てそのまま入っており、そこにスクリーンサイズの比率による指定を追加している。

変更内容

変更内容を詳しくみてみよう。まず、パラメータとして新しく-XRatio、-YRatio、-HeightRatio、-WidthRatioが追加してある。値はどれも実数(Double型)で、0から1の間で指定する。

    [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
    [Double]$WidthRatio="-1",   # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$HeightRatio="-1",  # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)

例えば、「-XRadio 0.5」と指定した場合、スクリーンの幅が「1980」であれば「-X 990」を指定したのと同じになる。「-WidthRatio 0.5」なら、スクリーンの幅が1980なら「-Width 990」を指定したのと同じだ。スクリーンのサイズに対して比率で指定できるので、スクリーンのサイズが変更されればそれに合わせて指定されるサイズが変わるというわけだ。

実装は次のようにしてある。-XRatio、-YRatio、-HeightRatio、-WidthRatioのデフォルト値を-1にしてあるので、それ以外の値が指定されていた場合に、割合計算を行って座標とサイズを計算するという処理を行っている。

◆X座標とY座標をスクリーンサイズの割合で指定する処理

    # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Xの値を算出
        $X = $screenWidth * $XRatio
    }
    if ($YRatio -ge 0) {
        # スクリーン高さを取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Yの値を算出
        $Y = $screenHeight * $YRatio
    }

◆ウインドウの幅をスクリーンサイズの割合で指定する処理

    # 幅が0~1の割合で指定されている場合にWidthの値へ変換
    if ($WidthRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Widthの値を算出
        $Width = $screenWidth * $WidthRatio
    }
    # 幅指定がないため、取得した座標データからウインドウの現在の幅を設定
    elseif ($Width -eq 0) {
        $Width = $rc.Right - $rc.Left;
    }

◆ウインドウの高さをスクリーンサイズの割合で指定する処理

    # 高が0~1の割合で指定されている場合にHeightの値へ変換
    if ($HeightRatio -ge 0) {
        # スクリーン高を取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Heightの値を算出
        $Height = $screenHeight * $HeightRatio
    }
    # 高指定がないため、取得した座標データからウインドウの現在の高を設定
    elseif ($Height -eq 0) {
        $Height = $rc.Bottom - $rc.Top;
    }

処理自体はそれほど難しくない。そもそも指定される値が0から1の間の実数となっているので、スクリーンサイズを取得して、それにパラメータで指定された値を掛けるだけで欲しい値が得られる。それを使用しているだけだ。

window_move.ps1もアップデート

window_deploy.ps1を作成する前に、window_move.ps1とwindow_resize.ps1を作成した。window_deploy.ps1は具体的には、window_move.ps1とwindow_resize.ps1を組み合わせて作成しただけだ。つまり、今回window_deploy.ps1に適用した変更は、window_move.ps1とwindow_resize.ps1に逆に適用することができるわけだ。

window_move.ps1

#!/usr/bin/env pwsh

#====================================================================
# ウインドウ移動スクリプト
#====================================================================

#====================================================================
# 引数を処理
#====================================================================
Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
    [Int32]$X="0",          # ウインドウ左上X座標
    [Int32]$Y="0",          # ウインドウ左上Y座標
    [Double]$XRatio="-1",       # ウインドウ左上X座標(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$YRatio="-1",       # ウインドウ左上Y座標(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
)

#====================================================================
# ウインドウプロセスを一覧表示
#====================================================================
if ($WindowProcessList) {
    Get-Process                         |
    ? {$_.MainWindowHandle -ne 0 }              |
    Format-Table -Property Id,ProcessName,MainWindowTitle
    exit
}

#====================================================================
# Win32 APIのインポート
#====================================================================
Add-Type @"
using System;
using System.Runtime.InteropServices;

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

public class WinAPI
{
    // ウインドウの現在の座標データを取得する関数
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    // ウインドウの座標を変更する関数
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    // スクリーンサイズを取得する関数
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int nIndex);
}
"@

#====================================================================
# ウインドウを移動する関数 Move-Window
#====================================================================
function Move-Window {
    param (
        $wh  # ウインドウハンドラ
    )

    # ウインドウ座標データ構造体
    $rc = New-Object RECT

    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # 取得した座標データからウインドウの幅と高さを計算
    $width = $rc.Right - $rc.Left;
    $height = $rc.Bottom - $rc.Top;

    # マイナス指定のXおよびYを正値へ変換
    if ($X -lt 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Xの値を算出
        $X = $screenWidth + $X - $width
    }
    if ($Y -lt 0) {
        # スクリーン高さを取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Yの値を算出
        $Y = $screenHeight + $Y - $height
    }

    # 座標が0~1の割合で指定されている場合にXおよびYの正値へ変換
    if ($XRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Xの値を算出
        $X = $screenWidth * $XRatio
    }
    if ($YRatio -ge 0) {
        # スクリーン高さを取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Yの値を算出
        $Y = $screenHeight * $YRatio
    }

    # ウインドウのサイズはそのままに、左上の場所を変更
    [WinAPI]::MoveWindow($wh, $X, $Y, $width, $height, $true) > $null
}

#====================================================================
# 対象となるウインドウを選択し、サイズを変更
#====================================================================
Get-Process -Name $processName |
    ? { $_.MainWindowHandle -ne 0 } |
    ? { $_.MainWindowTitle -match "$windowTitle" } |
    % {
        # ウインドウを移動
        Move-Window($_.MainWindowHandle);
}

ピクセルを考えずに割合で場所を指定できると、実は結構便利だ。

window_resize.ps1もアップデート

続いて、window_resize.ps1もアップデートしてみよう。

#!/usr/bin/env pwsh

#====================================================================
# ウインドウリサイズスクリプト
#====================================================================

#====================================================================
# 引数を処理
#====================================================================
Param(
    [String]$ProcessName="*",   # プロセス名
    [String]$WindowTitle=".*",  # ウインドウタイトル(正規表現)
    [Int32]$Width="0",      # ウインドウ幅
    [Int32]$Height="0",     # ウインドウ高さ
    [Double]$WidthRatio="0",    # ウインドウ幅(スクリーン幅を1とし、0~1の実数で指定)
    [Double]$HeightRatio="0",   # ウインドウ高(スクリーン高を1とし、0~1の実数で指定)
    [Switch]$WindowProcessList  # ウインドウプロセス一覧を表示
)

#====================================================================
# ウインドウプロセスを一覧表示
#====================================================================
if ($WindowProcessList) {
    Get-Process                         |
    ? {$_.MainWindowHandle -ne 0 }              |
    Format-Table -Property Id,ProcessName,MainWindowTitle
    exit
}

#====================================================================
# ウインドウサイズを変更する関数 Resize-Window
#====================================================================
Add-Type @"
using System;
using System.Runtime.InteropServices;

public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

public class WinAPI
{
    // ウインドウの現在の座標データを取得する関数
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    // ウインドウの座標を変更する関数
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    // スクリーンサイズを取得する関数
    [DllImport("user32.dll")]
    public static extern int GetSystemMetrics(int nIndex);
}
"@

function Resize-Window {
    param (
        $wh  # ウインドウハンドラ
    )

    # ウインドウ座標データ構造体
    $rc = New-Object RECT

    # ウインドウの現在の座標データを取得
    [WinAPI]::GetWindowRect($wh, [ref]$rc) > $null

    # 幅が0~1の割合で指定されている場合にWidthの値へ変換
    if ($WidthRatio -ge 0) {
        # スクリーン幅を取得
        $screenWidth = [WinAPI]::GetSystemMetrics(0);
        # Widthの値を算出
        $Width = $screenWidth * $WidthRatio
    }
    # 幅指定がないため、取得した座標データからウインドウの現在の幅を設定
    elseif ($Width -eq 0) {
        $Width = $rc.Right - $rc.Left;
    }

    # 高が0~1の割合で指定されている場合にHeightの値へ変換
    if ($HeightRatio -ge 0) {
        # スクリーン高を取得
        $screenHeight = [WinAPI]::GetSystemMetrics(1);
        # Heightの値を算出
        $Height = $screenHeight * $HeightRatio
    }
    # 高指定がないため、取得した座標データからウインドウの現在の高を設定
    elseif ($Height -eq 0) {
        $Height = $rc.Bottom - $rc.Top;
    }

    # 左上の場所はそのままに、ウインドウのサイズを変更
    [WinAPI]::MoveWindow($wh, $rc.Left, $rc.Top, $width, $height, $true) > $null
}

#====================================================================
# 対象となるウインドウを選択し、サイズを変更
#====================================================================
Get-Process -Name $processName |
    ? { $_.MainWindowHandle -ne 0 } |
    ? { $_.MainWindowTitle -match "$windowTitle" } |
    % {
        # ウインドウサイズを変更
        Resize-Window($_.MainWindowHandle);
}

必要に応じてすぐに書き換える、これがスクリプトの醍醐味

スクリプトのサイズがかなりの大きさになってきたが、実際に行っている処理は簡単なものだ。書き換えも簡単だし、今回説明したように新しい機能を追加するのも難しくない。このように、必要に応じてすぐに書き換えられるのが、こうしたスクリプト言語の便利なところだ。

すでに作成したスクリプトを使って別のスクリプトを作っているなら、追加するパラメータの位置を工夫するなどして、既存のスクリプトに影響を与えないようにしてやるところが1つのポイントだ。今回の変更もそのようにしてある。

次回はこうして書き換えたスクリプトを使って、実際にどのようにdeploy_001.ps1を書き換えるかについて説明するとともに、解像度の異なるディスプレイでの実行例を取り上げる。