Question

Basically, in our environment, we have a ton of security groups. Security groups that are nested within other groups etc. So it is a real PITA to find out why a setting is applying to a user, because of one of the nested groups they may or may not be a part of

For e.g. If you add a user to group X, they suddenly have a published application in Citrix. Citrix is configured for security group Y. Attempting to find the link between X and Y is very time consuming but can be automated.

I want to create a script where, you enter a user and the end security group (group Y from above), and the script outputs the intermediary groups that connects the user to the final group. If this makes sense?

Something like this:

function get-grouprelationship($username, $knownsecuritygroup)
{
    $getallgroups = get-adgroupmember $knownsecuritygroup | where-object {$_.ObjectClass -eq "Group" | select-object SamAccountName | foreach-object {get-adgroupmember $_.SamAccountName}
}

(The above variable takes your group, and loops through all members of that group, printing their members)

$usergroups = (get-aduser -identity $username -Properties memberof | select-object memberof).memberof

(The above gets all groups that a user is in)

$usergroups1 = $usergroups.split(",")
$usergroups2 = $usergroups1[0]
$usergroups3 = $usergroups2.substring(3)

(the above formats the text nicely)

if ($usergroups3 -contains $groupname){write-host "$username is directly in $groupname}

From here, I am quite stuck as I basically need to nest multiple for loops, depending on how many groups are in each group. Then do a condition check that

if ($groupname -eq $currentgroup){write-host "connected by $groupname and $currentgroup}

I'm also stuck with the $getallgroups variable, because it only checks 1 level down. It would then need another foreach loop inside that, which would need another one inside that etc.

Having no prior coding experience, I am really struggling to get my head around an easy way to achieve my goal.

EDIT:

I found this script here - script. Below basically works, except it is way to verbose:

import-module activedirectory
$username = read-host "What's their username?"
Function RecurseUsersInGroup {
    Param ([string]$object = "", [int]$level = 0)
    $indent = "-" * $level

    $x = Get-ADObject -Identity $object -Properties SamAccountName

    if ($x.ObjectClass -eq "group") {
        Write-Host "# $($x.SamAccountName)"

        $y = Get-ADGroup -Identity $object -Properties Members

        $y.Members | %{
            $o = Get-ADObject -Identity $_ -Properties SamAccountName

            if ($o.ObjectClass -eq "user" -and $o.SamAccountName -eq $username) {
                Write-Host "-> $($o.SamAccountName)"
            } elseif ($o.ObjectClass -eq "group") {
                RecurseUsersInGroup $o.DistinguishedName ($level + 1)
            }
        }
    } else {
        Write-Host "$($object) is not a group, it is a $($x.ObjectClass)"
    }
}
$thegroup = read-host "What's the Group?"
RecurseUsersInGroup (get-adgroup $thegroup).DistinguishedName

That works fine, but appears to output every security group, oppose to the connecting ones. Certainly a step in the right direction though! If I find the source I will post the credit as well.

Was it helpful?

Solution

The following version is not less verbose (could probably be written a lot more terse, but I'm hoping the script is at least semi-readable), but it does a search for the group and returns the Active Directory group objects for each group along the branch in which the group was found.

function Get-GroupConnection
{
    [CmdletBinding()]
    PARAM (
        $Username,
        $GroupName
    )

    $User = Get-AdUser -Identity $Username -Properties MemberOf
    if (-Not ($User))
    {
        return;
    }

    $SearchedGroups = @()

    function Find-GroupBranches
    {
        [CmdletBinding()]
        PARAM (
            $GroupNameList,
            $SearchForGroupName
        )

        $ADGroups = $GroupNameList | Foreach { Get-ADGroup -Identity $_ -Properties MemberOf }

        foreach($group in $ADGroups)
        {
            Write-Verbose "Testing if either '$($Group.SamAccountName)' or '$($Group.DistinguishedName)' are equal to '$SearchForGroupName'"
            if ($Group.SamAccountName -eq $SearchForGroupName -OR $Group.DistinguishedName -eq $SearchForGroupName)
            {
                Write-Verbose "Found $($Group.DistinguishedName)"
                Write-Output $Group
                return
            }
        }

        Write-Verbose "No match in current collection, checking children"
        foreach ($currentGroup in $ADGroups)
        {
            if ($SearchedGroups -Contains $currentGroup.DistinguishedName)
            {
                Write-Verbose "Already checked children of '$($currentGroup.DistinguishedName)', ignoring it to avoid endless loops"
                continue
            }
            $SearchedGroups += $currentGroup.DistinguishedName

            if ($currentGroup.MemberOf)
            {
                Write-Verbose "Checking groups which $($currentGroup.DistinguishedName) is member of"

                $foundGroupInTree = Find-GroupBranches -GroupNameList $currentGroup.MemberOf -SearchForGroupName $SearchForGroupName
                if ($foundGroupInTree)
                {
                    Write-Output $currentGroup
                    Write-Output $foundGroupInTree
                    break
                }
            }
            else
            {
                Write-Verbose "$($currentGroup.DistinguishedName) is not member of any group, branch ignored"
            }
        }
    }

    Write-Verbose "Searching immediate group membership"
    Find-GroupBranches -GroupNameList $User.MemberOf -SearchForGroupName $GroupName
}

Get-GroupConnection -Username MyUser -GroupName SubSubGroup -Verbose

A description of how it searches follows.

Given the following Active Directory structure:

MyUser
    Domain Admins
        AnotherSubGroup
    Other Group
    DirectMemberGroup
        Domain Admins (same group as MyUser is direct member of, above)
            AnotherSubGroup (which is of course the same as above too)
        SubGroup
            SubSubGroup
    Some Other Group

If we search for the connection between MyUser and 'SubSubGroup' the script would search first the direct membership of the MyUser user, i.e. 'Domain Admins', 'Other Group', 'DirectMemberGroup' and 'Some Other Group'. None of these match the 'SubSubGroup' we search for, so it starts checking 'child'groups.

'Domain Admins' is member of 'AnotherSubGroup', but that does not match 'SubSubGroup'. 'AnotherSubGroup' is not member of any group, so that branch is ignored.

'Other Group' is not member of any group, so that branch are ignored.

'DirectMemberGroup' is member of other groups, so it iterates through those groups. It has already checked 'Domain Admins' for children, so that group is skipped to avoid getting stuck in a circular search. Therefore it check 'SubGroup'.

'SubGroup' does not match 'SubSubGroup' so it check the groups which 'SubGroup' is member of. 'SubGroup' is member of 'SubSubGroup', so it checks that group.

'SubSubGroup' does match 'SubSubGroup' and will therefore be chosen as a match.

In the above example, the output group object will be branches which lead to the 'SubSubGroup' group, in the following order:

DirectMemberGroup
SubGroup
SubSubGroup

Observe that this method will return the first connection it finds between the user and the group. If, for example, the 'Some Other Group' group would also be a member of 'SubSubGroup' this would not change the output, nor the search process, mentioned above.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top