question

DarrenRose-2607 avatar image
0 Votes"
DarrenRose-2607 asked DarrenRose-2607 commented

Get username (or profile path) of current logged on user from app running as admin or service even if running in RDP

Hi

I need to be able to get the username (or profile path) of the currently logged on user from my app which may be running either as admin or a service running under system account

I have tried all the various methods mentioned found on google to varying degrees of success, but none of them work in all circumstances e.g. WMI "SELECT UserName FROM Win32_ComputerSystem" doesn't work if running in RDP, or WMI get owner of a process such as explorer.exe is too slow.

I have also tried looking in registry, but a lot of the locations where path / name is shown e.g. HKEY_CURRENT_USER\Volatile Environment is then not available when accessing registry as admin or system, it only shows when accessing it as the user.

Any ideas please?

windows-apidotnet-visual-basic
· 8
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

You need to clarify the scenario when an application running with administrator privileges is being used.

For example
1. Will the application be started in the interactive user's session (i.e., started with Run As Administrator)?
2. Will the application be started as a scheduled task by the Task Scheduler?
3. If started as a scheduled task will it be configured to run whether or not the user is logged on?


0 Votes 0 ·

Hi, thanks for your response, in answer to your points

1) Yes
2) No probably not
3) If it was then it would only be when user logged on, but as 2 not likely

Ultimately app is more likely to be running as a service so will use system account, but from my testing this causes exactly the same issue as running as admin, so once solved for admin I can use same solution for system hopefully.

0 Votes 0 ·

Running as Administrator in the logged on user's interactive session is considerably less complicated than running as a service.

0 Votes 0 ·
Show more comments
RLWA32-6355 avatar image
1 Vote"
RLWA32-6355 answered DarrenRose-2607 commented

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





· 39
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you will give it a test in the next day or two and then report back - much appreciated.

Any benefit in using the second method you posted instead of the first? or will the first not work in a service?

0 Votes 0 ·

Windows services run in non-interactive session 0. The first method demonstrated is dependent on the application being in the interactive session of the logged-on user so it won't work for a service.

0 Votes 0 ·

Okay that make sense, thank you for the code and also for helping me understand the logic behind it as well - very useful

0 Votes 0 ·
Show more comments

Thanks again @RLWA32-6355 - both methods tested and work perfectly for my needs - appreciate you taking the time to reply and to explain the reasoning behind the differences as well :)

0 Votes 0 ·

You're welcome. Glad to help out.

0 Votes 0 ·

Think I spoke to soon. Seem to keep getting "Failed to retrieve information for logged on user" error with a service. If I restart the service it then works okay, so perhaps it is just trying to get information "too soon"? I did try setting service to Automatic (Delayed Start) but same issue.

Any thoughts please?

0 Votes 0 ·
Show more comments
RLWA32-6355 avatar image
1 Vote"
RLWA32-6355 answered DarrenRose-2607 commented

Following VB.Net console application when run as Administrator (manifest for requireAdministrator to be sure) should return the desired information when run in the interactive user's session. Expect an unhandled exception if you don't use the manifest and run it as a standard user.

 Module Module1
     Dim TOKEN_DUPLICATE As UInteger = &H2
     Dim TOKEN_IMPERSONATE As UInteger = &H4
     Dim TOKEN_QUERY As UInteger = &H8
     Dim PROCESS_QUERY_INFORMATION As UInteger = &H400
    
     <DllImport("user32.dll", CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
     Public Function GetShellWindow() As IntPtr
     End Function
    
     <DllImport("user32.dll", CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
     Public Function GetWindowThreadProcessId(hwnd As IntPtr, ByRef processid As UInteger) As UInteger
     End Function
    
     <DllImport("kernel32.dll", CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
     Public Function OpenProcess(processaccess As UInteger, inherit As Boolean, processid As UInteger) As SafeProcessHandle
     End Function
    
     <DllImport("advapi32.dll", CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
     Public Function OpenProcessToken(handle As SafeProcessHandle, desiredaccess As UInteger, ByRef token As SafeAccessTokenHandle) As Boolean
     End Function
    
     Sub Main()
         Console.WriteLine("Running as user {0}", Environment.UserName)
         Dim processid As UInteger
         Dim hwnd As IntPtr = GetShellWindow()
         Dim tid As UInteger = GetWindowThreadProcessId(GetShellWindow(), processid)
         Dim explorerhandle As SafeProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION, False, processid)
         If (Not explorerhandle.IsInvalid) Then
             Dim token As SafeAccessTokenHandle = New SafeAccessTokenHandle(IntPtr.Zero)
             If (OpenProcessToken(explorerhandle, TOKEN_IMPERSONATE Or TOKEN_QUERY Or TOKEN_DUPLICATE, token)) Then
                 WindowsIdentity.RunImpersonated(token, Sub() GetUserInfo())
                 token.Dispose()
                 Console.ReadKey()
             Else
                 Console.WriteLine(String.Format("OpenProcessToken failed error code is {0}", Marshal.GetLastWin32Error()))
             End If
             explorerhandle.Dispose()
         Else
             Console.WriteLine(String.Format("OpenProcess failed error code is {0}", Marshal.GetLastWin32Error()))
         End If
    
     End Sub
     Sub GetUserInfo()
         Console.WriteLine("Logged on user: {0}, pofile path: {1}", Environment.UserName, Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
     End Sub
    
    
 End Module
· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Excellent, thank you very much, will test fully tomorrow when I have more time but a quick test I did just now seems to work perfectly even in RDP :)

Thank you

1 Vote 1 ·

You can find your user name by going to the settings
button

0 Votes 0 ·

And how is that at all relevant to my question of obtaining the information in vb.net in an app which is running as admin or a service running as system???.....

0 Votes 0 ·