PowerShell Pipeline

Reporting on Local Groups in PowerShell

In any environment on your network, you have to be sure you know who had administrator rights on specific systems and also want a way to report on who (accounts or groups) that are a part the local groups on a system.

Today I am going to show you how to find members of a local group and show you a quick function that can quickly pull that information that you can then send to a spreadsheet or some other data source for reporting purposes.

We will start out by making a connection to the local system using the Active Directory Service Interface (ADSI) provider so we can then start to examine the group membership. In this case, I am going to use my local system to view the contents of the Administrators group.

$Computer = $env:COMPUTERNAME
$ADSIComputer = [ADSI]("WinNT://$Computer,computer")

If we look at the properties, we really cannot see anything that is too useful to determine how to view the membership of a group.

[Click on image for larger view.]  Figure 1. Properties of the System.DirectoryServices.DirectoryEntry object.

We need to take this a step further by looking at the psbase (raw view of the object) property of the object. In that we can see that there is a Children property which has a method called Find that we can then use to narrow down the scope of what we want to look at.

[Click on image for larger view.]  Figure 2. Properties underneath the psbase property and the Find method under Children property.

Just in case there happens to be a user with the same name as the group, I want to use the second Find method that says to include the schemaClassName along with the name of the group. The schemaClassName for a group is just that: group. With that knowledge, we can now locate the Administrators group on the local system.

$group = $ADSIComputer.psbase.children.find('Administrators',  'Group') 

We can now view the properties of this group and see what is in the object.

[Click on image for larger view.]  Figure 3. Group properties.

Notice how the Children property has absolutely nothing within it. Of course it couldn't be that easy to get the members, right? We'll have to resort to using a method that may not be as simple to understand its purpose.

$group.psbase.invoke("members") 

This simply returns back some System.__ComObject objects which pretty much tells us nothing nor does it really gives us much of a clue as to what should happen next.

Fortunately, I will guide you through this process of what happens next. We will need to iterate through each of these objects and call GetType() to bring out the object type and then call InvokeMember on that object, which gives us access to a method called InvokeMember.

We will then specify five parameters that includes the property name that we want to query as well as the current Com object that we are going to be performing the query against.

$group.psbase.invoke("members")  | ForEach{
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}

What we end up with is a list of Accounts/Groups which are members of the Administrators group.

[Click on image for larger view.]  Figure 4. Members of the Administrators group.

With this information, we are now able to put a little report together that can show us who is currently residing in all of the groups on a local or remote system. Now, I want to make sure that this report not only reports the groups on a system, but also lists all of the accounts/groups that are in the group and also the SIDs of each group.

I have a couple of helper functions in my Get-LocalGroup function that help to pull the members of the group as well as converting the binary SID into the typical SID that you are used to seeing.

Function Get-LocalGroup  {
[Cmdletbinding()]
Param(
[Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[String[]]$Computername = $Env:COMPUTERNAME,
[parameter()]
[string[]]$Group
)
Begin {
Function ConvertTo-SID {
Param([byte[]]$BinarySID)
(New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
}

        Function Get-LocalGroupMember {
Param ($Group)
$group.Invoke('members') | ForEach {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
}
}
Process {
ForEach ($Computer in $Computername) {
Try {
Write-Verbose "Connecting to $($Computer)"
$adsi = [ADSI]"WinNT://$Computer"
If ($PSBoundParameters.ContainsKey('Group')) {
Write-Verbose "Scanning for groups: $($Group -join ',')"
$Groups = ForEach ($item in $group) {                       
$adsi.Children.Find($Item, 'Group')
}
} Else {
Write-Verbose "Scanning all groups"
$groups = $adsi.Children | where {$_.SchemaClassName -eq 'group'}
}
If ($groups) {
$groups | ForEach {
[pscustomobject]@{
Computername = $Computer
Name = $_.Name[0]
Members = ((Get-LocalGroupMember -Group $_)) -join ', '
SID = (ConvertTo-SID -BinarySID $_.ObjectSID[0])
}
}
} Else {
Throw "No groups found!"
}
} Catch {
Write-Warning "$($Computer): $_"
}
}
}
}

Get-LocalGroup -Computername $env:COMPUTERNAME -Group Administrators, Users -Verbose | Format-List

Using it is a matter of sending a list of computers to the function (or just 1) and viewing the results of it.

Get-LocalGroup -Computername  $env:COMPUTERNAME 
[Click on image for larger view.]  Figure 5. All groups returned from function.

Maybe I only want just the members of the Administrators group. No problem! I'll just use the –Group parameter and specify Administrators.

Get-LocalGroup -Computername  $env:COMPUTERNAME -Group  Administrators |  Format-List 
[Click on image for larger view.]  Figure 6. Local Administrators group returned from function.

And if we want more than 1 group, but not all of the groups:

Get-LocalGroup -Computername  $env:COMPUTERNAME -Group  Administrators,  Users -Verbose  | 
Format-List
[Click on image for larger view.]  Figure 7. Users and Administrators groups returned from function.

Now we have a way of not only viewing all of the local groups and its members, but we can easily filter for specific groups to report on as well!

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular