Managing Local Group Members (Part 1)

by Dec 28, 2020

Fortunately, PowerShell 5 and better comes with cmdlets like Get-LocalGroupMember which list the members of local groups. Unfortunately, these cmdlets have a flaw: if the group contains one or more orphaned members, the cmdlet fails to list any group member.

Orphaned group members could be users or groups that were added to the group but later deleted in Active Directory. Such orphans show as SID numbers instead of names in dialogs.

To work around this and list group members regardless of orphaned accounts, try following Get-GroupMemberLocal function which uses the old ADSI approach that was commonly used before PowerShell shipped with its own cmdlets:

function Get-GroupMemberLocal
{
    [CmdletBinding(DefaultParameterSetName='Name')]
    param
    (
        [Parameter(Mandatory,Position=0,ParameterSetName='Name')]
        [string]
        $Name,

        [Parameter(Mandatory,Position=0,ParameterSetName='Sid')]
        [System.Security.Principal.SecurityIdentifier]
        $Sid,

        [string]
        $Computer = $env:COMPUTERNAME
    )
    
    if ($PSCmdlet.ParameterSetName -eq 'Sid')
    {
        $Name = $sid.Translate([System.Security.Principal.NTAccount]).Value.Split('\')[-1]
    }

    $ADSIComputer = [ADSI]("WinNT://$Computer,computer")
    $group = $ADSIComputer.psbase.children.find($Name,  'Group') 
    $group.psbase.invoke("members") | 
        ForEach-Object {
            try
            {
                $disabled = '-'
                $disabled = $_.GetType().InvokeMember("AccountDisabled",  'GetProperty',  $null,  $_, $null)
            } catch {}
            [PSCustomObject]@{
                Name = $_.GetType().InvokeMember("Name",  'GetProperty',  $null,  $_, $null)
                SID  = [Security.Principal.SecurityIdentifier]::new($_.GetType().InvokeMember("objectSid",  'GetProperty',  $null,  $_, $null),0)
                Path = $_.GetType().InvokeMember("AdsPath",  'GetProperty',  $null,  $_, $null)
                Type = $_.GetType().InvokeMember("Class",  'GetProperty',  $null,  $_, $null)
                Disabled = $disabled
            }
        }
 }

Below is a practical example: let’s dump the local Administrator group. You can access the group either by name or by culture-invariant SID:

 
PS> Get-GroupMemberLocal -Sid S-1-5-32-544 | Format-Table

Name          SID                                            Path                                     Type Disabled
----          ---                                            ----                                     ---- --------
Administrator S-1-5-21-2770831484-2260150476-2133527644-500  WinNT://WORKGROUP/DELL7390/Administrator User     True
Presentation  S-1-5-21-2770831484-2260150476-2133527644-1007 WinNT://WORKGROUP/DELL7390/Presentation  User    False
RemoteAdmin   S-1-5-21-2770831484-2260150476-2133527644-1013 WinNT://WORKGROUP/DELL7390/RemoteAdmin   User    False
Management    S-1-5-21-2770831484-2260150476-2133527644-1098 WinNT://WORKGROUP/DELL7390/Management    Group       -  
 


Twitter This Tip! ReTweet this Tip!