さて、今回はhtaとvbsを使ったローカルスクリプトをちょっとGUIのパワーシェルにして行こうかなって話。
パワーシェルでハローワールド
まずパワーシェルでハロワしましょう。
今回はファイルを作りたいのでパワーシェルに直打ちではないです。
文字を表示するだけならWrite-Hostを使います。
「Write-Host "ハローワールド";pause」とメモ帳に記載して「はろわ.ps1」UTF-8BOM付きで保存。
とりまOSデフォのパワーシェル5.1を使います。
デフォルトのエンコードがsjisなのでBOM付き保存が必要です。
※6以降を使う場合は別途インストールが必要。新しい方を使う場合は起動のexeが変わります。デフォの場合「powershell~」新しい方の場合は「pwsh~」になります。
※pwshの方のファイルはデフォがUTF-8BOMなしです。気になる場合はパワーシェルのコンソールで「[System.Text.Encoding]::Default」を打てば分かります。
セキュリティが高くてデフォルトでは実行出来ないので右クリックショートカット作成で「powershell -ExecutionPolicy Bypass -File C:\t\はろわ.ps1」パスは保存先のパスを指定。「-ExecutionPolicy」が実行ポリシーを現セッションのみ変更する指定で「Bypass」が全ての許可指定。「-File」はファイルから実行って意味。
GUIウィンドウでハローワールド
HTAの代わりにウィンドウを作らないといけないので次はウィンドウ表示
Add-Type -AssemblyName System.Windows.Forms $form=New-Object System.Windows.Forms.Form $form.ShowDialog()
これだけ。
1行目は「.NET Framework」ライブラリのウィンドウ(フォーム)を使える様にする指定
2行目はオブジェクトを変数に代入
3行目で表示
ファイルを上書きして実行(実行方法はさっきと同じ)
そうするとパワーシェルウィンドウとなんも表示されないウィンドウが表示されます。
これはパワーシェルが本体で、そこからウィンドウを表示してるから。
ウィンドウを✕閉じするとパワーシェルも一緒に閉じます。
次にウィンドウのタイトルとウィンドウ内でもハロワしてみましょう
オブジェクトを代入する時に「 -Property @{プロパティ設定}」でプロパティを設定しながら代入出来ます。
Add-Type -AssemblyName System.Windows.Forms $form = New-Object System.Windows.Forms.Form -Property @{ Text = "ハローワールド" Size = New-Object System.Drawing.Size(600, 400) } $label = New-Object System.Windows.Forms.Label -Property @{ Text = "ハローワールド" } $form.Controls.Add($label) $form.ShowDialog()
htaと違ってウィンドウ内には直接文字を打てません。
テキストを入力するにはテキスト入力エリア=ラベルを作ってそこに文字を指定します。
更にラベルを作ったら、それをウィンドウに追加「$form.Controls.Add($label)」しないとウィンドウ上にラベルが配置されません。変更したら上書き保存。
今回から起動ショートカットのコマンドに「-windowstyle hidden」を追加します。
具体的には「C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -ExecutionPolicy Bypass -File C:\t\はろわ.ps1」
これで表示メインじゃない起動元のパワーシェルを非表示(=バックグラウンド)にできます。
これでGUIハローワールドも終了
ボタン等の配置方法
htaはhtmlと同様の仕組みなので簡単にinputboxやtextarea、ボタンなどを配置出来ていましたが今回は違います。
テキストを表示するだけでもラベルが必要なようにhtmlなどで言えば全てのコントロール(テキストボックスやボタン等)がz-indexのように個々がレイヤー別にあり、コントロールに追加した順番に(後追加ほど手前に)表示されます。
<input type="button" value="フォルダリスト取得" onClick="getfolderlist()" >
としてた場合は
$btnFolderList = New-Object System.Windows.Forms.Button -Property @{ Text = "フォルダリスト取得" Location = New-Object System.Drawing.Point(680, 10) Size = New-Object System.Drawing.Size(100, 25) }
こんな感じで必ず座標と基本的にはサイズの指定も必要になる
htaで「onClick="getfolderlist()"」と関数へキックしていたが、処理する関数を呼ぶのと違って自身のコントロール(変数)のクリックに処理を記述します
$btnFolderList.Add_Click({処理})
で記述する事が出来ます。ただ毎回「New-Object System.Windows.Forms.Button -Property @{~」と書くのは面倒なのでコントロールの操作を関数化しましょうか
function newbutton{ param($tx,$x,$y,$w,$h) $setbt = New-Object System.Windows.Forms.Button -Property @{ Text = $tx Location = New-Object System.Drawing.Point($x, $y) Size = New-Object System.Drawing.Size($w, $h) } return $setbt }
例えば、この様に関数を作ります
$btnFolderList =newbutton "フォルダリスト取得" 680 10 100 25
そうすれば、この様に関数で設定出来ます。
関数を呼ぶ時は「,」不要です。コンソールのコマンド系に倣ってこういう形です。
一応プログラム的な書き方にも対応していて
$btnFolderList =newbutton( "フォルダリスト取得",680, 10, 100, 25)
という記述方法も可能です。括弧を付ける場合、区切りは「,」に変更する必要があります。
テキスト入力コントロールの生成
次はテキスト入力エリア、htaでは以下通りですが
<input type="text" name="ifo" size="100" value="C:\">
<textarea name="txa" cols="120" rows="25">リスト編集エリア</textarea>
パワーシェル(System.Windows.Forms)ではテキストエリアとインプットテキストとような区分けはありません。
テキストを入力するエリアに行入力(Multiline)を許すかどうかが違うだけで機能的にはTextBoxの挙動の差を設定するだけです。
function newtextbox{ param($tx,$x,$y,$w,$h,$ml = $false , $sb = "none",$ww=$true) $settb= New-Object System.Windows.Forms.TextBox -Property @{ Text = $tx Location = New-Object System.Drawing.Point($x, $y) Size = New-Object System.Drawing.Size($w, $h) Multiline = $ml ScrollBars = $sb #Vertical/Both WordWrap = $ww } return $settb }
こんな感じですね。
$txtReadPath =newtextbox $desktoppath 70 10 600 20
$txtArea=newtextbox "リスト編集エリア" 10 70 760 400 $true "Both" $false
呼ぶ時はこの様に記述し、デフォルト値を設定すれば引数のありなしも制御可能です。
HTAツールをパワーシェルGUIツール化
さて、概ねパワーシェルのGUI化が把握できたので実際に過去に作ったHTAツールをパワーシェルでGUI化してみます
Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName Microsoft.VisualBasic # 定義 $desktoppath=[Environment]::GetFolderPath("Desktop") # フォームの作成 $form = New-Object System.Windows.Forms.Form -Property @{ Text = "一括フォルダ生成" Size = New-Object System.Drawing.Size(800, 600) StartPosition = "CenterScreen" } # コントロール生成関数 function newlabel{ param($tx,$x,$y,$w,$h) $setlabel = New-Object System.Windows.Forms.Label -Property @{ Text = $tx Location = New-Object System.Drawing.Point($x, $y) Size = New-Object System.Drawing.Size($w, $h) } return $setlabel } function newtextbox{ param($tx,$x,$y,$w,$h,$ml = $false , $sb = "none",$ww=$true) $settb= New-Object System.Windows.Forms.TextBox -Property @{ Text = $tx Location = New-Object System.Drawing.Point($x, $y) Size = New-Object System.Drawing.Size($w, $h) Multiline = $ml ScrollBars = $sb #Vertical/Both WordWrap = $ww } return $settb } function newbutton{ param($tx,$x,$y,$w,$h) $setbt = New-Object System.Windows.Forms.Button -Property @{ Text = $tx Location = New-Object System.Drawing.Point($x, $y) Size = New-Object System.Drawing.Size($w, $h) } return $setbt } # コントロールの作成 # tx $lblReadPath = newlabel "読込パス:" 10 10 60 20 $lblOutputPath = newlabel "出力パス:" 10 480 60 20 # txbx $txtReadPath =newtextbox $desktoppath 70 10 600 20 $txtArea =newtextbox "リスト編集エリア" 10 70 760 400 $true "Both" $false $txtOutputPath =newtextbox $desktoppath 70 480 600 20 # btn $btnFolderList =newbutton "フォルダリスト取得" 680 10 100 25 $btnFileList =newbutton "ファイルリスト取得" 680 40 100 25 $btnMakeFolders =newbutton "フォルダ生成" 680 480 100 25 $btnNumberSequence =newbutton "連番入力" 10 510 100 25 #ボタン処理 $btnFolderList.Add_Click({ if ($txtReadPath.Text -eq "") { $txtArea.Text = "ディレクトリを入力して!" return } if (-not (Test-Path $txtReadPath.Text -PathType Container)) { $txtArea.Text = "ディレクトリが正しくありません" return } $folders = Get-ChildItem -Path $txtReadPath.Text -Directory | ForEach-Object { $_.Name } $txtArea.Text = $folders -join "`r`n" }) $btnFileList.Add_Click({ if ($txtReadPath.Text -eq "") { $txtArea.Text = "ディレクトリを入力して!" return } if (-not (Test-Path $txtReadPath.Text -PathType Container)) { $txtArea.Text = "ディレクトリが正しくありません" return } $files = Get-ChildItem -Path $txtReadPath.Text -File | ForEach-Object { $_.Name } $txtArea.Text = $files -join "`r`n" }) $btnMakeFolders.Add_Click({ if (-not (Test-Path $txtOutputPath.Text -PathType Container)) { [System.Windows.Forms.MessageBox]::Show("出力先ディレクトリが正しくありません", "エラー", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) return } $outputPath = $txtOutputPath.Text if (-not $outputPath.EndsWith("\")) { $outputPath += "\" } $folderNames = $txtArea.Text -split "`r`n" | Where-Object { $_ -ne "" } foreach ($folder in $folderNames) { try { New-Item -Path ($outputPath + $folder) -ItemType Directory -ErrorAction Stop | Out-Null } catch { # エラーは無視(既存フォルダなど) } } }) $btnNumberSequence.Add_Click({ $myput = [Microsoft.VisualBasic.Interaction]::InputBox("テキストエリアに数字連番生成するだけ`n数字を入力してください。", "連番入力") if ($myput -eq ""){return} if (-not ($myput -match '^\d+$')) { [System.Windows.Forms.MessageBox]::Show("数値(整数)を入力してください", "エラー", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) return } $n = [int]$myput $len = $myput.Length $result = 1..$n | ForEach-Object { $_.ToString("D$len") } $txtArea.Text = $result -join "`r`n" }) # コントロールをフォームに追加 $form.Controls.AddRange(@($lblReadPath, $txtReadPath, $btnFolderList, $btnFileList, $txtArea, $lblOutputPath, $txtOutputPath, $btnMakeFolders, $btnNumberSequence)) # フォームの表示 $form.ShowDialog()
1つずつ処理確認しつつほぼそのまま実装出来ました