Link to home
Start Free TrialLog in
Avatar of usachrisk1983
usachrisk1983Flag for United States of America

asked on

Get List of User Profiles

Hello,

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.

Bonus points if I can get the domain they logged in with.

Thanks!
Chris.
Avatar of willbdman
willbdman
Flag of Australia image

First you need to add a reference to System.DirectoryServices.dll

Then use this code to add users to a listbox:-

    Private Sub GetUsers()

        'Make DirectoryEntry of Your Computer
        Dim AD As System.DirectoryServices.DirectoryEntry = New System.DirectoryServices.DirectoryEntry("WinNT://" + Environment.MachineName)

        ' For each child in tree
        For Each child As DirectoryServices.DirectoryEntry In AD.Children

            ' If child is user
            If child.SchemaClassName = "User" Then Me.ListBox1.Items.Add(child.Name)

        Next


    End Sub
Avatar of usachrisk1983

ASKER

Looks like that is only getting me local accounts on the machine.
There are several steps to the process...

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.
I'd love to see your full example.
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

                ' get the listing of profiles from the registry
                key = reg_hklm.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", False)
                If Not IsNothing(key) Then
                    ' loop thru each subkey
                    num_users = 0
                    For Each keyname In key.GetSubKeyNames
                        subkey = key.OpenSubKey(keyname, False)
                        flags = CInt(subkey.GetValue("Flags", -1))
                        RawSID = CType(subkey.GetValue("SID", Nothing), Byte())

                        ' 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

                                dr = ds.Profiles.NewProfilesRow
                                dr.ID_Profile = ID
                                dr.Profile = Left(ProfileName, ds.Profiles.ProfileColumn.MaxLength)
                                dr.Profile_Path = Left(ImagePath, ds.Profiles.Profile_PathColumn.MaxLength)
                                If UserID <> "" Then
                                    dr.Profile_Owner = Left(UserID, ds.Profiles.Profile_OwnerColumn.MaxLength)
                                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

                    DefaultUserProfile = key.GetValue("DefaultUserProfile", "Default User").ToString
                    key.Close()

                    ' Update the PC table's NumUser field
                    If ds.PC.Count > 0 Then
                        ds.PC(0).PC_NumUsers = num_users
                    End If

                    ' manually create an entry for the Default User
                    If ds.Profiles.Count > 0 Then
                        Dim parts() As String

                        dr = ds.Profiles.NewProfilesRow
                        dr.ID_Profile = ID
                        dr.Profile = DefaultUserProfile

                        ' use an existing entry as a prototype
                        parts = ds.Profiles(0).Profile_Path.Split("\"c)
                        parts(parts.Length - 1) = DefaultUserProfile
                        ImagePath = Join(parts, "\")
                        dr.Profile_Path = Left(ImagePath, ds.Profiles.Profile_PathColumn.MaxLength)
                        dr.Profile_IsAdmin = False
                        ds.Profiles.AddProfilesRow(dr)

                        we_mounted_it = False
                        If admin AndAlso Array.IndexOf(LoadedHives, DefaultUserProfile) < 0 Then
                            ' Since we're doing this remotely, the path
                            ' to the hive is relative to the remote PC
                            If Misc.MountHive(RemotePC, DefaultUserProfile, ImagePath & "\NTUSER.DAT") = 0 Then
                                we_mounted_it = True
                                ' we tag the ones that we mounted with
                                ' an asterisk
                                ProfileList.Add(ImagePath, "*" & DefaultUserProfile)
                            End If
                        End If
                    End If
                End If
            Catch ex As Exception
                dr = ds.Profiles.NewProfilesRow
                dr.ID_Profile = ID
                dr.Profile = "Error"
                dr.Profile_IsAdmin = False
                dr.Profile_Path = Left("Error: " & ex.Message, ds.Profiles.Profile_PathColumn.MaxLength)
                ds.Profiles.AddProfilesRow(dr)
                Misc.ErrorLog(ex, MachineName, "Profiles", Misc.ErrLogLevel.Errors)
            End Try
        End If
    End Sub

    'BOOL LookupAccountSid(
    '  LPCTSTR lpSystemName,
    '  PSID lpSid,
    '  LPTSTR lpName,
    '  LPDWORD cchName,
    '  LPTSTR lpReferencedDomainName,
    '  LPDWORD cchReferencedDomainName,
    '  PSID_NAME_USE peUse
    ');
    Private Declare Auto Function LookupAccountSid Lib "advapi32.dll" ( _
        ByVal lpSystemName As String, _
        ByVal lpSid As IntPtr, _
        ByVal lpName As String, _
        ByRef cchName As Integer, _
        ByVal lpReferenceDomainName As String, _
        ByRef cchReferencedDomainName As Integer, _
        ByRef peUse As Integer _
    ) As Boolean

    '
    ' Take a "raw" SID (as a byte array) and determine the User ID (used in
    ' GetProfiles) (overloaded)
    '
    Public Shared Function SidToName(ByVal RemotePC As String, ByVal RawSid As Byte()) As String
        Dim Sid As IntPtr
        Dim gch As GCHandle
        Dim ans As String

        ' get a safe pointer to the SID array of bytes
        gch = GCHandle.Alloc(RawSid, GCHandleType.Pinned)
        Sid = Marshal.UnsafeAddrOfPinnedArrayElement(RawSid, 0)
        ans = SidToName(RemotePC, Sid)
        gch.Free()

        Return ans
    End Function

    '
    ' 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

        Return SidToName(RemotePC, sid)
    End Function

    '
    ' Take an IntPtr SID and generate the User ID (overloaded)
    '
    Private Shared Function SidToName(ByVal RemotePC As String, ByVal sid As IntPtr) As String
        Dim UserName, name, domain_name As String
        Dim name_len, domain_len, peUse As Integer

        name_len = NAME_SIZE
        domain_len = NAME_SIZE
        name = Space(name_len)
        domain_name = Space(domain_len)

        ' 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

        ' put the name parts together
        If domain_len > 0 Then
            UserName = Left(domain_name, domain_len) & "\" & Left(name, name_len)
        Else
            If RemotePC = "" Then
                RemotePC = Environment.MachineName
            End If
            UserName = RemotePC & "\" & Left(name, name_len)
        End If

        Return UserName
    End Function

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/
This line:

ans = SidToName(RemotePC, Sid)

Gives the error: value of type 'System.IntPtr' cannot be converted to '1-dimensional array of Byte'.

Avatar of Bob Learned
Here is a WMI answer, using the Win32_NetworkLoginProfile:

' Add a reference to System.Management.dll.

Imports System.Management

Public Class Win32_NetworkLoginProfile

  Public Caption As String
  Public Description As String

  Public Shared Function GetList() As Win32_NetworkLoginProfile()

    Dim query As String = "Select * From Win32_NetworkLoginProfile"

    Dim searcher As New ManagementObjectSearcher(query)

    Dim Profiles As ManagementObjectCollection = searcher.Get()

    Dim listProfiles(Profiles.Count - 1) As Win32_NetworkLoginProfile

    Dim count As Integer = 0

    For Each entryCurrent As ManagementObject In Profiles

      Dim profile As New Win32_NetworkLoginProfile

      profile.Caption = entryCurrent.Properties("Caption").Value
      profile.Description = entryCurrent.Properties("Description").Value

      listProfiles(count) = profile

      count += 1

    Next entryCurrent

    Return listProfiles

  End Function

End Class

Test code:
    Dim builder As New System.Text.StringBuilder

    For Each profile As Win32_NetworkLoginProfile In Win32_NetworkLoginProfile.GetList()
      builder.Append(profile.Caption & vbTab & profile.Description & vbNewLine)
    Next profile

    MessageBox.Show(builder.ToString())

Bob
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).
Bob... The WMI classes only give you the psuedo-accounts and the currently logged in user's profile.  It will not enumerate profiles that are not currently loaded.
Graye: I was actually just taking what I needed, and excluding all else.  I appreciate your assistance, but I think the following solution is going to work for me.

Bob, your solution worked perfectly, and I can't believe I didn't run immediately to WMI for this, since I tend to work in VBScript.
Ooops, did I then accept too soon?

Graye, what type of profiles do you not expect to showwith this?
@graye,
Good to know Emmet, I thought that was the only accounts that I have profiles for, but it appears that I have more.

Bob
Naaa... WMI is not the answer

Would you like us to write you a fully working sample?   I can do that sometime later this evening (or perhaps tomorrow)
Thanks for saving me a post to Community Support, LearnedOne ;)

If adding the remaining two SID functions will fix it, then I will give that a go.  
Would this help?

different results from Win32_NetworkLoginProfile
http://www.scriptinganswers.com/Community/DiscussionForums/tabid/154/forumid/6/postid/4179/view/topic/Default.aspx

Server Stops Responding When Win32_NetworkLoginProfile Performs Enumeration
http://support.microsoft.com/kb/816485

Bob
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.
ASKER CERTIFIED SOLUTION
Avatar of graye
graye
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Worked great, and increaing points to 500 :)