This is a PowerShell version 1.0 script to document the organizational structure specified by the manager and directReports attributes of Active Directory objects. On the "Organization" tab of the user properties dialog in the Active Directory Users and Computers MMC you can specify a manager for a user or contact. This assigns the distinguished name of the manager selected to the manager attribute of the user. This single-valued DN attribute is linked to the directReports multi-valued DN attribute of the corresponding manager.
For example, if you assign user "cn=Jim Smith" as the manager for users "cn=Sam Jones" and "cn=Sally Wilson", then the manager attributes of users "cn=Sam Jones" and "cn=Sally Wilson" will be the distinguished name of user "cn=Jim Smith". In addition, the system will add the distinguished names of "cn=Sam Jones" and "cn=Sally Wilson" to the directReports attribute of user "Jim Smith".
The manager attribute applies to objects of class user, contact, and computer. The directReports attribute applies to objects of class user, contact, computer, group, organizationalUnit, container, and domain. This means, for example, that you can assign the DN of a group as manager of a user. However, since ADUC does not provide for this, you would need to use a tool like ADSI Edit or a script to assign the group DN to the manager attribute of the user object. Once you do this, Active Directory will automatically update the directReports attribute of the group object. The system does not allow you to modify the directReports attribute directly.
This PowerShell script first finds all managers at the top of any organizational structure in your Active Directory. These are objects (possibly more than one) which have one or more values assigned to the directReports attribute, but no value assigned to the manager attribute. For each such object the program recursively documents the direct reports. The hierarchy of the organization is indicated by indenting. All objects reporting to any manager are indented below the manager.
The program will ignore any "circular" organizational structures. For example, if Jim reports to Bob, Bob reports to Frank, and Frant reports to Jim, we have a "circular" structure. Active Directory will allow you do to this, but it makes no sense in the real world. The script ignores this situation because none of the people involved is at the top of any organization hierarchy. That is, none has direct reports but no manager assigned. If you could also make Jim report to Richard, then Richard would be at the top of an organizational hierarchy. However, Active Directory would not allow this because Jim would then have two managers (Bob and Richard), and the manager attribute is single-valued.
This PowerShell script supports two optional parameters. One specifies the format of the output file created documenting the organization. You can specify -text or -html or -csv (comma delimited). The default is -text. Another optional parameter specifies the name attributes used in the output. If you specify -dn (the default), the output documents distinguished names. If you specify -name, the output documents the common names, with the sAMAccountName (pre-Windows 2000 names) in parentheses. Note that if any manager or direct report is a contact, the sAMAccountName will be missing.
The script generates a report in the current directory called Organization.txt if -text is specified, Organization.htm if -html is specified, or Organization.csv if -csv is specified. When the program completes the resulting file is displayed to the user. By default, the *.txt file will be displayed in Notepad, the *.htm file will be displayed in your default browser, and the *.csv file will be displayed in Excel (if installed on the local computer).
PowerShell
Edit|Remove
# ADOrganization.ps1 
# PowerShell program to document the organization specified by 
# the manager and directReports attributes in Active Directory. 
# Author: Richard L. Mueller 
# PowerShell Version 1.0 
# October 152014 
# Revised February 272015 
 
Trap {"Error: $_"; Break;} 
 
Function Get-Reports($ReportDN, $ManagerDN, $ManagerName, $Offset) 
{ 
    # Recursive function to document the organization. 
    # The first time this function is called it considers managers at 
    # the top of the organization hierarchy. These are objects with 
    # direct reports but no manager. 
    If ($ReportDN -eq "Top") 
    { 
        # Filter on objects with no manager and at least one direct report. 
        $Filter = "(&(!manager=*)(directReports=*))" 
    } 
    Else 
    { 
        # The function has been called recursively to deal with a direct report. 
        # Output the object that reports to the previous manager. 
        If ($Output  -eq "DN") 
        { 
            Switch ($Format) 
            { 
                "HTML" {"<li>$ReportDN</li>" | Out-File -FilePath $File -Append} 
                "Text" { 
                            # Direct reports are indented beneath their manager. 
                            "$Offset$ReportDN" | Out-File -FilePath $File -Append 
                            # Indent the next level of the hierarchy 4 spaces. 
                            $Offset = "$Offset    " 
                       } 
                "CSV" {"""$ManagerDN"",""$ReportDN""" | Out-File -FilePath $File -Append} 
            } 
        } 
        If ($Output -eq "Name") 
        { 
            # Escape any forward slash characters with the backslash escape character. 
            $ReportDN = $ReportDN.Replace("/""\/") 
            # Use ADSI to bind to the direct report object and retrieve names. 
            $Object = [ADSI]"LDAP://$ReportDN" 
            $Name = $Object.name 
            $NTName = $Object.sAMAccountName 
            $ReportName = "$Name ($NTName)" 
            Switch ($Format) 
            { 
                "HTML" {"<li>$ReportName</li>" | Out-File -FilePath $File -Append} 
                "Text" { 
                            # Direct reports are indented beneath their manager. 
                            "$Offset$ReportName" | Out-File -FilePath $File -Append 
                            # Indent the next level of the hierarchy 4 spaces. 
                            $Offset = "$Offset    " 
                       } 
                "CSV" {"""$ManagerName"",""$ReportName""" | Out-File -FilePath $File -Append} 
            } 
        } 
        # Search for all objects that report to this object. 
        $Filter = "(manager=$ReportDN)" 
    } 
 
    # Run the query. 
    $Searcher.Filter = $Filter 
 
    $Results = $Searcher.FindAll() 
    If ($Results.Count -gt 0) 
    { 
        If ($Format -eq "HTML") 
        { 
            "<ul>"  | Out-File -FilePath $File -Append 
        } 
        ForEach ($Result In $Results) 
        { 
            # Output the object. 
            $DN = $Result.Properties.Item("distinguishedName") 
            If ($Output -eq "DN") 
            { 
                Switch ($Format) 
                { 
                    "HTML" {$Line = "<li>$DN</li>"} 
                    "Text" {$Line = "$Offset$DN"} 
                    "CSV" 
                    { 
                        # When $ReportDN is "Top", there is no manager. 
                        # Enclose DN values in quotes. There will be embedded commas. 
                        If ($ReportDN -ne "Top"{$Line = """$ReportDN"",""$DN"""} 
                        Else {$Line = ",""$DN"""} 
                    } 
                } 
            } 
            If ($Output -eq "Name") 
            { 
                # Retrieve name and sAMAccountName. 
                $Name = $Result.Properties.Item("name") 
                $NTName = $Result.Properties.Item("sAMAccountName") 
                Switch ($Format) 
                { 
                    "HTML" {$Line = "<li>$Name ($NTName)</li>"} 
                    "Text" {$Line = "$Offset$Name ($NTName)"} 
                    "CSV" 
                    { 
                        # When $ReportDN is "Top", there is no manager. 
                        # Enclose values in quotes. There could be embedded commas. 
                        If ($ReportDN -ne "Top") 
                        {$Line = """$ReportName"",""$Name ($NTName"""} 
                        Else {$Line = ",""$DN"""} 
                    } 
                } 
            } 
            $Line | Out-File -FilePath $File -Append 
 
            # Retrieve any direct reports for this object. 
            $Reports = $Result.Properties.Item("directReports") 
            If ($Reports.Count -gt 0) 
            { 
                If ($Format -eq "HTML") 
                { 
                    "<ul>" | Out-File -FilePath $File -Append 
                } 
                ForEach ($Report In $Reports) 
                { 
                    # Recursively call this function for each direct report. 
                    # Increase any indenting by 4 more spaces. 
                    Get-Reports $Report $DN "$Name ($NTName)" "$Offset    " 
                } 
                If ($Format -eq "HTML") 
                { 
                    "</ul>" | Out-File -FilePath $File -Append 
                } 
            } 
        } 
        If ($Format -eq "HTML") 
        { 
            "</ul>"  | Out-File -FilePath $File -Append 
        } 
    } 
} 
 
Function GetHelp() 
{ 
    "The ADOrganization.ps1 script documents the organization hierarchy" 
    "specified by the manager and directReports attributes in Active Directory." 
    "Optional parameters:" 
    "  One of the following to specify the output format (default is -text):" 
    "    -html      Output in HTML format to be displayed in a browser" 
    "    -csv       Output in CSV format to be displayed in a spreadsheet" 
    "    -text      Output in text format to be displayed in notepad" 
    "    -help      Display this help information" 
    "  One of the following to specify the attributes to output (default is -dn):" 
    "    -dn        Display distinguished names" 
    "    -name      Display common names and sAMAccountNames" 
    "Creates output file ""Organization.htm"" or ""Organization.csv"" or" 
    """Organization.txt"" in the current directory, depending on the format." 
} 
 
# Check optional parameters indicating output format. 
# The default is "Text" format and output "DN" distinguished names. 
$Format = "Text" 
$Output = "DN" 
$Abort = $False 
 
# Process any optional parameters. 
If ($Args.Count -gt 2{ 
    "Error: Wrong number of parameters." 
    GetHelp 
    Break 
} 
 
If ($Args.Count -gt 0{ 
    ForEach ($Arg In $Args) 
    { 
        Switch ($Arg.ToLower()) 
        { 
            "-help" 
            { 
                GetHelp 
                $Abort = $True 
            } 
            "-html" {$Format = "HTML"} 
            "-csv" {$Format = "CSV"} 
            "-text" {$Format = "Text"} 
            "-dn" {$Output = "DN"} 
            "-name" {$Output = "Name"} 
            Default 
            { 
                "Error: Invalid paramter" 
                GetHelp 
                $Abort = $True 
            } 
        } 
    } 
} 
# Abort the script if invalid parameter found. 
If ($Abort -eq $True){Break} 
 
# Specify the output file, with the appropriate extension. 
Switch ($Format) 
{ 
    "HTML" {$File = ".\ADOrganization.htm"} 
    "Text" {$File = ".\ADOrganization.txt"} 
    "CSV" {$File = ".\ADOrganization.csv"} 
} 
 
# Setup the DirectorySearcher object. 
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() 
$Domain = [ADSI]"LDAP://$D" 
$Searcher = New-Object System.DirectoryServices.DirectorySearcher 
$Searcher.PageSize = 200 
$Searcher.SearchScope = "subtree" 
$Searcher.PropertiesToLoad.Add("distinguishedName") > $Null 
$Searcher.PropertiesToLoad.Add("directReports") > $Null 
If ($Output -eq "Name"{ 
    $Searcher.PropertiesToLoad.Add("name") > $Null 
    $Searcher.PropertiesToLoad.Add("sAMAccountName") > $Null 
} 
$Searcher.SearchRoot = "LDAP://" + $Domain.distinguishedName 
 
# Output header lines. 
If ($Format -eq "HTML"{ 
    "<div style=""font-family:Courier New,Courier"">" | Out-File -FilePath $File 
    "<h1>Organization: $D</h1>" | Out-File -FilePath $File -Append 
} 
If ($Format -eq "Text"{ 
    "Organization: $D"  | Out-File -FilePath $File 
} 
If ($Format -eq "CSV"{ 
    "Manager,Direct Report" | Out-File -FilePath $File -Encoding ASCII 
} 
 
# Retrieve organization hierarchy, starting from the top. 
Get-Reports "Top" "" "" "" 
 
# Output final tag for HTML format. 
If ($Format -eq "HTML"{ 
    "</div>" | Out-File -FilePath $File -Append 
} 
 
# Display the output file, in the application appropriate for the file extension. 
Invoke-Expression $File