none
スレッド内のWTSSendMessageが正常にタイムアウトしない RRS feed

  • 質問

  • こちらのフォーラムで始めて質問させて頂きます。

    よろしくお願いします。

    現在SYSTEM権限で動くプログラムからログインユーザにダイアログを表示するために、以下のURLを参考にしてWTSSendMessageという関数をVB.NetでP/Invoke実装しています。

    https://msdn.microsoft.com/ja-jp/library/cc429631.aspx

    リファレンスを見るとダイアログがユーザの応答を待機する時間を設定できるようなので、試しに5分待機するように設定してプログラムを実行したところ、タイムアウトするのに6分以上かかっているようでした。(イベントログを確認したところ、ダイアログが閉じるまでに6分20秒かかっていました)

    そこで質問なのですが上記のURLで取り上げているWTSSendMessageという関数ではタイムアウトの処理に誤差が発生しやすいものなのでしょうか。

    または、何か別の原因があるのでしょうか。

    以下にプログラムの実行環境とソースコードを記載します。

    ・実行環境

    OS:Windows 10 EnterPrise 2015 LTSB

    プログラムの起動方式:PC起動時にWindows サービスから実行される

    開発環境:Visual Studio 2017 , .Net Framework 4.6.1

    Imports System.Runtime.InteropServices
    Imports System.Threading
    Imports System.Windows.Forms
    Imports System.Text

    Module Module1

        Dim t As Thread
        Dim test_flg As Boolean = True

        Sub Main()

            Do While True

                'ログオンユーザが存在しない場合、ログオンユーザが取得できるまでループ
                Do Until GetSession() <> String.Empty

                    '5秒スリープ
                    Thread.Sleep(5000)

                Loop

                'ログオンユーザが取得できた場合、ログオンユーザが取得できなくなるまでループ
                Do Until GetSession() = String.Empty

                    If test_flg = True Then

                        '通知ウィンドウを表示
                        t = New Thread(New ThreadStart(Sub() UserNotification())) With {.Priority = ThreadPriority.Highest}
                        t.Start()

                        test_flg = False

                    End If

                    '一定時間スリープ
                    Thread.Sleep(5000)

                Loop

            Loop

        End Sub

        ''' <summary>
        ''' qwinstaコマンドの戻り値から「Active」状態のユーザのセッションIDを取得する
        ''' </summary>
        ''' <returns>現在ログインしているユーザのセッションID</returns>
        Private Function GetSession() As String

            Dim p As New Process
            p.StartInfo.FileName = "C:\Windows\System32\qwinsta.exe"
            p.StartInfo.UseShellExecute = False
            p.StartInfo.RedirectStandardOutput = True
            p.StartInfo.RedirectStandardInput = False
            p.StartInfo.CreateNoWindow = True

            'コマンド実行
            p.Start()
            p.WaitForExit()

            '出力を読み取り、改行文字で分割
            Dim results As String = p.StandardOutput.ReadToEnd()
            Dim aryLines As String() = results.Split(vbCrLf)

            '"Active"を含む行をのセッションIDを返す
            For Each aryLine As String In aryLines

                If aryLine.Contains("Active") Then

                    Dim lineContents() As String = aryLine.Substring(19).Split(New Char() {" "}, StringSplitOptions.RemoveEmptyEntries)
                    Return lineContents(1)

                End If

            Next

            'Activeのユーザが存在しない場合は空文字を返す
            Return String.Empty

        End Function

        ''' <summary>
        ''' ログオンユーザにダイアログを表示する
        ''' </summary>
        Private Sub UserNotification()

            'タイムアウトの時間を5分(300秒)に設定
            Dim Timeout_sec As UInt32 = 300
            EventLog.WriteEntry("test", $"{Timeout_sec}秒後にタイムアウトします。", EventLogEntryType.Information)

            '表示したダイアログのレスポンスコード
            Dim pResponse As UInt32

            WTSSendMessage(0,
                           GetSession(),
                           t,
                           Encoding.GetEncoding("Shift-Jis").GetByteCount(t),
                           m,
                           Encoding.GetEncoding("Shift-Jis").GetByteCount(m),
                           MessageBoxButtons.OK,
                           Timeout_sec,
                           pResponse,
                           True)

            EventLog.WriteEntry("test", $"通知ダイアログが閉じました。{vbCrLf}レスポンスコード={pResponse}", EventLogEntryType.Information)

        End Sub

        ''' <summary>
        ''' SYSTEMからダイアログを表示するためのP/Invoke実装
        ''' </summary>
        ''' <param name="hServer"></param>
        ''' <param name="sessionId"></param>
        ''' <param name="title"></param>
        ''' <param name="titleLength"></param>
        ''' <param name="message"></param>
        ''' <param name="messageLength"></param>
        ''' <param name="style"></param>
        ''' <param name="timeout"></param>
        ''' <param name="pResponse"></param>
        ''' <param name="bwait"></param>
        ''' <returns></returns>
        <DllImport("wtsapi32.dll", SetLastError:=True)>
        Private Function WTSSendMessage(
        ByVal hServer As IntPtr,
        ByVal sessionId As Int32,
        ByVal title As String,
        ByVal titleLength As UInt32,
        ByVal message As String,
        ByVal messageLength As UInt32,
        ByVal style As UInt32,
        ByVal timeout As UInt32,
        ByRef pResponse As UInt32,
        ByVal bwait As Boolean) As Boolean
        End Function

    End Module

    何かご存知方がいましたら、よろしくお願いします。

    2018年11月6日 4:51

すべての返信

  • skawaniさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    上記の件につきまして、
    ご提供したコードでテストしてみました。ご提供されていないいくつかの変数は、自分でテストするように定義しました。

    こちらのテストに基づいて、
    300秒のタイムアウトを設定し、テストするために test3 を使用しています。
    以下のスクリーンショットはこちらのイベントログです。ご確認ください。
    タイムアウトには5分かかりました。問題ありませんでした。




    こちらでテストするために、skawaniさんの値をご提供いただけますでしょうか。
    イベントビューアでイベントログを確認します。 
    何か違うことがあれば教えてください。

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


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

    2018年12月27日 0:41
    モデレータ