PoshCode Archive  Artifact [d65a284bda]

Artifact d65a284bdae62dc12e3334d3877b81098d12b4eabcee59196e252b4829304d7d:

  • File Draw-Nested-Groups.ps1 — part of check-in [dbaca95ca5] at 2018-06-10 13:41:52 on branch trunk — Checking Security Group Nesting Strategy (ie: G.U.DL.), searching Circular Nesting or only graphically reporting Nested Security Groups, ADSecurityGroupMembers.ps1 helps on those tasks exploring group member property and generating a Graphviz file. (user: Axel Limousin size: 8381)

# encoding: utf-8
# api: powershell
# title: Draw Nested Groups
# description: Checking Security Group Nesting Strategy (ie: G.U.DL.), searching Circular Nesting or only graphically reporting Nested Security Groups, ADSecurityGroupMembers.ps1 helps on those tasks exploring group member property and generating a Graphviz file.
# version: 1.0
# type: script
# author: Axel Limousin
# license: CC0
# function: Dig-ADSecurityGroupMembers
# x-poshcode-id: 4497
# x-archived: 2016-09-07T03:24:08
# x-published: 2016-09-29T09:31:00
#
#
<#
#######
Syntax
#######

List-ADSecurityGroupMembers <ObjectDN> [-SC {<Onelevel> | <Subtree>}] [-RL <integer>] | 
Graph-ADSecurityGroupMembers > <FilePath>

#########
Examples
#########

#1 List-ADSecurityGroupMembers "OU=MyOU,DC=MyDom,DC=MyRoot" | 
Graph-ADSecurityGroupMembers > "$env:USERPROFILE\Desktop\ADSecurityGroupMembers.viz"

 same as

List-ADSecurityGroupMembers "OU=MyOU,DC=MyDom,DC=MyRoot"  -SC Subtree -RL 5 | 
Graph-ADSecurityGroupMembers > "$env:USERPROFILE\Desktop\ADSecurityGroupMembers.viz"

With default Scope (-SC) "Subtree" and default RecursionLevel (-RL) of 5, it draws Group Nesting for all Security Groups in "MyOU" and children OUs digging through 5 nesting levels.

 #2 List-ADSecurityGroupMembers "CN=MyGroup,OU=MyOU,DC=MyDom,DC=MyRoot" | 
Graph-ADSecurityGroupMembers > "$env:USERPROFILE\Desktop\ADSecurityGroupMembers.viz"

 It draws Group Nesting for the Security Groups "MyGroup" digging through 5 nesting levels (default RecursionLevel).

######
Usage
######

ADSecurityGroupMembers generates ADSecurityGroupMembers.viz file. Install Graphviz (www.graphviz.org) and associate "gvedit.exe" to .viz extension. With Graphiz GUI App, in "Graph>Settings" menu, you have the choice between "dot", "fdp", "sfdp", "circo", "neato", "osage" or "twopi" models to gen a .pdf, .svg or .png graphic. You should try "dot" model first (default). Domain Local Group graph cell border is colored in red, Global in green, Universal in cyan.

#################
Steps suggestion
#################

- Install Graphviz from www.graphviz.org
- Add the program path “C:\Program Files (x86)\Graphviz2.30\bin;” to Path environment variable
- In PowerShell console, verify that dot.exe is available just by executing dot –help
- In PowerShell console, paste script code and complement with code below:

 $Output = List-ADSecurityGroupMembers $ADObj | Graph-ADSecurityGroupMembers 
 $Output | Out-File -FilePath "$env:USERPROFILE\Desktop\NestedGroups.viz" -Encoding ASCII
 dot -Tjpg "$env:USERPROFILE\Desktop\NestedGroups.viz" -o "$env:USERPROFILE\Desktop\NestedGroups.jpg"
 del "$env:USERPROFILE\Desktop\NestedGroups.viz"
 Where $ADObj is a distinguishedName of a domain, an ou, a container or a group. The process can be long if you handle hundred of nested groups.

#######
Script
#######
 
=====================================================================================================
PURPOSE:	Graph Nested AD Security Groups by Member Property
http://gallery.technet.microsoft.com/scriptcenter/Graph-Nested-AD-Security-20aa3fcd

AUTHORS:	Axel Limousin
VERSION:	1.0 
DATE:	09/28/2013 

THANKS:	Thomas Corbiere
=====================================================================================================
#>

$ErrorActionPreference="SilentlyContinue"

function Dig-ADSecurityGroupMembers
{
	param
	(
		[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
		[ValidateScript({$_.GroupCategory -eq "Security"})]
		[Microsoft.ActiveDirectory.Management.ADGroup] $ADSGroup,
		
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[Alias("RL")]
		[Byte] $RecursionLevel = 5,
		
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[Alias("CL")]
		[Byte] $CurrentLevel = 0
    	)
 
    	if ($CurrentLevel -ge $RecursionLevel)
	{  
		return
    	}
 
	$ADSGroupMbrs = Get-ADGroupMember $ADSGroup.DistinguishedName | 
	Where-Object -FilterScript { (Get-ADGroup $_).GroupCategory -eq "Security" }

	$ADSGObj = New-Object PSObject -Property @{
		Name			= $ADSGroup.Name
		DistinguishedName	= $ADSGroup.DistinguishedName
    		GroupScope		= $ADSGroup.GroupScope
    		Members			= $ADSGroupMbrs | Select-Object distinguishedName
		}
	
	$ADSGObj
	
    	$ADSGroupMbrs | ForEach-Object -Process {
	
		Get-ADGroup $_.DistinguishedName | Dig-ADSecurityGroupMembers -RL $RecursionLevel -CL ($CurrentLevel+1)
	}
}

function List-ADSecurityGroupMembers
{
	param
	(
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[String] $ADDN = (Get-ADDomain).DistinguishedName,
		
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[Alias("SC")]
		[Microsoft.ActiveDirectory.Management.ADSearchScope] $Scope = "Subtree",
		
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[Alias("RL")]
		[Byte] $RecursionLevel = 5
    	)
 	
	$ADObject = Get-ADObject $ADDN -Properties groupType
	
	if ($ADObject.ObjectClass -eq "domainDNS" -or $ADObject.ObjectClass -eq "organizationalUnit" -or $ADObject.ObjectClass -eq "container")
	{ 
		$ADSGroupList = Get-ADGroup -Filter {GroupCategory -eq "Security"} -SearchBase $ADDN -SearchScope $Scope | 
		ForEach-Object -Process {
		
			$_ | Dig-ADSecurityGroupMembers -RL $RecursionLevel 
    		} 
    
		$ADSGroupList = $ADSGroupList | Sort-Object -Unique -Property DistinguishedName
    
		return $ADSGroupList 
	}
	
	elseif ($ADObject.ObjectClass -eq "group" -and $ADObject.groupType -like "-2*")
	{
		$ADSGroup = Get-ADGroup $ADObject.DistinguishedName
		
		$ADSGroupList = Dig-ADSecurityGroupMembers $ADSGroup -RL $RecursionLevel
		
		$ADSGroupList = $ADSGroupList | Sort-Object -Unique -Property DistinguishedName 
		
		return $ADSGroupList
	}
	
	else
	{
		Write-Warning -Message "Please select a Domain, an OU, a Container or a Security Group" 
	}
}

function Graph-ADSecurityGroupMembers
{
	param
	(
		[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
		[PSObject[]] $ADSGroupList,
		
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[String] $DomainLocalColor = "Red",
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[String] $GlobalColor      = "Green",
		[Parameter(ValueFromPipeline = $false, Mandatory = $false)]
		[String] $UniversalColor   = "Cyan"
    	)

	begin
	{
		$Groups = @()

		$GroupColor = {
            
			param
			(	
				[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
				[Microsoft.ActiveDirectory.Management.ADGroupScope] $GroupScope
			)

            		switch ($GroupScope)
			{
				"DomainLocal" { return $DomainLocalColor }
				"Global"      { return $GlobalColor }
				"Universal"   { return $UniversalColor }
            		}
        		}

        		$GraphNode = {
		
			param
			(
				[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
				[PSObject] $ADSGroup
			)
		
			$StrMark = $ADSGroup.DistinguishedName.IndexOf("DC=")
			$DomainName = $ADSGroup.DistinguishedName.Substring($StrMark)

            		"node_{0} [" -f [Array]::indexOf($Groups,$ADSGroup.DistinguishedName) | Write-Output
            		'label="{0}|{1}";' -f $ADSGroup.Name, $DomainName | Write-Output
            		"color={0};" -f (&$GroupColor $ADSGroup.GroupScope) | Write-Output
            		'shape = "record"' | Write-Output
            		"]" | Write-Output
        		}

        		"digraph g {" | Write-Output
        		'graph [rankdir = "LR"] ;' | Write-Output
        		"overlap = false ;" | Write-Output
    	}

 	process
	{
		foreach ($ADSGroup in $ADSGroupList)
		{
			if ([Array]::indexOf($Groups,$ADSGroup.DistinguishedName) -eq -1)
			{
				$Groups += $ADSGroup.DistinguishedName
            			&$GraphNode $ADSGroup
			}

			$ADSGroup.Members | 
			ForEach-Object -Process {
				
				if ([array]::indexOf($Groups,$_.distinguishedName) -eq -1)
				{
					$Groups += $_.distinguishedName
					$ChildGroup = Get-ADGroup $_.distinguishedName
            				&$GraphNode $ChildGroup
       				}

            			'node_{0} -> node_{1} [dir= "back"] ;' -f [Array]::indexOf($Groups,$ADSGroup.DistinguishedName), [Array]::indexOf($Groups,$_.distinguishedName) | 
				Write-Output
			}
		}
	}

	end
	{    
		"}" | Write-Output
	}
}