先日以下の記事を書いたが、ひとつ問題が発覚した。
パラメーター付きのコマンドがうまく実行されないのだ。
実行時エラーで、「ファイルが見つかりません」と出てしまう。つまり、コマンドが失敗してテンポラリーファイルが作成されてないということ。
さんざん悩んでようやく原因が判明したので詳細を解説しようと思う。
どうやら、ひとつめのコマンドにパラメーターをつける場合、パラメーターごとシングルクォートで括る必要があるようだ。
Sub Sample1() Debug.Print SystemAccessor.GetPSCommandResult( _ "'dir c:\work\'") 'dirはPowerShellではGet-ChildItemのエイリアス End Sub
ひとつめのと書いたのは、パイプで複数つなぐ場合にふたつめ以降はシングルクォートが要らないから。
Sub Sample2() Debug.Print SystemAccessor.GetPSCommandResult( _ "'dir c:\work\' | select name") 'dirはPowerShellではGet-ChildItemのエイリアス End Sub
なんでこんな変な仕様になってるのか。
まず私が作ったモジュールではコマンドは最終的に以下のように展開されてWindows Script Hostに渡る。
powershell -ExecutionPolicy RemoteSigned -Command Invoke-Expression "'dir c:\work\' | select name | Out-File -filePath C:\Users\thom\AppData\Local\Temp\rad47E4A.tmp -encoding Default"
ここで以下の部分に注目
Invoke-Expression "'dir c:\work\' | select name
もしシングルクォートがなかったらこうなる。
Invoke-Expression "dir c:\work\ | select name
このとき、Invoke-Expressionは、dirとc:\work\を両方自分に渡されたパラメーターとして処理してしまい、エラーになるのだ。
しかしパイプを挟んで次のコマンドはシングルクォートで囲むとうまくいかない。統一感がなくてややこしいけど、単一コマンドならシングルクォートで囲む、パイプ処理なら最初のコマンドだけシングルクォートで囲むということをしないといけない。
そのうちVBA側で加工するように改良したいけどちょっとエスケープ関連の処理が面倒だ。ということでこの挙動は既知の問題、暫定回避策は最初のコマンドをシングルクォートで括る。として一旦対応を保留することにした。
さて、ではもう少し実践的なサンプルを。
たとえば「x200」というホストから稼働中のサービスを取得するにはこのように書く。
Sub Sample3() Debug.Print SystemAccessor.GetPSCommandResult( _ "'Get-Service -ComputerName x200'|Where-Object{$_.Status -eq 'Running'}") End Sub
短縮版のPowerShellコマンドでもOK。
Sub Sample4() Debug.Print SystemAccessor.GetPSCommandResult( _ "'gsv -c x200'|?{$_.status -like 'r*'}") End Sub
もう少し複雑な処理サンプル。
Sub Sample5() Debug.Print SystemAccessor.GetPSCommandResult( _ "'Get-Service -ComputerName x200'" & _ "|Where-Object{$_.Status -eq 'Running'}" & _ "|Select-Object -f 10") End Sub
当初はなぜエラーになるかわからずだいぶハマった。
teratailで質問しようかと思い、試したことや挙動を情報整理していたのだが、そこでコマンドプロンプトに直接展開後のコマンドを張り付けることを思い立ち、以下を張り付けたところ、
powershell -ExecutionPolicy RemoteSigned -Command Invoke-Expression "dir c:\work\ | select name"
Invoke-Expression : 引数 'c:\work\' を受け入れる位置指定パラメーターが見つかりません。というエラーが発生した。
なぜにInvoke-Expressionが引数'c:\work\'を取ろうとしているのか。。ということで原因に気付いた次第。
基本的にVBAからWSH経由で実行させるPowerShellコマンドは、コマンドプロンプトで直接実行させることもできるので、VBAでうまくいかないときはコマンドプロンプトでやってみると良いと思う。
以上。