We help IT Professionals succeed at work.

New podcast episode! Our very own Community Manager, Rob Jurd, gives his insight on the value of an online community. Listen Now!

x

Powershell script to list all groups, nested groups and their members in a csv file

Indie101
Indie101 asked
on
2,787 Views
Last Modified: 2018-12-14
Hi,

I know I can use

Get-AdGroupMember "Name of group"

-Recursive| Select DistinguishedName

To find the nested groups, I want to use this with get-adgroup -filter *  | sort name | select Name (realise this is pretty basic)

Want to pipe it to | export-csv C:\test.csv -NotypeInformation

Do I have to have the name of each group which is nested? Is there a handy way to do this? Thanks
Comment
Watch Question

Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
Is this what you had in mind?
Get-ADGroup -filter * | Select-Object Name, @{n='Members';e={ $_ | Get-ADGroupMember -Recursive | Select-Object Name }}

Open in new window

Author

Commented:
Thanks Chris looks great would you mind giving a brief explanation  of @{n='Members';e={ $_ | Get-ADGroupMember -Recursive | Select-Object Name }} just for future reference

It returns all the group names, but only members when that group has one member, is there a way to show all members?

I know the @ turns list contents into an array just investigating further :)
Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
Certainly.

Select-Object is normally used to pick specific properties from an object (or collection / array of objects).
Get-Process | Select-Object Name, Id

Open in new window

Select-Object can also be used to create custom properties. This feature is useful when there is a need to rename a property, or add related information.

Custom properties are created using a hashtable (@{}), an associative array (keys and values). The hashtable must contain two keys, Name or Label, and Expression. For example:
@{Name = 'NewProperty'; Expression = { 'Code to generate the value' }}
@{Label = 'NewProperty'; Expression = { 'Code to generate the value' }}

Open in new window

The label key, while permissible, is rarely used; name is the most common choice. Select-Object allows short-hand, the keys Name and Expression can be shortened to "n" and "e" respectively.
@{n = 'NewProperty'; e = { 'Code to generate the value' }}

Open in new window

The example below selects 3 properties from Get-Process. The third value, WorkingSet, which holds the amount of memory a process uses, is converted from bytes to MB using a custom property.
Get-Process | Select Name, Id, @{n='WorkingSet';e={ $_.WorkingSet / 1MB }}

Open in new window

Select-Object can include any number of custom properties, each separated by a comma. The example below adds a second property, the owner of the file (based on the NTFS Access Control List) if the Path property of the process is set.
Get-Process | Select Name,
    Id,
    @{n='WorkingSet';e={ [Math]::Round($_.WorkingSet / 1MB, 2) }},
    @{n='Owner';e={ if ($_.Path) { (Get-Acl $_.Path).Owner } }}

Open in new window

Author

Commented:
Thanks Chris it shows members when only one member in a group , is there a way to get all members for each group? thanks again, great explanation
PowerShell Developer
CERTIFIED EXPERT
Top Expert 2010
Commented:
Unlock this solution and get a sample of our free trial.
(No credit card required)
UNLOCK SOLUTION

Author

Commented:
Got below error it had run two thirds through before giving it

Get-ADGroup : The server has returned the following error: invalid enumeration context.
At line:1 char:1
+ Get-ADGroup -filter * | Select-Object Name, @{n='Members';e={ $_ | Ge ...
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ADGroup], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroup
Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
Hah it didn't like that much did it...

It always makes me sigh when the AD module does that. I think it's caused by a rate limiting mechanism. Somewhat reasonable because the recursive query is very intensive.

We can try and change the manner in which it gets that. Is it members which are groups you're interested in?
Get-ADGroup -filter * | Select-Object Name, @{n='Members';e={ Get-ADGroup -LdapFilter "(memberOf:1.2.840.113556.1.4.1941:=$($_.DistinguishedName))" | Select-Object -ExpandProperty Name }}

Open in new window

This uses a specific OID to expand membership.

Documentation for this one is here:

https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx

Author

Commented:
Thanks it returns 258 groups, not the full amount as original did, 3048 groups originally you've done a lot more than I expected

I'm just not sure how to get all 3048 groups
Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
In the member list? Or is that the number of groups it's returning (from Get-ADGroup)?

Author

Commented:
3048 groups were returned from initial code you supplied

Get-ADGroup -filter * | Select-Object Name, @{n='Members';e={ $_ | Get-ADGroupMember -Recursive | Select-Object Name }}
Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
The only change I've made is to the "Members" column. That's limited to groups at the moment, it can be opened up again:
Get-ADGroup -filter * | Select-Object Name, @{n='Members';e={ Get-ADObject -LdapFilter "(memberOf:1.2.840.113556.1.4.1941:=$($_.DistinguishedName))" | Select-Object -ExpandProperty Name }}

Open in new window

If you actually have groups with that number of members you need to go back to the original requirement, think about why you're doing this, and evaluate if this is really the best way. It's a horribly expensive operation, recursive group expansion is a big thing to ask a DC to do, doing it fora  large number of groups is not sensible at all.

Author

Commented:
Apologies Chris generally the groups have 20-30 members or less from what I have seen, 3048 is the number of groups present which is great

Just the original shows one member (when one member is present)
Chris DentPowerShell Developer
CERTIFIED EXPERT
Top Expert 2010

Commented:
I can't guarantee if this will work very well. It completely avoids the MS commands. Whether or not it works as is depends on your PowerShell version as well. I run PowerShell 5.1, syntax used is generally compatible down to PowerShell 4.0. Anything less than that is a bit less sure.

$PSVersionTable will show the version you're running.
function Get-ADSIGroup {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String]$Name
    )

    $searcher = [ADSISearcher]"(&(objectClass=group)(objectCategory=group)(name=$Name))"
    $searcher.PageSize = 1000

    $searcher.PropertiesToLoad.AddRange(@('name', 'distinguishedName'))

    $searcher.FindAll() | ForEach-Object {
        [PSCustomObject]@{
            Name = $_.Properties['name'][0]
            DistinguishedName = $_.Properties['distinguishedName'][0]
        }
    }
}

function Get-ADSIGroupMember {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [String]$DistinguishedName,

        [Switch]$Recursive
    )

    process {
        if ($Recursive) {
            $LdapFilter = "(memberOf:1.2.840.113556.1.4.1941:=$DistinguishedName)"
        } else {
            $LdapFilter = "(memberOf=$DistinguishedName)"
        }
        
        $searcher = [ADSISearcher]$LdapFilter
        $searcher.PageSize = 1000

        $searcher.PropertiesToLoad.AddRange(@('name', 'distinguishedName'))

        $searcher.FindAll() | ForEach-Object {
            [PSCustomObject]@{
                Name = $_.Properties['name'][0]
                DistinguishedName = $_.Properties['distinguishedName'][0]
            }
        }
    }
}

Get-ADSIGroup -Name * | Select-Object Name, @{n='Members';e={ $_ | Get-ADSIGroupMember -Recursive | Select-Object -ExpandProperty Name }}

Open in new window

Author

Commented:
Thanks Chris I'll check this out I'll close the question and award the marks, you really have done a lot more than the question asked for so thanks again (using PS 5.0)
Unlock the solution to this question.
Thanks for using Experts Exchange.

Please provide your email to receive a sample view!

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.