【PowerShell】他プロセスの実行方法まとめ
2018/11/22 追記
以下の修正を行いました。
- ちゃんとシンタックスハイライトを適用しました。
- 呼び出し元のプロセスに標準 / エラー出力をリダイレクトする方法を追記しました。
- 一部のリンクが 404 を返すようになったので削除しました。
前書き
何だかんだで最近 PowerShell を書く機会が増えまくっています。まぁ中身はほとんど C#なんですが。
で、全部 C#で書いてもいいんだけど、外部ツール使えば一瞬みたいなものもあるので、そう言うものはなるべくそっちでやってしまいたいです。特に zip 関連の処理。
シェルってぐらいなんだからそれぐらい簡単にできるでしょー?と思っていたら大分ハマったので色々とメモしておきます。
実行例
引数で受け取ったフォルダのサブフォルダ内にある zip ファイルをC:\Program Files\7-Zip\7z.exe
で解凍する、みたいなパターン。7z.exe にパスは通していない状態。
コマンドはx
コマンドを使います。
大体こんなコードになります。Get-ChildItem
?何のことかよくわからないですね…。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"*7z")) { #ここで7zipを使って解凍したい } }
普通に実行する
何をもって普通と呼ぶのかですが、こんな風にすりゃ呼べるんじゃねーの?と普通は思います。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root, "*7z")) { # ここで7zipを使って解凍したい $7zexe x -o$root $7zFile } }
$root
や$7zFile
にスペースが入っていたりするとやっぱりダメなので、安全性を鑑みてこのようにするのがベストでしょう。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"*7z")) { #ここで7zipを使って解凍したい & $7zexe x -o"$root" "$7zFile" } }
ちなみにこの&
はInvoke-Expression
のエイリアスです。まぁわざわざInvoke-Expression
なんて長ったらしいコマンドを書く必要は皆無なので tips として覚えておきましょう。
また、似たようなコマンドレットにInvoke-Item
がありますが、使える範囲が非常に狭いので覚えなくていいです。(既定のプログラムで txt 開きたいとかのケースだと使えるけど…。)
また、$?
を使うことで直前のコマンドが成功したかどうかを boolean で取得できるので、こんなこともできます。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"*7z")) { #ここで7zipを使って解凍したい & $7zexe x -o"$root" "$7zFile" if($?) { #解凍後にzipを削除する [System.IO.File]::Delete($7zFile) } } }
Start-Process
を使う
他の方法としてはStart-Process
コマンドレットを使用する必要があります。
System.Diagnostics.Process::Start
みたいなもんだと思えば OK です。
Start-Process
のパラメータ
色々と引数がありますが、頻繁に使うのはこのあたりでしょうか。
パラメータ | 用途 |
---|---|
-FilePath |
実行ファイルのパス。パス内にスペースが入っているとキレられるので、ダブルクオーテーションでくくってやる必要がある。 |
-ArgumentList |
実行ファイルへ渡す引数。一応String[] を受け付けるが、普通に一個のString を渡す方が安全。 |
-NoNewWindow |
プロセス実行時に別ウィンドウを立ち上げない。-WindowStyle とは併用不可。 |
-PassThru |
実行するプロセスのオブジェクトを生成してくれる。デフォルトではオフなので、戻り値が欲しい時は必須のパラメータとなる。後述。 |
-Wait |
指定したプロセスの実行が完了するまで PowerShell が待っていてくれる。 |
-WindowStyle |
プロセスを実行する際のウィンドウサイズを指定。規定値はNormal 。-NoNewWindow とは併用不可。 |
-WindowStyle
に指定可能な値
-WindowStyle
には以下の値を設定することができます。内容は読んで字の如く。
Normal
Hidden
Minimized
Maximized
Start-Process
でのプロセス実行例
と言うわけで、先ほどの処理を行うならこんな感じですね。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in$cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"\*7z")) { #ここで 7zip を使って解凍したい $arg = "x -o`"$root`" `"$7zFile`"" Start-Process -FilePath $7zexe -ArgumentList $arg -Wait -NoNewWindow } }
ただこの方法、標準出力 / エラー出力を取得することが出来ません…。
-PassThru を使って更に細かい制御を行う
先述した通り、-PassThru
オプションをつけるとプロセスのオブジェクトを作成してくれます。
具体的には、System.Diagnostics.Process
が取得できます。
Process
に含まれるプロパティExitCode
を見ることで処理結果からその後の処理を分岐させることが可能です。
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"*7z")) { #ここで7zipを使って解凍したい $arg = "x -o`"$root`" `"$7zFile`"" $proc = Start-Process -FilePath $7zexe -ArgumentList $arg -Wait -NoNewWindow -PassThru if($proc.ExitCode -eq 0) { #解凍後にzipを削除する [System.IO.File]::Delete($7zFile) } } }
他にもWaitForExit
メソッドあたりを使ってタイムアウトの制御なんかもできます。(当然-Wait
オプションをはずさないと意味ないですが)
System.Diagnostics.Process
を使う
え?PowerShell の文法なんか覚えられない?じゃあ C#書いたら?
$nIO = "System.IO" $cDirectory = "$nIO.Directory" -as [type] $nDiagnostics = "System.Diagnostics" $7zexe = "C:\Program Files\7-Zip\7z.exe" foreach($root in $cDirectory::GetDirectories($args[0])) { foreach($7zFile in $cDirectory::GetFiles($root,"*7z")) { #ここで7zipを使って解凍したい $arg = "x -o`"$root`" `"$7zFile`"" $proc = New-Object "$nDiagnostics.Process" $psi = New-Object "$nDiagnostics.ProcessStartInfo" $psi.FileName = $7zexe $psi.Arguments = $arg $psi.UseShellExecute = $false $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $proc.StartInfo = $psi $proc.Start() $proc.WaitForExit() if($proc.ExitCode -eq 0) { #解凍後にzipを削除する [System.IO.File]::Delete($7zFile) } } }
とまぁ、見ていただくとわかるんですがすげー冗長です。シェルとは何だったのか。
ただ、Start-Process
とは違ってSystem.Diagnostics.ProcessStartInfo
をこちらで用意できるので、標準出力 / エラー出力をごにょごにょし放題です。
呼び出し元のプロセスと同じ標準出力 / エラー出力にリダイレクトさせるだけなら、以下のプロパティをtrue
にすれば OK です。
また、この記事でProcess.OutputDataReceived
の設定方法などが詳細に解説されていますので、興味ある人はどうぞ。私はスクリプトのためにそこまでしないタイプの人間なので紹介だけにとどめておきます。
まとめ
PowerShell 使いにくい。