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 を実行すると、現在のセッションの状態を表示することができます。これを見て、セッションが少しわかったような気がしました。

VB.NET WindowsService から画面をロックする

ここしばらく、WindowsServiceからWindowsをシャットダウンするソフトを作っていましたが。

シャットダウンではなくて画面ロックでできないか、ということでやってみました。

 

 

<DllImport("Advapi32.dll", EntryPoint:="CreateProcessAsUser",
   ExactSpelling:=False, SetLastError:=True, CharSet:=CharSet.Unicode)>
Public Shared Function CreateProcessAsUser(ByVal hToken As IntPtr,
                     ByVal lpApplicationName As String,
                     <[In](), Out(), [Optional]()> ByVal lpCommandLine As System.Text.StringBuilder,
                     ByVal lpProcessAttributes As IntPtr,
                     ByVal lpThreadAttributes As IntPtr,
                     <MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandles As Boolean,
                     ByVal dwCreationFlags As Integer,
                     ByVal lpEnvironment As IntPtr,
                     ByVal lpCurrentDirectory As String,
                     <[In]()> ByRef lpStartupInfo As STARTUPINFOW,
<Out()> ByRef lpProcessInformation As PROCESS_INFORMATION) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("Wtsapi32.dll", EntryPoint:="WTSQueryUserToken", SetLastError:=True)>
Public Shared Function WTSQueryUserToken(ByVal SessionId As UInteger,
ByRef phToken As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("kernel32.dll", EntryPoint:="WTSGetActiveConsoleSessionId", SetLastError:=True)>
Public Shared Function WTSGetActiveConsoleSessionId() As UInteger
End Function

 

<StructLayout(LayoutKind.Sequential)>
Public Structure STARTUPINFOW
  Public cb As UInteger
  <MarshalAs(UnmanagedType.LPWStr)>
  Public lpReserved As String
  <MarshalAs(UnmanagedType.LPWStr)>
  Public lpDesktop As String
  <MarshalAs(UnmanagedType.LPWStr)>
  Public lpTitle As String
  Public dwX As UInteger
  Public dwY As UInteger
  Public dwXSize As UInteger
  Public dwYSize As UInteger
  Public dwXCountChars As UInteger
  Public dwYCountChars As UInteger
  Public dwFillAttribute As UInteger
  Public dwFlags As UInteger
  Public wShowWindow As UShort
  Public cbReserved2 As UShort
  Public lpReserved2 As IntPtr
  Public hStdInput As IntPtr
  Public hStdOutput As IntPtr
  Public hStdError As IntPtr
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure PROCESS_INFORMATION
  Public hProcess As IntPtr
  Public hThread As IntPtr
  Public dwProcessId As UInteger
  Public dwThreadId As UInteger
End Structure

 

ここまでが関数のインポート等です。

 

これを

 

Dim UserTokenHandle As IntPtr = IntPtr.Zero
WTSQueryUserToken(WTSGetActiveConsoleSessionId, 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, False, 0, IntPtr.Zero, Nothing, StartInfo, ProcInfo)

 

のようにWindowsService上で呼び出すことにより、画面をロックすることができました。

 

実際やってみると、シャットダウンよりも安定して動作するようなので、今回はこちらを採用しようかと思っています。シャットダウンは、ふいに動作しないことがあったので、少し不安が残るのです。

 

Mistel BAROCCO の設定について1 右クリック

右クリックをキーに割り当てたい。

 

右クリックのショートカットキーは「SHIFTG+ F10」

なので、

「FN + CTRL」でマクロ記録スタート。

今回は「FN + SPACE」に割り当てるので、「FN + SPACE」を押下。

「SHIFT + FN + 0」を押下

「FN + CTRL」で記録完了

 

以上です。

VB.net パラメータクエリ DBNull.Value を入れているのに、0になってしまう。

 

 

params.Add(New OleDb.OleDbParameter("@data1",
params.Add(New OleDb.OleDbParameter("@data2"
params.Add(New OleDb.OleDbParameter("@data3",

params.Add(New OleDb.OleDbParameter("@data1", ← これが重複していた
params.Add(New OleDb.OleDbParameter("@data2", ← これが重複していた
params.Add(New OleDb.OleDbParameter("@data3" ← これが重複していた

 

params.Add(New OleDb.OleDbParameter("@data4",
params.Add(New OleDb.OleDbParameter("@data5", DBNull.Value) ← これが0になる
params.Add(New OleDb.OleDbParameter("@data6"

 

こんなかんじで最初のパラメータ3つが重複して、順番がおかしくなっていたようです。しかし、パラメータが多くても通るんですね。気を付けます。

実行されないサービスの罠

案件: 指紋ダブル認証システム

    

ログイン時に指紋認証画面を表示し、二人の認証がない場合には、Windows を強制的にシャットダウンする。

 

ログイン画面は.netで、シャットダウン処理はWindowsServiceを.net で作成し実現する。

ログイン画面はタスクスケジューラに、Priority0で登録し、ログイン時になるべく早く起動する。また、起動するまでに時間がかかる場合があるので、Win32アプリで「お待ちください」の表示を行う。Win32なので.netより早く表示される。これもタスクスケジューラにPriority0で登録しておく。(一度エクスポートし、XMLを編集しインポートする)※もっと早く画面表示する方法をご存知の方、ご教示ください。

 

さて、サービスは起動と同時に開始され、認証フォームのプロセスが出現するのを監視している。認証フォームにおいて認証がなされると、サービスのその旨が通知される。

認証フォームのプロセスの消失とともに、

認証されていれば、サービスは監視動作を停止する。

認証されていなければ、強制的にシャットダウンする。

のどちらかの処理を行う。

ちなみに、サービスはもう一つのサービスと相互監視を行い、どちらかが止まっている場合に、相手を再起動することにより、サービスの停止を防止する。

 

今回なぜか、強制シャットダウンが行われないことがあった。

たまに再現するが条件が分からない。

たまたまタスクマネージャを開いていると、

認証フォームが二重に起動していた。

画面に表示されているのは一つだけで、もう一つはプロセスにあるだけだったので気づかなかった。

この隠れたプロセスにより、プロセスの消失を検出できず、シャットダウン処理が行われなかったようだ。

Windowsアプリケーションフレームワークプロパティ」の「単一インスタンスのアプリケーションを作成する」にチェックを入れた。

また、タスクスケジューラの「タスクが既に実行中の場合に適用される規則」について、「新しいインスタンスを並列で実行」にしていたのを、「新しいインスタンスを開始しない」に変更した。

この二つの処理により、二重起動しないようになるといいな。

 

※追記 二重起動でもなくて、シャットダウンに失敗することがあるようだ。

原因は不明だが、シャットダウン処理をタイマーで3秒おきに起動するとうまく動作しているような気がする。ちょっと処理を待つのがコツなのか?

今後のテストで安定動作するかどうか。また報告する。

 

シャットダウンが行われなかった時のために、サービスの後に、コンソールアプリで認証が終了しているかチェックし、認証されていなければシャットダウンするダブルシャットダウンとして実装しましたが。はてさてどうなることか。

 

コンソールアプリでの監視の方が確実のようだ。

しかし、シャットダウンまでの時間はサービスの方が上。

ということで、コンソールアプリは100秒待機してから動作するようにしました。

 

この話はここまでにしとうございます。

VisualStudio2017 Microsoft.Office.Interop.Access が参照できない 

COM参照「4affc9a0-5f99-101b-af4e-00aa003f0f07」バージョン9.0を解決できませんでした。 オブジェクト参照がオブジェクト インスタンスに設定されていません。

 

始まりはOFFICE365の上にそのままOFFICE2016をインストールしたこと。

気づいたら、Interop.Accessが参照ができなくなっていました。

アンインストール、再インストール、Fixit 挙句はEXCEL2007を再インストールまでしてみましたがダメでした。

レジストリの該当箇所を削除もしてみましたが、ダメ。

 

 

結局、直接参照したら、エラーは解消した。

とりあえずですが。

参照の追加で、「C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Access

を参照するとエラーが消えました。

が、普通には参照できないので、今度クリーンインストールする予定です。