Print Local Group Members in PowerShell 5.0
Asked Answered
F

5

10

I use the following code to determine members of the local Administrators group:

$obj_group = [ADSI]"WinNT://localhost/Administrators,group"
$members=@($obj_group.Invoke("Members"))|foreach{$_.GetType().InvokeMember("Name","GetProperty",$null,$_,$null)}
Write-Output "Current local Administrators: $members"

This code works in PowerShell 2.0 - 4.0. However, on my Windows 10 machine with PowerShell 5.0, it breaks. For each local account that is a member of the local Administrators group, it throws the following error:

Error while invoking GetType. Could not find member.
At line:2 char:54
+ ... "))|foreach{$_.GetType().InvokeMember("Name","GetProperty",$null,$_,$ ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], MissingMemberException
    + FullyQualifiedErrorId : System.MissingMemberException

For domain accounts that are a member of Administrators, no error is generated.

The thing that puzzles me is GetType() is a member of of the object (I traced out the command by hand), so I am not sure why it errors out.

I looked at the changelog for PowerShell 5.0 and did not see anything that would obviously explain this behavior.

Why is this happening? If there a better way to print members of a local group in PowerShell 5.0?

Fredkin answered 11/8, 2015 at 18:39 Comment(0)
W
12

Ran into this issue myself and figured out a workaround (tested in windows 10 and 8.1)

$obj_group = [ADSI]"WinNT://localhost/Administrators,group"
$members= @($obj_group.psbase.Invoke("Members")) | foreach{([ADSI]$_).InvokeGet("Name")}
Write-Output "Current local Administrators: $members"
Whitmire answered 20/8, 2015 at 14:40 Comment(1)
That fixed it. Tested in XP (PowerShell 2.0), Windows 7 (PowerShell 4.0), and Windows 10 (PowerShell 5.0).Fredkin
S
5

Jamie's answer was perfect for your specific issue, but I have a need to get multiple properties from the members. I found you can address this issue without otherwise changing your code by calling Invoke against GetType before calling InvokeMember against it. Note that GetType no longer has () after it in the code below:

$obj_group = [ADSI]"WinNT://localhost/Administrators,group"
$members=@($obj_group.Invoke("Members"))|foreach{$_.GetType.Invoke().InvokeMember("Name","GetProperty",$null,$_,$null)}
Write-Output "Current local Administrators: $members"

This was my use case which provides more information about the group members. This does require PowerShell 4.0 due to the use of the Resolve-DNS command:

function Get-LocalGroupMembers {
<#
.Synopsis
   Get the group membership of a local group on the local or a remote computer
.EXAMPLE
   Defaults to collecting the members of the local Administrators group

    PS C:\> Get-LocalGroupMembers | ft -AutoSize

    ComputerName ParentGroup Nesting Name          Domain       Class
    ------------ ----------- ------- ----          ------       -----
    EricsComputer                   0 Administrator EricsComp    User 
    EricsComputer                   0 eric          EricsComp    User 
    EricsComputer                   0 Domain Admins DomainName   Group
.EXAMPLE
   Query a remote computer (that is known not to respond to a ping) and a targeted group

    PS C:\> Get-LocalGroupMembers -computerName EricsComputer -localgroupName Users -pingToEstablishUpDown $false

    ComputerName ParentGroup Nesting Name          Domain       Class
    ------------ ----------- ------- ----          ------       -----
    EricsComputer                   0 SomeOtherGuy  EricsComp    User 

.NOTES
   The ParentGroup and Nesting attributes in the output are present to allow
   the output of this function to be combined with the output of 
   Get-ADNestedGroupMembers.  They serve no purpose otherwise.
#>
    Param(
        $computerName = $env:computername,
        $localgroupName = "Administrators",
        $pingToEstablishUpDown = $true
    )
    $requestedComputerName = $computerName
    if ($computername = Resolve-DnsName $computername) {
        $computername = ($computername | where querytype -eq A).Name
        if ($computername -ne $requestedComputerName) {
            Write-Warning "Using name $computerName for $requestedComputerName"
        }
    } else {
        Write-Warning "Unable to resolve $requestedComputerName in DNS"
        return "" | select @{label="ComputerName";Expression={$requestedComputerName}},
                                        @{label="ParentGroup";Expression={""}},
                                        @{label="Nesting";Expression={""}},
                                        @{Label="Name";Expression={"ComputerName did not resolve in DNS"}},
                                        @{Label="Domain";Expression={"ComputerName did not resolve in DNS"}},
                                        @{Label="Class";Expression={"ComputerName did not resolve in DNS"}}
    }
    if ($pingToEstablishUpDown) {
        if (-not (Test-Connection -count 1 $computerName)) {
            Write-Warning "Unable to ping $computerName, aborting ADSI connection attempt"
            return "" | select @{label="ComputerName";Expression={$requestedComputerName}},
                                        @{label="ParentGroup";Expression={""}},
                                        @{label="Nesting";Expression={""}},
                                        @{Label="Name";Expression={"Not available to query"}},
                                        @{Label="Domain";Expression={"Not available to query"}},
                                        @{Label="Class";Expression={"Not available to query"}}
        }
    }
    try {
        if([ADSI]::Exists("WinNT://$computerName/$localGroupName,group")) {    
            $group = [ADSI]("WinNT://$computerName/$localGroupName,group")  
            $members = @()  
            $Group.Members() | foreach {
                $AdsPath = $_.GetType.Invoke().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)
                # Domain members will have an ADSPath like WinNT://DomainName/UserName.  
                # Local accounts will have a value like WinNT://DomainName/ComputerName/UserName.  
                $a = $AdsPath.split('/',[StringSplitOptions]::RemoveEmptyEntries)
                $name = $a[-1]  
                $domain = $a[-2]  
                $class = $_.GetType.Invoke().InvokeMember("Class", 'GetProperty', $null, $_, $null)  

                $members += "" | select @{label="ComputerName";Expression={$computerName}},
                                        @{label="ParentGroup";Expression={""}},
                                        @{label="Nesting";Expression={0}},
                                        @{Label="Name";Expression={$name}},
                                        @{Label="Domain";Expression={$domain}},
                                        @{Label="Class";Expression={$class}}
            }    
        }  
        else {  
            Write-Warning "Local group '$localGroupName' doesn't exist on computer '$computerName'"  
        }
    }
    catch { 
        Write-Warning "Unable to connect to computer $computerName with ADSI"
        return $false }
    return ,$members
}
Szabo answered 29/12, 2015 at 18:28 Comment(0)
C
3

Nice! Needed this!

The .net way was also a bypass:

Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$computer =  $env:COMPUTERNAME
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members |  select @{N='Server'; E={$computer}}, @{N='Domain'; E={$_.Context.Name}}, samaccountName
Canzona answered 28/1, 2016 at 14:49 Comment(1)
This was the only solution that worked for me on a AzureAD joined computerRhineland
M
1

You may want to post the bug here as someone might have a workaround available.

Magdamagdaia answered 12/8, 2015 at 1:30 Comment(1)
It was already posted: connect.microsoft.com/PowerShell/feedback/details/1437366/…Fredkin
T
0

I found you could also resolve this by replacing $_.GetType() with the underlying [__ComObject] type:

$obj_group = [ADSI]"WinNT://./Administrators,group"
$members = @($obj_group.Invoke('Members')) | ForEach-Object { [__ComObject].InvokeMember('Name', 'GetProperty', $null, $_, $null) }
Write-Output "Current local Administrators: $members"

Hope this helps

Tarratarradiddle answered 15/3 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.