PowerShell を bat として実行する方法

# Powershell で作ったツールを簡単に配布してダブルクリックで起動したい

昔（20年くらい？）から私はWSH(VBScript)をよく使っていたのですが、最近の若い者から「今はPowershellですよ。VBScriptなんて、もうすぐ無くなりますよ」と年寄り扱いされたので、去年からPowerShellを使うようになりました。
使ってみるとpowershell は強力なんだけど ps1 ファイルを配布しただけでは、ダブルクリックしても起動しないので、ツールとしては不便を感じていました。

いろいろやってみて WSH(VBScript) みたいにツール単体を配布するだけで簡単に起動できるものが出来たので、投稿します。

.bat にすることで起動方法のバリエーションが増えるので、ツールとしての利便性が格段に上がります。
是非お試しください。
私は powershell で作ったツールは全部この方法で起動してます。一度使ったら止められません。
~~なお、一回作ったものに機能追加するときは、一旦 .ps1 に変更して ISE でコーディング/デバッグしてから .bat に戻してます。~~
.ps1 で編集して出来上がったら、.bat をハードリンクで作成しておくと、実行時は .bat 編集時は .ps1 としてエディタで編集できるので便利です。

## 作り方
+ powershell を普通に（Windows Powershell ISE などで）.ps1 で作ります。
+ パラメータで渡したいものは、変数 $parm[0..8] にセットされている前提で作ってください。
+ 以下の HelloWorld.bat の最後の1行（Write-Host の行）以外の部分を .ps1 の頭の部分に挿入します
+ ファイル名を .bat に変更します
+ ファイルをメモ帳で開いてみて UTF-8 だった場合は、ANSI (Shift_JIS) で保存しなおします。
+ 実行用の.bat をハードリンクで作ります。＝＞ DOS 窓を管理者権限で開いて、mklink /H xxxxxx.bat xxxxxx.ps1

```HelloWorld.bat
:DUMMY for($i=1;$i -eq 0;$i++) {echo DUMMY} <#
@rem 上の行は batでは（goto されない）ラベル。
@rem パワーシェルでは（条件は常にfalseで実行されない）ラベル付きfor
@rem bat でも パワーシェルでもエラーにならずに、パワーシェルにとってのコメントブロックを開始。
@rem 2行目以降は bat を実行するエリア。パワーシェルから見れば単なるコメントブロック。
@rem /C オプションのときは bat は待たずに終了。オプションがなければ PAUSE で待つ。
@rem bat に渡された引数は、パワーシェルに$parm[0..8]で渡す。

@echo off
set ME=%~dpnx0
rem /C オプションの有無をチェック
if /i CHK%1==CHK/C (
  set CHK=EXIT
  shift
) else (
  set CHK=PAUSE
)
rem パワーシェルを実行。自分自身をコマンドの入ったテキストとして取込んで実行。
powershell -ExecutionPolicy RemoteSigned -Command "Invoke-Expression -Command (@('$parm=@(\"%1\",\"%2\",\"%3\",\"%4\",\"%5\",\"%6\",\"%7\",\"%8\",\"%9\")') + (Get-Content '%ME%') -join \"`n\")"
if /i %CHK%==EXIT exit /b
pause
exit /b
bat のエリアは、exitまで。ここから先は（exit後なので）bat にとっては単なるゴミ　#>
# ここから下はパワーシェルの領域

Write-Host "Hello World!"

```

.bat の中にコメント（rem 文）を入れてあります。何をやっているのかは、そちらをご覧ください。

最大のポイントは1行目。powershell のブロックコメント <# を始めたかったのですが、不用意に書くと bat でリダイレクトとして認識されてしまうので、これを避けるために工夫してます。
これ以外の書き方もあると思います。誰か、もっとカッコいい方法があったら教えてください。

## パラメータ渡しのサンプル tail.bat
（長いログファイルの最後の部分だけ見たかったので作りました）
powershell へのパラメータの渡し方のサンプルとして、tailコマンドの例を以下に添付します。
サンプルでは input のファイルをパラメータとして渡してます。

tai.bat の使い方は以下の通り、いろいろな起動方法で実行できるので、ps1 より格段に便利です。

+ tail.bat をダブルクリックして実行して、コマンドラインからファイル名を手で入力する
+ 入力のtxtファイルをマウスでドラッグして、tail.bat の上にドロップする
+ コマンドラインからコマンド入力＝＞　[パス\]tail [/C] [/N 行数] [ファイル名]
+ バッチファイルの中から呼ぶ＝＞ CALL [パス\]tail.bat /C [/N 行数] ファイル名

```tail.bat
:DUMMY for($i=1;$i -eq 0;$i++) {echo DUMMY} <#
@rem 上の行は batでは（goto されない）ラベル。パワーシェルでは（条件は常にfalseで実行されない）ラベル付きfor
@rem bat でも パワーシェルでもエラーにならずに、パワーシェルにとってのコメントブロックを開始したかっただけ。
@rem 2行目以降は bat を実行するエリア。パワーシェルから見れば単なるコメントブロック。
@rem /C オプションのときは待たずに終了。オプションがなければ PAUSE で待つ。
@rem bat に渡された引数は、パワーシェルに$parm[0..8]で渡す。

@echo off
set ME=%~dpnx0
rem /C オプションの有無をチェック
if /i CHK%1==CHK/C (
  set CHK=EXIT
  shift
) else (
  set CHK=PAUSE
)
rem パワーシェルを実行。自分自身をコマンドの入ったテキストとして取込んで実行。
powershell -ExecutionPolicy RemoteSigned -Command "Invoke-Expression -Command (@('$parm=@(\"%1\",\"%2\",\"%3\",\"%4\",\"%5\",\"%6\",\"%7\",\"%8\",\"%9\")') + (Get-Content '%ME%') -join \"`n\")"
if /i %CHK%==EXIT exit /b
pause
exit /b
bat のエリアは、exitまで。ここから先は（exit後なので）bat にとっては単なるゴミ　#>
# ここから下はパワーシェルの領域

# Write-Host "受け取ったパラメータ" ($parm[0..8] -join ", ")
# パラメータから /N スイッチをチェックして行数をセット。デフォルトは10行
$idx=0
if ($parm[$idx] -eq "/N") {
  $idx++
  $num=$parm[$idx]
  $idx++
} else {
  $num=10
}
# ファイル名を取得
if ($parm[$idx] -eq "") {
  $mymsg = "`r`ntail [/C] [/N 行数] [ファイル名] : ファイルの最後のn行（デフォルト10行）を出力。/C でpauseせずに画面を閉じます`r`n`r`n"
  $current=Get-Location
  $mymsg = "$mymsg `r`nカレントフォルダ $current `r`n`r`n対象ファイルの PATH を入力してください"
  $input = Read-Host $mymsg
} else {
  $input = $parm[$idx]
}
if ($input -eq ""){
  Write-Host "`r`nファイル名が空です`r`n`r`n"
  exit
}
if (Test-Path $input) {
  $fullpath = $input
} else {
  Write-Host "`r`n$input が見つかりません`r`n`r`n"
  exit
}

# ファイルの最後のn行を取得
Get-Content -Path $fullpath -Tail $num

```

##あとがき

昔これと同じ発想で perl を起動していました。当時は perl 結構流行ってたんですよね。最近あまり見かけませんね。
Power shell は何でもできるのは運用周りのスクリプトとして優れてますが、やっぱりとっつきにくいですよね。コマンドが独特で。

こちらのwebsiteもよろしくお願いします。
[[初心者のためのWEBシステムのインフラ構築]](https://olto3-sugi3.tk/ja/index.html)