The following code can be used in a service running as LocalSystem to retrieve the user name and profile path for the logged on interactive user. The service used for this code was written using .Net Framework 4.8. Its not guaranteed to be bug free so you should test thoroughly.
The code is written so that it accepts a custom command that can be issued to the service by an interactive user. The command code is integer value 142 and can be issue to the service by using the sc.exe command. For example, at a command prompt enter "sc control <servicename> 142". The service will report the information by writing an entry in the Application Event Log.
Code in VB.Net service -
Protected Overrides Sub OnCustomCommand(command As Integer)
If command = 142 Then
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf LoggedOnUserInfo))
End If
End Sub
Private Structure USER_INFO
Public username As String
Public profilepath As String
End Structure
Private Sub LoggedOnUserInfo()
Dim interactiveuser As USER_INFO = New USER_INFO()
Try
If GetLoggedOnUser(interactiveuser) Then
EventLog1.WriteEntry(String.Format("Username: {0}, Profile path: {1}", interactiveuser.username, interactiveuser.profilepath))
Else
EventLog1.WriteEntry("Failed to retrieve information for logged on user", EventLogEntryType.Error)
End If
Catch ex As Exception
EventLog1.WriteEntry(ex.Message)
End Try
End Sub
Private Function GetLoggedOnUser(ByRef pUinfo As USER_INFO) As Boolean
Dim sessions() As Win32.WTS_SESSION_INFO = New Win32.WTS_SESSION_INFO() {}
Dim Retval As Boolean = False
If GetSessions(sessions) Then
Dim ui As New USER_INFO()
If GetActiveSessionInfo(sessions, ui) Then
pUinfo = ui
Retval = True
End If
End If
Return Retval
End Function
Private Function GetSessions(ByRef pSessions As Win32.WTS_SESSION_INFO()) As Boolean
Dim iSessions As IntPtr = IntPtr.Zero
Dim count As UInteger
Dim Retval As Boolean = False
Try
If (Win32.WTSEnumerateSessions(IntPtr.Zero, 0, 1, iSessions, count)) Then
ReDim pSessions(count - 1)
Dim cbytes = Marshal.SizeOf(Of Win32.WTS_SESSION_INFO)()
Dim p As IntPtr = iSessions
Dim offset As Integer = 0
For i = 0 To count - 1 Step 1
pSessions(i) = Marshal.PtrToStructure(Of Win32.WTS_SESSION_INFO)(p + offset)
offset += cbytes
Next
Retval = True
Else
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
Finally
If (iSessions <> IntPtr.Zero) Then
Win32.WTSFreeMemory(iSessions)
End If
End Try
Return Retval
End Function
Private Function GetActiveSessionInfo(sessions As Win32.WTS_SESSION_INFO(), ByRef pUinfo As USER_INFO) As Boolean
Dim Retval As Boolean = False
For Each sess As Win32.WTS_SESSION_INFO In sessions
If (sess.State = Win32.WTS_CONNECTSTATE_CLASS.WTSActive) Then
Dim iBuffer As IntPtr = IntPtr.Zero
Dim bytesreturned As UInteger
Try
If Win32.WTSQuerySessionInformation(IntPtr.Zero, sess.Sessionid, Win32.WTS_INFO_CLASS.WTSSessionInfo, iBuffer, bytesreturned) Then
Dim activesession As Win32.WTSINFO = Marshal.PtrToStructure(Of Win32.WTSINFO)(iBuffer)
pUinfo.username = activesession.UserName
pUinfo.profilepath = GetLoggedOnProfilePath(activesession.SessionId)
Retval = True
Else
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
Exit For
Finally
If (iBuffer <> IntPtr.Zero) Then
Win32.WTSFreeMemory(iBuffer)
End If
End Try
End If
Next
Return Retval
End Function
Private Function GetLoggedOnProfilePath(SessionId As UInteger) As String
Dim strprofile As String = String.Empty
Dim token As IntPtr = IntPtr.Zero
Try
If Win32.WTSQueryUserToken(SessionId, token) Then
Dim cch As Integer = 260
Dim strpath As StringBuilder = New StringBuilder(cch)
If Not Win32.GetUserProfileDirectory(token, strpath, cch) Then
strpath.Capacity = cch
If Not Win32.GetUserProfileDirectory(token, strpath, cch) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
End If
strprofile = strpath.ToString()
Else
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
Finally
If token <> IntPtr.Zero Then
Win32.CloseHandle(token)
End If
End Try
Return strprofile
End Function
Stuff for P/Invoke -
Public Class Win32
Public Enum WTS_CONNECTSTATE_CLASS As UInteger
WTSActive ' User logged On To WinStation
WTSConnected ' WinStation connected To client
WTSConnectQuery ' In the process Of connecting To client
WTSShadow ' Shadowing another WinStation
WTSDisconnected ' WinStation logged On without client
WTSIdle ' Waiting For client To connect
WTSListen ' WinStation Is listening For connection
WTSReset ' WinStation Is being reset
WTSDown ' WinStation Is down due To Error
WTSInit ' WinStation In initialization
End Enum
Public Enum WTS_INFO_CLASS As UInteger
WTSInitialProgram
WTSApplicationName
WTSWorkingDirectory
WTSOEMId
WTSSessionId
WTSUserName
WTSWinStationName
WTSDomainName
WTSConnectState
WTSClientBuildNumber
WTSClientName
WTSClientDirectory
WTSClientProductId
WTSClientHardwareId
WTSClientAddress
WTSClientDisplay
WTSClientProtocolType
WTSIdleTime
WTSLogonTime
WTSIncomingBytes
WTSOutgoingBytes
WTSIncomingFrames
WTSOutgoingFrames
WTSClientInfo
WTSSessionInfo
WTSSessionInfoEx
WTSConfigInfo
WTSValidationInfo 'Info Class value used To fetch Validation Information through the WTSQuerySessionInformation
WTSSessionAddressV4
WTSIsRemoteSession
End Enum
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Public Structure WTS_SESSION_INFO
Public Sessionid As UInteger
<MarshalAs(UnmanagedType.LPWStr)>
Public pWinStationName As String
Public State As WTS_CONNECTSTATE_CLASS
End Structure
<StructLayout(LayoutKind.Explicit)>
Public Structure LARGE_INTEGER
<FieldOffset(0)>
Public LowPart As UInteger
<FieldOffset(4)>
Public HighPart As Integer
<FieldOffset(0)>
Public QuadPart As Long
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Public Structure WTSINFO
Public State As WTS_CONNECTSTATE_CLASS ' connection state (see Enum)
Public SessionId As UInteger ' session id
Public IncomingBytes As UInteger
Public OutgoingBytes As UInteger
Public IncomingFrames As UInteger
Public OutgoingFrames As UInteger
Public IncomingCompressedBytes As UInteger
Public OutgoingCompressedBytes As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
Public WinStationName As String '[WINSTATIONNAME_LENGTH]
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=17)>
Public Domain As String '[DOMAIN_LENGTH]
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=21)>
Public UserName As String '[USERNAME_LENGTH+1]
Public ConnectTime As LARGE_INTEGER
Public DisconnectTime As LARGE_INTEGER
Public LastInputTime As LARGE_INTEGER
Public LogonTime As LARGE_INTEGER
Public CurrentTime As LARGE_INTEGER
End Structure
<DllImport("wtsapi32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function WTSEnumerateSessions(hServer As IntPtr, reserved As UInteger,
version As UInteger, ByRef ppSessionInfo As IntPtr, ByRef count As UInteger) As Boolean
End Function
<DllImport("wtsapi32.dll", CallingConvention:=CallingConvention.StdCall)>
Public Shared Sub WTSFreeMemory(pmemory As IntPtr)
End Sub
<DllImport("wtsapi32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function WTSQueryUserToken(Sessionid As UInteger, ByRef htoken As IntPtr) As Boolean
End Function
<DllImport("kernel32.dll", CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
Public Shared Function CloseHandle(handle As IntPtr) As Boolean
End Function
<DllImport("wtsapi32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function WTSQuerySessionInformation(hServer As IntPtr, SessionId As UInteger, WTSInfoClass As WTS_INFO_CLASS,
ByRef ppBuffer As IntPtr, ByRef BytesReturned As UInteger) As Boolean
End Function
<DllImport("userenv.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function GetUserProfileDirectory(htoken As IntPtr, lpProfileDir As StringBuilder, ByRef cch As UInteger) As Boolean
End Function
End Class