8
5

More than 5 years have passed since last update.

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

Posted at

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

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

何で書きますか?

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

  1. Windows バッチ
    • コマンドは echo、cd、dir と ipconfig /all しか打てない
    • たまに ls とか打って怒られます
  2. VBScript
    • (略)
  3. JScript
    • 少し前までは大体これでした
    • .NET Framework を使いたいときはあきらめて exe を撒きます
  4. 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 を起動するやり方の中では、一番長くこれを使っていました。
でも、可読性という点では0点ですし、なによりスクリプトの使いまわしに手間がかかる(いったん 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 バッチに自分自身のファイルを食わせる)

解説を入れると、

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

すごい、柔らか頭!

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

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

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

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 つよい
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5