Get List of User Profiles

I need a way in VB.NET to get a list of users who have profiles on the machine. I'd rather not do this by just going to the Documents and Settings folder and enumerating folder names, and it needs to be their user names, and not just folder names (since TestUser.046's user name isn't TestUser.046 ;)). I'd like code tha will work on at least Windows 2000 and Windows XP, and I'd be extra happy if it was NT compatible. It should also run for Power User. I have control over the machines, so if this means changing the security on a few keys, that's OK.

1) Open the registry to get the ProfileList
key = reg_hklm.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", False)
2) Loop thru the subkeys
For Each keyname In key.GetSubKeyNames
3) Get the SID value under each subkey (or use the keyname as the SID)
RawSID = CType(subkey.GetValue("SID", Nothing), Byte())
4) Convert the SID to a Username using the API (Misc.SidToName is just a wrapper class I made around the APIs)
If Not IsNothing(RawSID) Then
UserID = Misc.SidToName(RemotePC, RawSID)
Else
' If we don't have an imbedded SID, then we
' use the key name instead
UserID = Misc.SidToName(RemotePC, keyname)
End If

I have a complete example (that does a lot more than just list the names) that works for WinNT thru WinVista.

Okey dokey... This sample is pulled from a complete application, so it will probably not compile by itself. It also contains a bunch of stuff that you can safely ignore (like the database stuff and the mounting of registry hives)

'
' Get information about the User Profiles and mount all of the User's
' registry hives.
' Note: The registry hives mounted here are used in: GetEmail, GetInternet,
' GetMapped, GetShares, and GetStartup. We do not update the "reference
' count" for the hive, so it's possible that some other program might
' umount the hive before we read from it.
'
Public Sub GetProfiles()
If IsWin32 Then
Dim DefaultUserProfile, keyname, ImagePath, ProfileName, UserID, LoadedHives() As String
Dim key, subkey, testkey As RegistryKey
Dim flags, num_users As Integer
Dim ld As LongDate
Dim RawSID() As Byte
Dim admin, we_mounted_it As Boolean
Dim dr As SOSOSDataSet.ProfilesRow

ImagePath = ""
keyname = ""
Try
' the mounting of registry hives is a tightly held privilege
' that is not enabled by default (even for administrators)
admin = Misc.IsAdmin()

' make a list of hives that are currently loaded
LoadedHives = reg_hku.GetSubKeyNames

' for WinVista, the flags trick doesn't work to weed
' out the non-numan keys
If OSVer >= 6.0 And IsNothing(RawSID) Then
flags = -1
End If

' we only want "human" profiles (WinNT doesn't have
' this flag, but it also doesn't list non-human keys)
If flags = 0 Or OSVer = 4.0 Then
num_users += 1
ImagePath = subkey.GetValue("ProfileImagePath", "").ToString
' We have to substitute the values for our expanded
' enivronmental variables with those of the remote
' PC!
If RemotePC <> "" Then
ImagePath = ImagePath.Replace(Environment.ExpandEnvironmentVariables("%SystemDrive%"), SystemDrive)
End If
If Directory.Exists(Misc.ConvUNC(RemotePC, ImagePath)) Then
ld.date_hi = CInt(subkey.GetValue("ProfileLoadTimeHigh", 0))
ld.date_lo = CInt(subkey.GetValue("ProfileLoadTimeLow", 0))
ProfileName = Path.GetFileName(ImagePath)
If Not IsNothing(RawSID) Then
UserID = Misc.SidToName(RemotePC, RawSID)
Else
' If we don't have an imbedded SID, then we
' use the key name instead
UserID = Misc.SidToName(RemotePC, keyname)
End If

' Let's mount the user's hive (if it isn't
' already mounted)
we_mounted_it = False
If admin AndAlso Array.IndexOf(LoadedHives, keyname) < 0 Then
' Since we're doing this remotely, the path
' to the hive is relative to the remote PC
If Misc.MountHive(RemotePC, keyname, ImagePath & "\NTUSER.DAT") = 0 Then
we_mounted_it = True
' we tag the ones that we mounted with
' an asterisk
ProfileList.Add(ImagePath, "*" & keyname)
End If
End If

If OSVer = 4.0 Or admin = False Then
' This is a bit crude, but it's better than
' nothing
dr.Profile_IsAdmin = False
If Not IsNothing(ds.Admins.FindByID_AdminAdmin_UserID(ID, UserID)) Then
dr.Profile_IsAdmin = True
End If
Else
dr.Profile_IsAdmin = Misc.IsRemoteAdmin(MachineName, reg_hku, UserID)
End If
If ld.Long64 > 0 Then
' Not supported in WinNT or WinVista
dr.Profile_LastLoad = Date.FromFileTime(ld.Long64)
End If
ds.Profiles.AddProfilesRow(dr)

' Let's do a "test run" to verify that the
' hive got mounted and that current user has
' permission to read it. We do this here
' rather than in each one of the "dependent"
' methods.
Try
testkey = reg_hku.OpenSubKey(keyname & "\Control Panel", False)
If Not IsNothing(testkey) Then
testkey.Close()
End If
If we_mounted_it = False Then
ProfileList.Add(ImagePath, keyname)
End If
Catch
' if this test fails, then this key
' won't appear in the ProfileList
End Try
End If
End If
subkey.Close()
Next

'
' Take a String version of a SID and generate a User ID (used in
' GetProfiles) (overloaded)
'
Public Shared Function SidToName(ByVal RemotePC As String, ByVal sid_string As String) As String
Dim sid As IntPtr

sid = StringToSID(sid_string)
If IsNothing(sid) Then
Return sid_string
End If

' look up the Account associated with that SID
If LookupAccountSid(RemotePC, sid, name, name_len, domain_name, domain_len, peUse) = False Then
' if the lookup fails, return the SID as as string
Return SIDtoString(sid)
End If

If you want to see the whole thing in action, you can download the complete application that this sample comes from. (it might be a bit of overkill, but what the heck) http://www.dpw.hood.army.mil/ftp/SOSOS/

The SidToName method is overloaded (3 different signatures for the same function).... one takes a Byte(), one takes an String, and one takes a IntPtr. You need to include them all.

But hang on... I just did a "cut-n-paste" from a working application to demontrate the concepts. (I wasn't expecting it to compile... after all, I was just trying to explain how you'd do something similar in your code).

On the other hand, you *can* download the whole application (which obviously will compile).

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Yeah, but.... the SeRestorePrivilege is typically only provided to members of the Administrators / Backup Operators group, so Power Users / Users would not be able to use that privilege and therefore would not be able to enumerate all of the profiles.

' open the registry (could be remote)
If RemotePC <> "" Then
reg_hklm = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, RemotePC)
Else
reg_hklm = Registry.LocalMachine
End If

' Get the list of profiles from the registry
key = reg_hklm.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", False)
If Not IsNothing(key) Then
' loop thru each subket
For Each keyname In key.GetSubKeyNames
subkey = key.OpenSubKey(keyname, False)
RawSID = CType(subkey.GetValue("SID", Nothing), Byte())

If Not IsNothing(RawSID) Then
UserID = SidToName(RemotePC, RawSID)
Else
' If we don't have an imbedded SID, then we
' use the key name instead
UserID = SidToName(RemotePC, keyname)
End If

'
' Take a String version of a SID and generate a User ID (used in
' GetProfiles) (overloaded)
'
Private Function SidToName(ByVal RemotePC As String, ByVal sid_string As String) As String
Dim sid As IntPtr

sid = StringToSID(sid_string)
If IsNothing(sid) Then
Return sid_string
End If

' look up the Account associated with that SID
If LookupAccountSid(RemotePC, sid, name, name_len, domain_name, domain_len, peUse) = False Then
' if the lookup fails, return the SID as as string
Return SIDtoString(sid)
End If

XML Literals are a great way to handle XML files and the community doesn’t use it as much as it should. An XML Literal is like a String (http://msdn.microsoft.com/en-us/library/system.string.aspx) Literal, only instead of starting and ending with w…

Creating an analog clock UserControl seems fairly straight forward. It is, after all, essentially just a circle with several lines in it! Two common approaches for rendering an analog clock typically involve either manually calculating points with…

Internet Business Fax to Email Made Easy -
With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number.
You'll receive secure faxes in your email, fr…

When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.