Search code examples
windowspowershellactive-directoryadsisid

ADSI Query Results in Duplicate SIDs for Active Directory Groups


I'm using a modified version of the function located here to get the SIDs of the members of the local Administrators group. I would use Get-LocalGroupMember if I could, but I need to use ADSI because I need access to the SIDs when Active Directory is not reachable (there's an issue with the cmdlet if only a SID is present).

The issue is that if there's 2 AD groups with the same name, but in different domains, they appear to have the same SID, which is not the case in reality. Here's an example:

Username Type SID Path
Domain Users Group S-1-5-21-1390067357-2000478354-839522115-513 DOMAIN1/Domain Users
Domain Users Group S-1-5-21-1390067357-2000478354-839522115-513 DOMAIN2/Domain Users

How can this be resolved? Below is the function I'm using:

Function Get-LocalGroupMembers  {

[Cmdletbinding()] 
Param( 
    [Parameter(Mandatory=$true)]
    [string]$GroupName
)

[adsi]$adsiGroup = "WinNT://$($env:COMPUTERNAME)/$GroupName,group"

$adsiGroup.Invoke('Members') | ForEach-Object{

    $username = $_.GetType().InvokeMember('Name','GetProperty',$null,$_,$null)
    $path = $_.GetType().InvokeMember('AdsPath','GetProperty',$null,$_,$null).Replace('WinNT://','')
    $class = $_.GetType().InvokeMember('Class','GetProperty',$null,$_,$null)
    $userObj = New-Object System.Security.Principal.NTAccount($username)
    
    try{
        $sid = $userObj.Translate([System.Security.Principal.SecurityIdentifier])
    }catch{
        $sid = $userObj
    }
    [pscustomobject]@{
        Username = $username
        Type = $class
        SID = $sid
        Path = $path
    }

}   
}

Solution

  • @Scepticalist had the solution and this is the modified function that now works as expected

    Function Get-LocalGroupMembers  {
    
    [Cmdletbinding()] 
    Param( 
        [Parameter(Mandatory=$true)]
        [string]$GroupName
    )
    
    [adsi]$adsiGroup = "WinNT://$($env:COMPUTERNAME)/$GroupName,group"
    
    $Members = @()
    
    $adsiGroup.Invoke('Members') | ForEach-Object{
    
        $username = $_.GetType().InvokeMember('Name','GetProperty',$null,$_,$null)
        $path = $_.GetType().InvokeMember('AdsPath','GetProperty',$null,$_,$null).Replace('WinNT://','')
        $class = $_.GetType().InvokeMember('Class','GetProperty',$null,$_,$null)
        $userObj = $null
        
        if ($path -notlike "*S-1-5*"){ #If the path does not contain a SID, then AD is reachable and we can translate successfully
            $Parts = $path.Split("/") 
            <#
                $Parts can look like:
                Local Account  - Domain, ComputerName, AccountName 
                Domain Account - Domain, AccountName 
            #>
            $Domain = $Parts[0]
    
            if ($Parts[1] -eq $env:COMPUTERNAME){ #Based on above comment, if arg 2 is the ComputerName, it's a local account
                $userObj = New-Object System.Security.Principal.NTAccount($username)
    
            }else{#Otherwise it's a domain account and we need to translate using the domain
                $userObj = New-Object System.Security.Principal.NTAccount($Domain, $username)
            }
            $sid = $userObj.Translate([System.Security.Principal.SecurityIdentifier])
        }else{ #Otherwise SID is the username since AD is not reachable
            $sid = $username
        }
    
        $Members += [pscustomobject]@{
            Username = $username
            Type = $class
            SID = $sid
            Path = $path
        }
    }   
    return $Members
    }