お仕事でこんな場面ありませんか?
- 非エンジニアスタッフ向けに、Windows 端末で実行してもらうツールを撒きたい
- exe や jar を撒くほど大したツールでもない
- 「ファイルをダブルクリックして実行して下さいね」で済ませたい
何で書きますか?
全てのWindows 端末(もちろんメインストリームor延長サポート期間中のものに限る)で、標準で扱えるスクリプトは…。
- Windows バッチ
- コマンドは echo、cd、dir と ipconfig /all しか打てない
- たまに ls とか打って怒られます
- VBScript
- (略)
- JScript
- 少し前までは大体これでした
- .NET Framework を使いたいときはあきらめて exe を撒きます
- PowerShell
- .NET Framework が標準で使えて、C# ソースも書けるので、ここ数年は PowerShell が好きです
- でも、ダブルクリックでメモ帳が開くし、実行ポリシーのせいで実行がブロックされるし…
どうやって書きますか?
ツールの拡張子は "*.bat" にしておいて、Windowsバッチから "powershell" コマンドで PowerShell を実行しています。
PowerShell のコマンド部分は?
ファイルはなるべく単体で撒きたい…。
普通に書いたあと、改行と無駄なスペースを削って1行にまとめる
普通のやり方ですね。
でも、主に可読性の問題とか、改行を含む文字列を扱うときに改行コードじゃなくてこれ→@'xxxxx'@で書くことが多いので、もにょもにょ。
普通に書いたあと、Base64エンコードする
PowerShellのリファレンスを見ると、 "-EncodedCommand" というオプションがあります。
このオプションを使うと、指定された文字列を Base64 デコードして実行してくれます。
こんな感じです。
@powershell -NoProfile -ExecutionPolicy Unrestricted -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAZgBvAHIAIAAoACQAaQA9ADAAOwAgACQAaQAgAC0AbAB0ACAANwAwADAAOwAgACQAaQArACsAKQB7ACAAIAAgACAAIABbAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwAuAEMAdQByAHMAbwByAF0AOgA6AFAAbwBzAGkAdABpAG8AbgAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAHIAYQB3AGkAbgBnAC4AUABvAGkAbgB0ACgAJABpACwAIAAkAGkAKQA7ACAAIAAgACAAIABbAFMAeQBzAHQAZQBtAC4AVABoAHIAZQBhAGQAaQBuAGcALgBUAGgAcgBlAGEAZABdADoAOgBTAGwAZQBlAHAAKAAxADAAKQA7ACAAfQA=
もちろん、デコードしたときは改行もそのまま復元されます。
Windows バッチを踏み台にして PowerShell を起動するやり方の中では、一番長くこれを使っていました。
でも、可読性という点では0点ですし、なによりスクリプトの使いまわしに手間がかかる(いったん PowerShell コマンドを手作業でデコードして、修正して、またエンコードする…)ので、これももにょもにょ。
Windows バッチでも、複数行にわたるコマンドは書ける! けど…
つい最近知ったのですが、Windows バッチでも行末にハット^を付ければバッチコマンドを続けて書けるのですね。↓
@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 バッチに自分自身のファイルを食わせる)
解説を入れると、
- 最初の行で Windows バッチの "powershell" コマンドを呼び出す
- 最初の行の中の PowerShell スクリプト部分では、
- 自分自身のファイルを1行目(まさにその行)以外全て読み込んで配列にし、それぞれの要素を改行コードでjoinする
- 作成した文字列をスクリプトとして実行する
- Windows バッチ制御部分に戻ったら、即座にeofまで飛ぶ(よってそれ以降の内容は無視される)
- 次の行から、PowerShell で行いたい操作を書く(この部分は無視されるので、Windows バッチ実行時にエラーとならない)
すごい、柔らか頭!
JScript から PowerShell を実行する場合の方法を書いていらっしゃる方もいました。
これも理屈は同じですね。
とりあえず自分でも書いてみました。
@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 つよい