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