VB.NET WindowsService から画面をロックする 2 リモートアクセスでもロックする

 さて、画面ロックのやり方がわかって喜んでいたのだが、

 

WindowsServer2016でテストしたところ、画面ロックが動作しない。

・・・しばらく試した末に、あきらめかけましたが、

頑張って調べました。

 

アクティブなユーザートークンを取得して、そのユーザーとして、LockWorkStationしているので、リモートデスクトップでユーザートークンが取得できずに、ロックできないという現象のようです。

 

1「WTSGetActiveConsoleSessionId」関数で物理コンソールのセッションIDを取得する。

2 「WTSQueryUserToken」関数で取得したセッションIDのユーザートークンを主とする。

 

とやっているのですが、つまり物理コンソールでないとだめだということです。

リモートデスクトップではセッションIDを取得することができません。

 

代わりに、「WTSEnumerateSessions」

を使用してアクティブなリモートデスクトップのセッションIDを取得し、

そのセッションIDでWTSQueryUserToken 関数を使用して、ユーザートークンを取得するということをやります。

 

それでは、まず、WTSEnumerateSession を使えるように、クラスを作ります。

ここを参照しました。

http://hongliang.seesaa.net/article/7243455.html

Imports System
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Text
 
Public Class TerminalService
    Public Shared ReadOnly CurrentServerHandle As IntPtr = IntPtr.Zero
    Public Shared Function GetSessionInfos(ByVal server As IntPtrAs WTSSessionInfo()
        Dim buffer As IntPtr  '返ってくる配列を受け取るポインタ。
        Dim count As Integer  '数。どちらも初期化不要。
        '早速実行。失敗したら例外。
        If Not (Win32Api.WTSEnumerateSessions(server, 01, buffer, count)) Then
            Throw New Win32Exception(Marshal.GetLastWin32Error())
        End If
        'バッファが空ならセッション無しと言うこと。
        If buffer.Equals(IntPtr.Zero) Then
            Return New WTSSessionInfo() {}
        End If
        Dim infos(count - 1As WTSSessionInfo
        'WTSSessionInfo構造体のサイズを取得。
        Dim size As Integer = Marshal.SizeOf(GetType(WTSSessionInfo))
        Try
            Dim i As Integer
            For i = 0 To count - 1
                'ポインタを、sizeずつずらしていく。
                Dim current As IntPtr = New IntPtr(buffer.ToInt64() + (size * i))
                'ポインタから構造体に変換して配列に格納。
                infos(i) = CType(Marshal.PtrToStructure(current,
                                         GetType(WTSSessionInfo)), WTSSessionInfo)
            Next
            Return infos
        Catch ex As Exception
            Throw New InvalidOperationException(
                            "バッファの読み出し中にエラーが発生しました。", ex)
            '確実に後始末
        Finally
            Win32Api.WTSFreeMemory(buffer)
        End Try
    End Function
    Public Shared Function GetProcessInfos(ByVal server As IntPtr) _
                                                             As WTSProcessInfo()
        'やってることはGetSessionInfosと同じ。使う関数と構造体が違うだけ。
        Dim buffer As IntPtr
        Dim count As Integer
        If Not (Win32Api.WTSEnumerateProcesses(server, 01, buffer, count)) Then
            Throw New Win32Exception(Marshal.GetLastWin32Error())
        End If
        If buffer.Equals(IntPtr.Zero) Then
            Return New WTSProcessInfo() {}
        End If
        Dim infos(count - 1As WTSProcessInfo
        Dim size As Integer = Marshal.SizeOf(GetType(WTSProcessInfo))
        Try
            Dim i As Integer
            For i = 0 To count - 1
                Dim current As IntPtr = New IntPtr(buffer.ToInt64() + (size * i))
                infos(i) = CType(Marshal.PtrToStructure(current,
                                         GetType(WTSProcessInfo)), WTSProcessInfo)
            Next
            Return infos
        Catch ex As Exception
            Throw New InvalidOperationException(
                            "バッファの読み出し中にエラーが発生しました。", ex)
        Finally
            Win32Api.WTSFreeMemory(buffer)
        End Try
    End Function
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
    Public Structure WTSSessionInfo
        Public SessionId As Integer
        Public WinStationName As String
        Public State As ConnectState
    End Structure
    Public Enum ConnectState
        Active = &H0
        Connected = &H1
        ConnectQuery = &H2
        Shadow = &H3
        Disconnected = &H4
        Idle = &H5
        Listen = &H6
        Reset = &H7
        Down = &H8
        Init = &H9
    End Enum
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
    Public Structure WTSProcessInfo
        Public SessionId As Integer
        Public ProcessId As Integer
        Public ProcessName As String
        Public UserSid As IntPtr
    End Structure
 
    Private NotInheritable Class Win32Api
        Declare Auto Function WTSOpenServer Lib "wtsapi32.dll" (
                ByVal name As StringAs IntPtr
        Declare Auto Function WTSEnumerateSessions Lib "wtsapi32.dll" (
                ByVal server As IntPtrByVal reserved As Integer,
                ByVal version As IntegerByRef infos As IntPtr,
                ByRef count As IntegerAs Boolean
        Declare Auto Function WTSEnumerateProcesses Lib "wtsapi32.dll" (
                ByVal server As IntPtrByVal reserved As Integer,
                ByVal version As IntegerByRef infos As IntPtr,
                ByRef count As IntegerAs Boolean
        Declare Auto Sub WTSFreeMemory Lib "wtsapi32.dll" (ByVal memory As IntPtr)
    End Class
End Class

で、これをこう使います。

Dim UserTokenHandle As IntPtr = IntPtr.Zero
TerminalService.GetProcessInfos(UserTokenHandle)
 
 
Dim infos As TerminalService.WTSSessionInfo() = TerminalService.GetSessionInfos(UserTokenHandle)
 
Dim activeID As UInteger
For Each inf As TerminalService.WTSSessionInfo In infos
    If inf.State = TerminalService.ConnectState.Active Then
        activeID = inf.SessionId
    End If
Next
WTSQueryUserToken(activeID, UserTokenHandle)
 
Dim ProcInfo As New PROCESS_INFORMATION
Dim StartInfo As New STARTUPINFOW
StartInfo.cb = CUInt(Runtime.InteropServices.Marshal.SizeOf(StartInfo))
Dim cmdline As New System.Text.StringBuilder
cmdline.Append("rundll32.exe user32.dll,LockWorkStation")
CreateProcessAsUser(UserTokenHandle, Nothing, cmdline, IntPtr.Zero, IntPtr.Zero, False0IntPtr.Zero, Nothing, StartInfo, ProcInfo)

これで 動作しました。

 

ちなみに、コマンドプロンプトから query session を実行すると、現在のセッションの状態を表示することができます。これを見て、セッションが少しわかったような気がしました。