PowerShellで書いたスクリプトをWindowsバッチから実行する方法（を探して試行錯誤したら答えがQiitaにあった話）

# お仕事でこんな場面ありませんか？

* 非エンジニアスタッフ向けに、Windows 端末で実行してもらうツールを撒きたい
* exe や jar を撒くほど大したツールでもない
* 「ファイルをダブルクリックして実行して下さいね」で済ませたい

# 何で書きますか？

全てのWindows 端末（もちろんメインストリームor延長サポート期間中のものに限る）で、標準で扱えるスクリプトは…。

1. Windows バッチ
  * コマンドは echo、cd、dir と ipconfig /all しか打てない
  * たまに ls とか打って怒られます
1. VBScript
  * （略）
1. JScript
  * 少し前までは大体これでした
  * .NET Framework を使いたいときはあきらめて exe を撒きます
1. PowerShell
  * .NET Framework が標準で使えて、C# ソースも書けるので、ここ数年は PowerShell が好きです
  * でも、ダブルクリックでメモ帳が開くし、実行ポリシーのせいで実行がブロックされるし…
 
# どうやって書きますか？

ツールの拡張子は "*.bat" にしておいて、Windowsバッチから "powershell" コマンドで PowerShell を実行しています。

## PowerShell のコマンド部分は？

ファイルはなるべく単体で撒きたい…。

### 普通に書いたあと、改行と無駄なスペースを削って1行にまとめる

普通のやり方ですね。
でも、主に可読性の問題とか、改行を含む文字列を扱うときに改行コードじゃなくてこれ→@'xxxxx'@で書くことが多いので、もにょもにょ。

### 普通に書いたあと、Base64エンコードする

PowerShellのリファレンスを見ると、 "-EncodedCommand" というオプションがあります。
このオプションを使うと、指定された文字列を Base64 デコードして実行してくれます。
こんな感じです。

```:execute_powershell_0.bat
@powershell -NoProfile -ExecutionPolicy Unrestricted -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAZgBvAHIAIAAoACQAaQA9ADAAOwAgACQAaQAgAC0AbAB0ACAANwAwADAAOwAgACQAaQArACsAKQB7ACAAIAAgACAAIABbAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwAuAEMAdQByAHMAbwByAF0AOgA6AFAAbwBzAGkAdABpAG8AbgAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAHIAYQB3AGkAbgBnAC4AUABvAGkAbgB0ACgAJABpACwAIAAkAGkAKQA7ACAAIAAgACAAIABbAFMAeQBzAHQAZQBtAC4AVABoAHIAZQBhAGQAaQBuAGcALgBUAGgAcgBlAGEAZABdADoAOgBTAGwAZQBlAHAAKAAxADAAKQA7ACAAfQA=
```

もちろん、デコードしたときは改行もそのまま復元されます。
Windows バッチを踏み台にして PowerShell を起動するやり方の中では、一番長くこれを使っていました。
でも、可読性という点では０点ですし、なによりスクリプトの使いまわしに手間がかかる（いったん PowerShell コマンドを手作業でデコードして、修正して、またエンコードする…）ので、これももにょもにょ。

### Windows バッチでも、複数行にわたるコマンドは書ける！ けど…

つい最近知ったのですが、Windows バッチでも行末にハット＾を付ければバッチコマンドを続けて書けるのですね。↓

```:execute_powershell_1.bat
@echo off
REM #################### powershell command start ####################
set PSCOMMAND=^
Add-Type -AssemblyName System.Windows.Forms;                                          ^
for ($i=0; $i -lt 700; $i++){                                                         ^
    [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point($i, $i);^
    [System.Threading.Thread]::Sleep(10);                                             ^
}                                                                                     ^

REM #################### powershell command end   ####################
powershell -NoProfile -ExecutionPolicy RemoteSigned -Command %PSCOMMAND%
exit
```

これで可読性を損なわずに PowerShell を書くことができる！と思いきや…。あれ、@'xxxxx'@ の中の改行ができていない？
echo してみると、PowerShellスクリプト部分は1行になってました。

そりゃそうか。

### すでに先人がベストプラクティスを公開していた（Windows バッチに自分自身のファイルを食わせる）

https://qiita.com/cd01/items/82829ba0ec0f59e1b04d

解説を入れると、

1. 最初の行で Windows バッチの "powershell" コマンドを呼び出す
1. 最初の行の中の PowerShell スクリプト部分では、
  1. 自分自身のファイルを1行目（まさにその行）以外全て読み込んで配列にし、それぞれの要素を改行コードでjoinする
  1. 作成した文字列をスクリプトとして実行する
1. Windows バッチ制御部分に戻ったら、即座にeofまで飛ぶ（よってそれ以降の内容は無視される）
1. 次の行から、PowerShell で行いたい操作を書く（この部分は無視されるので、Windows バッチ実行時にエラーとならない）

すごい、柔らか頭！

JScript から PowerShell を実行する場合の方法を書いていらっしゃる方もいました。

https://qiita.com/itasnasal/items/c9bd297c6c2050db6d76

これも理屈は同じですね。

とりあえず自分でも書いてみました。

```:execute_powershell_2.bat
@powershell -NoProfile -ExecutionPolicy Unrestricted "$s=[System.Management.Automation.ScriptBlock]::create((Get-Content \"%~f0\"|Where-Object{$_.readcount -gt 1}) -join \"`n\");&$s" %*&goto:eof
#### write PowerShell script here. ####
Add-Type -AssemblyName System.Windows.Forms;
for ($i=0; $i -lt 700; $i++){
    [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point($i, $i);
    [System.Threading.Thread]::Sleep(10);
}
```

# 学んだこと

* Windows バッチでもコマンドの改行はできるけど、変数に改行を入れるのはトリッキーなやり方が必要
* 集合知すごい
* PowerShell つよい