t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

(解決済)VBAからPowerShellのパラメーター付きコマンドが実行できずにハマった話

先日以下の記事を書いたが、ひとつ問題が発覚した。

thom.hateblo.jp

パラメーター付きのコマンドがうまく実行されないのだ。
実行時エラーで、「ファイルが見つかりません」と出てしまう。つまり、コマンドが失敗してテンポラリーファイルが作成されてないということ。

さんざん悩んでようやく原因が判明したので詳細を解説しようと思う。

どうやら、ひとつめのコマンドにパラメーターをつける場合、パラメーターごとシングルクォートで括る必要があるようだ。

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は、dirc:\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\' を受け入れる位置指定パラメーターが見つかりません。というエラーが発生した。
f:id:t-hom:20170202033409p:plain

なぜにInvoke-Expressionが引数'c:\work\'を取ろうとしているのか。。ということで原因に気付いた次第。

基本的にVBAからWSH経由で実行させるPowerShellコマンドは、コマンドプロンプトで直接実行させることもできるので、VBAでうまくいかないときはコマンドプロンプトでやってみると良いと思う。

以上。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。