2022-05-26に投稿

PowerShell:指定フォルダのbatを実行するLauncherを作ってみた

はじめに

お仕事とかでテスト用batとか作ってしょっちゅうたたくけどいちいちコマンドプロンプトに打つのも面倒なので画面のはじっこにでもbat実行用のLauncherでも置いとけばいいかなと思って作ってみた。
最初は適当にexe作ろうと思ったけどたまにはPowerShellでForm作るの復習がてらやってみようってことで昼休みに作ってたらまんまと罠にはまって一旦諦め。
解決はしたものの悔しさと絶対覚えてられない自身があったので記事に残す。

やりたいこと

  • 引数で渡されたPathにある.batファイルをそれぞれボタンコントロールにして一列に並べる。
  • ボタンをクリックすると対応するbatを実行する
  • 後ろに隠れると不便だからTopMost
  • batファイルがなければエラーメッセージでもだして終了

単純にFormの話

最初のおまじないさえ知っておけばそんなに難しくない

こういうの(アセンブリ)使うよって教えてあげて

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

Form(外側)作ってあげて

$Form = New-Object System.Windows.Forms.Form 
$Form.Text = "BatToolLauncher"
$Form.Width = 180
$Form.Height = $BatList.Count*30 + 50
$Form.TopMost = $true

ボタンを配置する

$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Point(0,0)
$Button.Top = $Button.Top + $i * 30
$Button.Size = New-Object System.Drawing.Size(160,30)
$Button.Text = $x.BaseName
$Form.Controls.Add($Button)

※$BatListはbatの一覧(Get-ChildItemの結果)
※$xは$BatListをforeachで回した時の一時変数

ボタンクリック時の挙動を設定する(今回の鬼門)

ボタンクリック時の挙動を書くことだけは簡単。
「Add_イベント」の形で定義してあげればいい。クリックイベントなら下の{}の中に書けばいい。

$Button.Add_Click({})

簡単。なはずだった。
とにもかくにも失敗例↓

$Button.Add_Click({Start-Process -WindowStyle Hidden $x.FullName})

これは$x.FullNameでbatのフルパスが入るからそいつをStart-Processするだけ簡単だよね、と思って頭を抱えたもの。
何が起こるかというと、実際のクリック時の$xを参照してくれる。foreachでぐるぐる回していたので、$xが指すのは最後のbat。つまり、どのボタンを押しても最後のbatファイルが実行される。
というわけで半日かけて試行錯誤・調査して解決策↓

$Button.Add_Click({Start-Process -WindowStyle Hidden $x.FullName}.GetNewClosure())

GetNewClosure()
を書くことでこの{}で囲まれた部分はスクリプトブロックじゃなくてクロージャだよ、となるらしい。今回の話に沿わせるとこの行を書いた時点の変数の内容でFixしてくれるらしい。
ちょっと説明の言葉が怪しい自覚はある。

スクリプト全体

結果的にこんな感じ。

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

if($args.Length -gt 0)
{
    $TargetDir = $args[0]
}
else
{
    $MyPath = $MyInvocation.MyCommand.Path
    $TargetDir = Split-Path -Parent $MyPath
}

# batファイルのリスト作成
$BatList = Get-ChildItem $TargetDir -Filter "*.bat"
if($BatList -eq $null)
{
    [System.Windows.Forms.MessageBox]::Show("no bat files", "Error")
    return
}
# Form作成
$Form = New-Object System.Windows.Forms.Form 
$Form.Text = "BatToolLauncher"
$Form.Width = 180
$Form.Height = $BatList.Count*30 + 50
$Form.TopMost = $true

# ボタン作成
$i = 0
foreach($x in $BatList)
{
    $Button = New-Object System.Windows.Forms.Button
    $Button.Location = New-Object System.Drawing.Point(0,0)
    $Button.Top = $Button.Top + $i * 30
    $Button.Size = New-Object System.Drawing.Size(160,30)
    $Button.Text = $x.BaseName
    # GetNewClosure様をたたえる!
    $Button.Add_Click({Start-Process -WindowStyle Hidden $x.FullName}.GetNewClosure())
    $Form.Controls.Add($Button)
    $i++
}
# 表示
$Form.ShowDialog()

おまけ

ps1ファイルってそのままたたけないのでいつも汎用性持たせて作ってるbat

powershell -ExecutionPolicy RemoteSigned %~dpn0.ps1 %*

同じ場所にある同名ps1を実行するbat
いつもの使いまわしファイルがどこかにいったので久しぶりに手書きしたけどだいたいこんなだったはず。

ツイッターでシェア
みんなに共有、忘れないようにメモ

RedCol

医療モダリティの開発に携わる底辺PG。主にDICOM周りをやるも毎度英語の壁にぶち当たり、泣きそうになりながら調べ続けて10年、いい加減アウトプットしときたいなと思ってようやく書き始める。飽き性なのでいつまで続くか心配。 スクリプト言語が好き、同じことを5回以上やるならスクリプト書こうとする。但し、書いたスクリプト保存しようとすると似たようなスクリプトが既に保存先にあることが多々あって困る。

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント