Process.WaitForExitが効かない

タグの編集
投稿者 brv  (中学生) 投稿日時 2009/12/7 01:30:15
コントロールパネルの項目(インターネットオプション)を表示し、終了するまでフォームを待機させておくコードを書きました。

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
        Using p As Process = Process.Start("inetcpl.cpl")
            p.WaitForExit()
            MsgBox("終了しました")
        End Using
    End Sub


しかし、インターネットオプションが開いた直後に「終了しました」とメッセージボックスが出ます。
Process.Startの引数をEXEファイルやテキストファイルにした場合は終了した後にメッセージボックスが出ました。メッセージボックスが終了前に出てしまう理由、そしてコントロールパネル終了までアプリケーションを待機させる方法を教えてください。

コードの作成には、以下のページを参考にしました。
http://dobon.net/vb/dotnet/process/opencontrolpanel.html
投稿者 あにす  (社会人) 投稿日時 2009/12/7 01:45:28
Windows XPでは再現しませんでした。実行しているOSはなんでしょうか?
投稿者 brv  (中学生) 投稿日時 2009/12/7 01:56:31
>実行しているOSはなんでしょうか?
すみません。書くのを忘れていました。
Windows Vista Home Premiumです。
>Windows XPでは再現しませんでした。
そうですか…
うちにはXPもあるのでそっちの環境でも後で試してみます。
ありがとうございます。
投稿者 葉月  (社会人) 投稿日時 2009/12/7 06:31:31
7でも想定された動作はしませんね。
VistaからUAC(ユーザーアカウント制御)の導入など、セキュリティに力を入れているの
でその関係かも知れません。
今回のはUAC絡みではないです。
一時的に切って試してみましたが、想定された動作はしませんでした。

ただ、以下のサンプルは正常動作するので、原因はコンパネに絞っていいと思います。
他の方の回答をまってみてください。

■サンプル
        Dim info As New ProcessStartInfo()
        info.FileName = "notepad.exe"
        info.RedirectStandardOutput = True
        info.UseShellExecute = False

        Using p As Process = Process.Start(info)
            p.WaitForExit()
            MsgBox("終了しました")
        End Using
投稿者 brv  (中学生) 投稿日時 2009/12/8 01:26:59
いろいろと試行錯誤をしてみました。

プロセスの起動後、5秒ほど待ってからプロセスのメインウインドウのタイトルを取得してみました。

Using cplProcess As Process = Process.Start("inetcpl.cpl")
            Threading.Thread.Sleep(5000)
            MsgBox(cplProcess.MainWindowTitle)
End Using


しかし、プロセスがすでに終了していて取得できませんでした。

タスクマネージャで確認したところ、コントロールパネルを閉じるとrundll32.exeが閉じるのがわかりました。
コントロールパネルを開いた5秒後にrundll32.exeを取得しようとしましたが…

        Using cplProcess As Process = Process.Start("inetcpl.cpl")

        End Using
        Threading.Thread.Sleep(5000)
        For Each p As Process In Process.GetProcessesByName("rundll32.exe")
            If p.HasExited Then
                MsgBox("終了しました")
            Else
                MsgBox("終了していません")
            End If
        Next

メッセージボックスが一つも出ずに終了しました。こちらもUACの関係でrundll32.exeのプロセスが取得できないようです。

結局何もわかりませんでした…
投稿者 brv  (中学生) 投稿日時 2009/12/9 00:54:36
プロセスから攻めても仕方がないということで、ウインドウハンドルを取得してやってみました。
かなりの力技ですが…

Public Class Form1
    Declare Function GetForegroundWindow Lib "user32.dll" () As IntPtr
    Declare Function SetWiondowText Lib "user32.dll" Alias "SetWindowTextA" ( _
                              ByVal hWnd As IntPtr, ByVal lpString As StringAs Integer
    Declare Function IsWindow Lib "user32.dll" (ByVal hWnd As IntPtr) As Integer
    Declare Function SetActiveWindow Lib "user32.dll" (ByVal hWnd As IntegerAs Integer

    Private CplWindowHandle As IntPtr

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
        Process.Start("inetcpl.cpl")

        Do
            '最前のウインドウを取得 
            CplWindowHandle = GetForegroundWindow
            If CplWindowHandle <> Me.Handle Then
                '自分のウインドウでない(コントロールパネル)なら脱出 
                Exit Do
            End If
            'スリープをかませる 
            Threading.Thread.Sleep(400)
            Application.DoEvents()
        Loop

        Me.Enabled = False
        WaitForWindowClose(CplWindowHandle)
        Me.Enabled = True
    End Sub

    Private Sub WaitForWindowClose(ByVal hWnd As IntPtr)
        Do
            If IsWindow(hWnd) = 0 Then
                'ウインドウが存在しないなら脱出 
                Exit Do
            End If
            'スリープをかませる 
            Threading.Thread.Sleep(400)
            Application.DoEvents()
        Loop
    End Sub
End Class

WinAPIに頼りすぎですね…
それと、ボタンを押してコントロールパネルが開かないうちにほかのウインドウをアクティブにされると困ります。そっちのウインドウをコントロールパネルと勘違いして、そのウインドウが閉じるまで、フォームがEnabled = Falseの状態になってしまいます。

一応できましたが、もっといい方法があれば教えてください。
投稿者 オショウ  (社会人) 投稿日時 2009/12/9 07:47:04
VISTA Ultimate ですが、そのコードですと、確かにインターネットのプロパティが
開いた瞬間、『終了しました』と表示されますネ!

以下のように修正して下さい。

        Dim pi As ProcessStartInfo

        pi = New ProcessStartInfo
        With pi
            .FileName = "rundll32.exe"
            .Arguments = "shell32.dll,Control_RunDLL inetcpl.cpl"
        End With

        Using p As Process = Process.Start(pi)
            p.WaitForExit()
            MsgBox("終了しました")
        End Using


以上。
投稿者 オショウ  (社会人) 投稿日時 2009/12/9 07:49:32
追伸・・・

http://meteoricstream.com/tips/hsp/rundll.html

参考まで

以上。
投稿者 brv  (中学生) 投稿日時 2009/12/10 07:16:03
オショウさん、ありがとうございます。提示してくださったサンプルで動作しました。
これからも何かあった時はよろしくお願いします。