none
VBAでWSHをEXECで実施時の終了確認について RRS feed

  • 質問

  • EXCELのVBAで次のようにWSHのEXECでDOSコマンドを実行しているのですが、Do While~Loopでいつまでループし続け終了しません。何か間違った記述があるのでしょうか。どなたか、ご教示いただけないでしょうか。

    Sub Sample2()

        Dim WSH, wExec, sCmd As String, Result As String, tmp, i As Long
        Dim tSht As Worksheet

        Set WSH = CreateObject("WScript.Shell")
        Set tSht = ThisWorkbook.Sheets(1)

        Set Use_range = tSht.UsedRange
        Rows(1 & ":" & Use_range.Rows.Count).Delete

        sCmd = "dir C:\ /b /s"

        Set wExec = WSH.Exec("%ComSpec% /c " & sCmd)

        Do While wExec.Status = 0
            DoEvents
        Loop                     

        Result = wExec.StdOut.Readall   
        tmp = Split(Result, vbCrLf)
        For i = 0 To UBound(tmp)
            Cells(i + 1, 1) = tmp(i)
        Next i
       
        tSht.Range("A1").Activate
       
        Set wExec = Nothing
        Set WSH = Nothing
    End Sub

    2018年3月27日 13:44

回答

  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2018



    calc、windowアプリの場合はstdoutがありません。一方、consoleアプリの場合、

        Set wExec = WSH.Exec("%ComSpec% /c " & sCmd)

        Do While wExec.Status = 0
            DoEvents
        Loop                     
        Result = wExec.StdOut.Readall   

    では

        Do While wExec.Status = 0
            DoEvents
        Loop    
    は不要です。conditional readでないので、stdout.readallで待ち合わせされます。

    それがあると、パイプ詰まりとデッドロックになって、永久待ちになります。

    今回のケースは、stdout.readallで待ち合わせ可能でしょうが、一般的には、stderrもパイプ詰まりの可能性があります。つまり、execの実装は一般的に問題のある実装です。問題回避のテクニックが必要。例えばstderrをファイルにリダイレクトするとか。
    2018年3月28日 11:45
  • フォーラム オペレーターの栗下 望です。
    bearbook41 さん、こんにちは。

    ご質問いただいた内容から 「Visual Basic for Application(VBA)」 フォーラムに
    スレッドの移動をさせていただきました。
    ※ご自身のスレッドは画面左上の「クイック アクセス」の「マイ スレッド」よりご確認いただけます。

    既に皆様から返信が寄せられておりますので、
    確認いただき参考になった回答には [回答としてマーク] をご設定くださいね。

    どうぞよろしくお願いいたします。


    参考になった投稿には回答としてマークの設定にご協力ください
    MSDN/TechNet Community Support 栗下 望


    2018年3月29日 3:00
    モデレータ

すべての返信

  • 内容的に、
    質問する場所が、Windows PowerShell ではなく Visual Basic for Application(VBA) なのではないかと思います。


    コマンドプロンプトで、以下のコマンドを入力すると、とめどなくデータ表示されることを確認しました。
    (途中で、Ctrl + C キーを押下して、停止させました)

    dir C:\ /b /s

    コマンドプロンプト上で、「dir /?」と入力して、オプションの説明を確認しましたところ、
    以下のような説明内容でした。

    /B          ファイル名のみを表示します (見出しや要約が付きません)。
    /S          指定されたディレクトリおよびそのサブディレクトリのすべての
                ファイルを表示します。

    上記から、VBA では以下のように修正したところ、すぐに結果が表示されました。
    (C ドライブ直下のフォルダのみの命令)

    'sCmd = "dir C:\ /b /s"
    sCmd = "dir C:\ /b"

    C ドライブを起点として全てのフォルダを表示させるのは、あまりにも大量にありすぎるため、現実的ではないかもしれません。

    2018年3月27日 15:07
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2018

        Do While wExec.Status = 0
            DoEvents
        Loop
    を削除。不要。
    2018年3月27日 15:21
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2018

    上記から、VBA では以下のように修正したところ、すぐに結果が表示されました。
    (C ドライブ直下のフォルダのみの命令)

    'sCmd = "dir C:\ /b /s"
    sCmd = "dir C:\ /b"
    stdoutのバッファが4096バイトとかで読み出さないとパイプが詰まる。
    2018年3月27日 15:29
  • ググって調べました。

    標準出力(または標準エラー出力)に、大量のデータを出力する場合は、
    バッファの容量制限があるパイプからのこまめな読み出し(1行単位で扱うように見直す等)といった工夫が必要になる場合があるんですね。
    勉強になります、ありがとうございます。
    • 編集済み sutefu7 2018年3月27日 23:44 文章修正
    2018年3月27日 23:36
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2018

        Do While wExec.Status = 0
            DoEvents
        Loop
    を削除。不要。

    すみません、なぜ不要なのか、教えていただけるでしょうか。というのも、調べてみたら、MSDNの説明に、このようなコードが書かれているからです。

    https://msdn.microsoft.com/ja-jp/library/cc364410.aspx より

    Dim WshShell, oExec
    Set WshShell = CreateObject("WScript.Shell")
    
    Set oExec = WshShell.Exec("calc")
    
    Do While oExec.Status = 0
         WScript.Sleep 100
    Loop
    
    WScript.Echo oExec.Status

    DoEvents が悪いのか、stdout のバッファを読み出しながら Status の変化を待たないといけないのか。
    # いや、stdout からの読み出しが終わった時点でコマンドも終了しているように思うけど。


    Jitta@わんくま同盟

    2018年3月27日 23:44
  • © ウィンドウズスクリプトプログラマ - Windows Script Programmer 2018



    calc、windowアプリの場合はstdoutがありません。一方、consoleアプリの場合、

        Set wExec = WSH.Exec("%ComSpec% /c " & sCmd)

        Do While wExec.Status = 0
            DoEvents
        Loop                     
        Result = wExec.StdOut.Readall   

    では

        Do While wExec.Status = 0
            DoEvents
        Loop    
    は不要です。conditional readでないので、stdout.readallで待ち合わせされます。

    それがあると、パイプ詰まりとデッドロックになって、永久待ちになります。

    今回のケースは、stdout.readallで待ち合わせ可能でしょうが、一般的には、stderrもパイプ詰まりの可能性があります。つまり、execの実装は一般的に問題のある実装です。問題回避のテクニックが必要。例えばstderrをファイルにリダイレクトするとか。
    2018年3月28日 11:45
  • フォーラム オペレーターの栗下 望です。
    bearbook41 さん、こんにちは。

    ご質問いただいた内容から 「Visual Basic for Application(VBA)」 フォーラムに
    スレッドの移動をさせていただきました。
    ※ご自身のスレッドは画面左上の「クイック アクセス」の「マイ スレッド」よりご確認いただけます。

    既に皆様から返信が寄せられておりますので、
    確認いただき参考になった回答には [回答としてマーク] をご設定くださいね。

    どうぞよろしくお願いいたします。


    参考になった投稿には回答としてマークの設定にご協力ください
    MSDN/TechNet Community Support 栗下 望


    2018年3月29日 3:00
    モデレータ
  • 皆様のご回答、有難うごいます。

    EXECでのコマンドプロンプトとの同期かつExcel出力は、今回の事例では時間がかかり、適切でないことがわかりました。

    RUNで一度、テキストファイルに出力し、それを、Excel展開することで対応することとします。

    2018年4月1日 5:32