It can be very useful to be able to automatically recognize the current database user. This could be to:
- automate the login process
- log which user add/edited/deleted records
- etc.
Today, I thought I’d quickly cover a couple way this can be accomplished:
Environ
One of the easiest way to quickly return the currently logged in user’s Windows username is to simply check the computer’s username environment variable by using the Environ function.
Environ("username")
The issue with technique is that there are relatively easy ways to spoof the environment variable thus enabling a user to impersonate any user they choose. So I don’t recommend this approach.
WScript
The second approach is to use WScript to get the Windows username. Now, with Wscript there are a couple approaches that can be used.
WScript Shell Object
The first, just like the Environ function approach, reads the environment variable by utilizing the Wscript.Shell ExpandEnvironmentString Method and thus is also not a reliable solution.
CreateObject("WScript.Shell").ExpandEnvironmentStrings("%USERNAME%")
WScript Network Object
The better approach is to use the WScript.Network object. Unlike environment variables, this can not easily be spoofed. The code to do so would look like:
CreateObject("WScript.Network").UserName
WMI
Another approach, more convoluted however, is to use WMI to list the active sessions and then cross-reference the session with the logged in username. It can be done by doing something along the lines of:
'---------------------------------------------------------------------------------------
' Procedure : WMI_GetUsernames
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : Return a dictionary (unique values) of usernames for the specified computer
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Uses Late Binding, so none required
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sHost : host computer to query, omit for the local PC
'
' Usage:
' ~~~~~~
' See the TestMe() proc below
'
' Revision History:
' Rev Date(yyyy/mm/dd) Description
' **************************************************************************************
' 1 2018-04-13 Initial Release
' 2 2018-04-26 Code cleanup for Option Explicit, sorry!
'---------------------------------------------------------------------------------------
Public Function WMI_GetUsernames(Optional sHost As String = ".") As Object 'Scripting Dictionary
'Ref: https://msdn.microsoft.com/en-us/library/aa394189(v=vs.85).aspx
' https://msdn.microsoft.com/en-us/library/aa394172(v=vs.85).aspx
' https://msdn.microsoft.com/en-us/library/aa384793(v=vs.85).aspx
On Error GoTo Error_Handler
Dim oWMI As Object 'WMI object to query about the PC's OS
Dim sWMIQuery As String 'WMI Query
Dim oLogonSessions As Object
Dim oLogonSession As Object
Dim oUsers As Object
Dim oUser As Object
Dim dictUsers As Object 'Scripting Dictionary
Set WMI_GetUsernames = Nothing 'Always empty it!
Set dictUsers = CreateObject("Scripting.Dictionary") 'New dictionary
Set oWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & sHost & "\root\cimv2")
sWMIQuery = "SELECT * " & _
"FROM Win32_LogonSession " & _
"WHERE LogonType=2"
Set oLogonSessions = oWMI.ExecQuery(sWMIQuery)
For Each oLogonSession In oLogonSessions
'Now that we have active sessions, let find out who is running them
sWMIQuery = "Associators of " & _
"{Win32_LogonSession.LogonId=" & oLogonSession.LogonId & "} " & _
"WHERE AssocClass=Win32_LoggedOnUser Role=Dependent"
Set oUsers = oWMI.ExecQuery(sWMIQuery)
For Each oUser In oUsers
With oUser
' Debug.Print .Name
If dictUsers.Exists(.Name) = False Then dictUsers.Add .Name, .Name
End With
Next
Next
Set WMI_GetUsernames = dictUsers
Error_Handler_Exit:
On Error Resume Next
Set dictUsers = Nothing
Set oUsers = Nothing
Set oUser = Nothing
Set oLogonSession = Nothing
Set oLogonSessions = Nothing
Set oWMI = Nothing
Exit Function
Error_Handler:
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: WMI_GetUsernames" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
Resume Error_Handler_Exit
End Function
and it can be used by doing something like:
Sub TestMe()
Dim User As Variant
For Each User In WMI_GetUsernames.keys
Debug.Print User
Next
End Sub
API
Another, and better solution IMHO, is to use an API. Below is a very well known API that was written by Dev Ashish that I tweaked to make it compatible with x32 and x64 usage.
#If VBA7 And Win64 Then
'x64 Declarations
Private Declare PtrSafe Function apiGetUserName Lib "advapi32.dll" Alias _
"GetUserNameA" (ByVal lpBuffer As String, _
nSize As Long) As Long
#Else
'x32 Declaration
Private Declare Function apiGetUserName Lib "advapi32.dll" Alias _
"GetUserNameA" (ByVal lpBuffer As String, _
nSize As Long) As Long
#End If
'******************** Code Start **************************
' This code was originally written by Dev Ashish.
' It is not to be altered or distributed,
' except as part of an application.
' You are free to use it in any application,
' provided the copyright notice is left unchanged.
'
' Code Courtesy of
' Dev Ashish
' Adapted for x64 usage from: http://www.theaccessweb.com/api/api0008.htm
'
Function fOSUserName() As String
' Returns the network login name
Dim lngLen As Long, lngX As Long
Dim strUserName As String
strUserName = String$(254, 0)
lngLen = 255
lngX = apiGetUserName(strUserName, lngLen)
If (lngX > 0) Then
fOSUserName = Left$(strUserName, lngLen - 1)
Else
fOSUserName = vbNullString
End If
End Function
'******************** Code End **************************
PowerShell
If you want, you can even use PowerShell to get the username of the current user. If you implement my PowerShell functions from:
Then you can simply retrieve the username using a single line like:
Debug.Print PS_GetOutput("[System.Environment]::UserName") 'Send the username to the immediate window
If ever you want to get the domain/username it is also very easy to achieve by simply doing:
Debug.Print PS_GetOutput("[System.Security.Principal.WindowsIdentity]::GetCurrent().Name") 'Send the username to the immediate window
I’ve also seen people recommend using:
Debug.Print PS_GetOutput("$env:UserName") 'Send the username to the immediate window
but once again, be careful here as this is reliant on the environment variable which can be spoofed!
Conclusion
As you can see, there are a multitude of ways to retrieve the current user’s username, but not all are reliable/secure.
My only recommendation is to avoid anything that is based on the environment variable as that can be spoofed very easily allowing people to impersonate anyone they want. Beyond that, the choice is yours!
Also, some will state that APIs are the efficient way to do things. I say Bah Humbug to them. When I tested, the difference was measured in microseconds, so negligible and when I look at the simplicity of the CreateObject(“WScript.Network”), 1 line, no API declarations to worry about or adapt for bitness… Add to that the simple fact that you don’t call this type of function over and over, it’s a one time deal.
Whatever the approach you select, a great way to implement this is to simply call it once at the startup of your application, set a global variable or TempVars and use the variable/tempvar thereafter throughout your code.
Page History
| Date | Summary of Changes |
|---|---|
| 2018-04-12 | Initial Release |
| 2022-09-07 | Added the PowerShell section Added the Conclusion section |
Error in text (WMI)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set WMI_GetUsernames= Nothing ‘ skipped s in WMI_GetUsernames
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
skipped
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dim oUsers As Object
Dim oUser As Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set oUser = Nothing
Thank you. The code has been tweaked and is Option Explicit compliant now.
Public Function LoginName() As String
‘ Full Name in System
Dim oUsers As Object ‘IADsUser
Dim strUserName As String
Dim strUserDomain As String
Dim FName() As String
strUserName = Environ(“username”)
strUserDomain = Environ(“USERDOMAIN”)
Set oUsers = GetObject(“WinNT://” & strUserDomain & “/” & strUserName & “,user”)
LoginName = Trim(oUsers.FullName)
Set oUsers = Nothing
‘Debug.Print LoginName
End Function
I wouldn’t use such code as it relies on Envrion() variables which are unsecure. You need to switch to WMI or APIs when getting the Username or Domain.
Function GetUserFullName() As String
Dim WSHnet As Object
Dim objUser As Object
Dim UserName As String
Dim UserDomain As String
Dim UserFullName As String
Set WSHnet = CreateObject(“WScript.Network”)
UserName = WSHnet.UserName
UserDomain = WSHnet.UserDomain
Set objUser = GetObject(“WinNT://” & UserDomain & “/” & UserName & “,user”)
GetUserFullName = objUser.FullName
Set objUser = Nothing
Set WSHnet = Nothing
Debug.Print “User Full Name: ” & vbCrLf & GetUserFullName
End Function
User for Net
Declare Function intWNetGetUser Lib “mpr.dll” Alias “WNetGetUserA” (ByVal _
lpname As String, ByVal lpUserName As String, lpnLength As Long) As Long
Public Function WNetGetUser(lpLocalName$) As String
Dim res&
Dim tbuf As String
Dim BufferSize&
Dim lenName As Integer
tbuf = String$(256, 0)
BufferSize = Len(tbuf)
res = intWNetGetUser(lpLocalName, tbuf, BufferSize)
‘Null
lenName = InStr(tbuf, Chr(0)) – 1
tbuf = Left(tbuf, lenName)
WNetGetUser = tbuf
End Function
This is a good run down of various ways to get the users Windows login name, but often that name is cryptic (numeric or abbreviated). A user will recognize their own name but not other peoples names. Like Dmitriy Sonnikh, I wanted a way to get the friendly display name that is stored in Active Directory. I like to store this name in each records Modified_By field when a record is changed. This makes it easy for any user to identify the person who made the last change.
Below is the function that I use:
‘**************************************************************
Public Function fGetDisplayName(Optional UserName As Variant) As String
‘ This will return the Active Directory Display Name for the passed NT login name or the current user.
‘ I modified the code to allow a passed parameter and added an error handler. BS 9/16/2013
On Error GoTo ErrorHandler
Dim WSHnet As Object
Dim UserDomain
Dim objUser As Object
Dim UserFullName
DoCmd.Echo True, “Retrieving user name…”
DoEvents ‘ in hopes of letting the screen redraw.
Set WSHnet = CreateObject(“WScript.Network”)
If IsMissing(UserName) Then
‘ No name passed. Use the current user name.
UserName = WSHnet.UserName
ElseIf IsNull(UserName) Then
fGetDisplayName = “”
Exit Function
End If
UserDomain = WSHnet.UserDomain
Set objUser = GetObject(“WinNT://” & UserDomain & “/” & UserName & “,user”)
UserFullName = objUser.FullName
‘ Debug.Print UserName, UserDomain, UserFullName
fGetDisplayName = UserFullName
Exit Function
ErrorHandler:
If err.Number = -2147022675 Then ‘ The user name could not be found.
‘ Return the NT user name or the passed user name.
fGetDisplayName = UserName
ElseIf err.Number = 462 Then ‘ The remote server machine does not exist or is unavailable.
‘ This happened when I was at a client site connected to the VPN. I don’t understand why.
fGetDisplayName = UserName
ElseIf err.Number = -2147463168 And Len(UserName) > 0 Then ‘ Automation error
‘ This can happen if a Display Name is passed to this function. Return the passed name.
fGetDisplayName = UserName
ElseIf err.Number = -2147024843 And Len(UserName) > 0 Then ‘ Automation error – The network path was not found.
‘ This can happen when working offline (not connected to the domain).
fGetDisplayName = UserName
Else
MsgBox “Error #” & err.Number & ” – ” & err.Description & vbCrLf & “in Function fGetDisplayName()”
fGetDisplayName = UserName
End If
End Function
‘**************************************************************
A few notes:
– This routine can be slow, especially if you are not connected to the domain.
– If this routine fails, it returns the Windows login name as a fallback.
– You can pass in a Windows login name for another user and it will return their display name.
– Because this routine can be slow, I use another function as my main way to get the display name, so this only needs to be called once. See below.
Public Function UserName() ‘ (Name this function whatever you are comfortable with.)
Static strDisplayName As String ‘ Use a static variable so the slow fGetDisplayName function doesn’t run more than once.
If strDisplayName = “” Then
strDisplayName = fGetDisplayName
strDisplayName = Replace(strDisplayName, “‘”, “”) ‘ Remove apostrophes from names like “O’Brien”
End If
UserName = strDisplayName
End Function
That a nice addition to the username code.
What I do is I have a table that houses the users Username and First/Last names, then I can cross-reference them as required but need only the UserName function to identify my users.
I would like to find code that explicitly returns the active/current/logged-in user of my db from anywhere in the world from anyone’s computer completely independent of the operating systems username. (i.e. I am logged into the db on Suzys computer all while while Suzy is logged into her computer. My name returns whenever called. Can you help me… please… ive looked everywhere and everyone says “just grab it from your OS.”
Thanks,
Justin
Take a look at http://www.devhut.net/2013/03/19/ms-access-who-is-logged-in/, I believe this is what you are looking for.
Hi, I’ve been using the API version for years and suddenly it is randomly erroring out with a Type Mismatch error on Access 2016. I’ve updated the code to add the PtrSafe and checked the declarations to make sure all variables are assigned the same datatypes in the GetUserName and USERNAME functions. Here is my code:
General Declarations Section:
Public Declare PtrSafe Function GetUsername Lib “advapi32.dll” Alias “GetUserNameA” (ByVal lpBuffer As String, nSize As LongPtr) As LongPtr
Called Function:
Function USERNAME() As String
‘ Returns the name of the logged-in user
‘ Requires mdlAPI
Dim Buffer As String * 100
Dim BuffLen As LongPtr
BuffLen = 100
GetUsername Buffer, BuffLen
USERNAME = Left(Buffer, BuffLen – 1)
End Function
Am I missing something obvious?
Before anything else, PtrSafe, why? Are you running 32 or 64 bit version of Access? PtrSafe is only for 64 bit instances.
Why not use application.username?
Because the command is not available in all Office applications (for instance it isn’t available in Access or PowerPoint).
Hi! I am trying to create a database that keeps up with users’ productivity on a secured network. I want the field to automatically capture their network ID when they open a new form. None of these Associates have any knowledge of Access, VBA, etc., so they will not be able to spoof. I am looking for an easy way to do this and was hoping to utilize Environ, but it doesn’t seem to be working no matter what I do. Do you have any suggestions? I have tried the following so far: Opened VBA Editor, inserted new module, and entered the following VBA:
Public Function GetUsername() As String
GetUsername = Environ(“Username”)
End Function
I navigated to the form, clicked on the corresponding field, and typed GetUsername() in the Default Value field of the Data tab.
Nothing happens. The field stays blank. I have also tried using GetUsername = Environ$(“Username”) as well. What am I missing? Thank you for your assistance!
Hello,
Thanks for the very helpful information. Is there an equivalent that returns the Domain Name? Thanks…
How about trying something like:
CreateObject("WScript.Network").UserDomain