tag:crieit.net,2005:https://crieit.net/tags/%E4%BE%BF%E5%88%A9%E3%83%84%E3%83%BC%E3%83%AB/feed
「便利ツール」の記事 - Crieit
Crieitでタグ「便利ツール」に投稿された最近の記事
2022-05-26T20:41:56+09:00
https://crieit.net/tags/%E4%BE%BF%E5%88%A9%E3%83%84%E3%83%BC%E3%83%AB/feed
tag:crieit.net,2005:PublicArticle/18198
2022-05-26T20:41:56+09:00
2022-05-26T20:41:56+09:00
https://crieit.net/posts/aka-tool-batlauncher
PowerShell:指定フォルダのbatを実行するLauncherを作ってみた
<h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1>
<p>お仕事とかでテスト用batとか作ってしょっちゅうたたくけどいちいちコマンドプロンプトに打つのも面倒なので画面のはじっこにでもbat実行用のLauncherでも置いとけばいいかなと思って作ってみた。<br />
最初は適当にexe作ろうと思ったけどたまにはPowerShellでForm作るの復習がてらやってみようってことで昼休みに作ってたらまんまと罠にはまって一旦諦め。<br />
解決はしたものの悔しさと絶対覚えてられない自身があったので記事に残す。</p>
<h1 id="やりたいこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">やりたいこと</a></h1>
<ul>
<li>引数で渡されたPathにある.batファイルをそれぞれボタンコントロールにして一列に並べる。</li>
<li>ボタンをクリックすると対応するbatを実行する</li>
<li>後ろに隠れると不便だからTopMost</li>
<li>batファイルがなければエラーメッセージでもだして終了</li>
</ul>
<h1 id="単純にFormの話"><a href="#%E5%8D%98%E7%B4%94%E3%81%ABForm%E3%81%AE%E8%A9%B1">単純にFormの話</a></h1>
<p>最初のおまじないさえ知っておけばそんなに難しくない</p>
<p>こういうの(アセンブリ)使うよって教えてあげて</p>
<pre><code>Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
</code></pre>
<p>Form(外側)作ってあげて</p>
<pre><code>$Form = New-Object System.Windows.Forms.Form
$Form.Text = "BatToolLauncher"
$Form.Width = 180
$Form.Height = $BatList.Count*30 + 50
$Form.TopMost = $true
</code></pre>
<p>ボタンを配置する</p>
<pre><code>$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)
</code></pre>
<p>※$BatListはbatの一覧(Get-ChildItemの結果)<br />
※$xは$BatListをforeachで回した時の一時変数</p>
<h1 id="ボタンクリック時の挙動を設定する(今回の鬼門)"><a href="#%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E6%99%82%E3%81%AE%E6%8C%99%E5%8B%95%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%28%E4%BB%8A%E5%9B%9E%E3%81%AE%E9%AC%BC%E9%96%80%29">ボタンクリック時の挙動を設定する(今回の鬼門)</a></h1>
<p>ボタンクリック時の挙動を書くことだけは簡単。<br />
「Add_イベント」の形で定義してあげればいい。クリックイベントなら下の{}の中に書けばいい。</p>
<pre><code>$Button.Add_Click({})
</code></pre>
<p>簡単。なはずだった。<br />
とにもかくにも失敗例↓</p>
<pre><code>$Button.Add_Click({Start-Process -WindowStyle Hidden $x.FullName})
</code></pre>
<p>これは$x.FullNameでbatのフルパスが入るからそいつをStart-Processするだけ簡単だよね、と思って頭を抱えたもの。<br />
何が起こるかというと、実際のクリック時の$xを参照してくれる。foreachでぐるぐる回していたので、$xが指すのは最後のbat。つまり、どのボタンを押しても最後のbatファイルが実行される。<br />
というわけで半日かけて試行錯誤・調査して解決策↓</p>
<pre><code>$Button.Add_Click({Start-Process -WindowStyle Hidden $x.FullName}.GetNewClosure())
</code></pre>
<p>GetNewClosure()<br />
を書くことでこの{}で囲まれた部分はスクリプトブロックじゃなくてクロージャだよ、となるらしい。今回の話に沿わせるとこの行を書いた時点の変数の内容でFixしてくれるらしい。<br />
ちょっと説明の言葉が怪しい自覚はある。</p>
<h1 id="スクリプト全体"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E5%85%A8%E4%BD%93">スクリプト全体</a></h1>
<p>結果的にこんな感じ。</p>
<pre><code>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()
</code></pre>
<h1 id="おまけ"><a href="#%E3%81%8A%E3%81%BE%E3%81%91">おまけ</a></h1>
<p>ps1ファイルってそのままたたけないのでいつも汎用性持たせて作ってるbat</p>
<pre><code>powershell -ExecutionPolicy RemoteSigned %~dpn0.ps1 %*
</code></pre>
<p>同じ場所にある同名ps1を実行するbat<br />
いつもの使いまわしファイルがどこかにいったので久しぶりに手書きしたけどだいたいこんなだったはず。</p>
RedCol