Organizational Awareness with PowerShell

Fri, Jun 24, 2011 5-minute read

One of the things that’s always unsettled me a bit in a big organization is keeping yourself informed about changes that you care about, even when those changes never end up making it through the grape vine. For example:

  • Promotions. Of course you want to congratulate them!
  • Transfers. Finding out that a team has moved to another manager, but no “re-org” mail to let you know.
  • Leaving the company. Finding out that a co-worker has left the company or division.

Naturally, PowerShell is here to save the day! I’ve been running a script I call “OrgDiff” as a scheduled task now for a long time – it came about organically, and I look forward to its results every Monday.

Here a couple of great examples of a few little bits of code (75 lines) coming together to make something of surprising utility.

param($User = 'mybigboss', $Limit = [Int32]::MaxValue, [Switch] $IncludeManager)
$depth = 0

## A list of all the domains that you want to search for
## a given user's alias. Try to organize them in order
## of popularity so that your script runs as quick as possible.
$domains = "LDAP://DC=domain1,DC=corp,DC=contoso,DC=com",
    "LDAP://DC=vendors,DC=corp,DC=contoso,DC=com",
    "LDAP://DC=international,DC=corp,DC=contoso,DC=com"
   
## Find all of the direct reports for a given user
function GetReports($username, $depth)
{
    if(-not $username) { return }
    if($depth -gt $Limit) { return }

    ## Go through all of the domains and try to find the user
    ## account in that domain
    foreach($domain in $domains)
    {
        $adsi = [ADSI] $domain

        $searcher = New-Object System.DirectoryServices.DirectorySearcher $adsi
        $searcher.Filter = "(&(objectClass=User)(mailnickname=$username))"
        $user = $searcher.FindOne()
       
        if($user) { break }
    }
   
    if(-not $user)
    {
        Write-Error "Could not find $username"
        return
    }

    ## If we want the report to include information about the user's manager,
    ## prepare that portion of the output.
    if($IncludeManager)
    {
        $manager = " - " +
            (($user.Properties.manager -split ',')[0] -replace 'CN=','')
    }
   
    ## Display the user's name, alias, title, and (optionally) manager -
    ## indented according to how deep we're investigating
    (" " * ($depth * 2)) + $user.Properties.name +
        " (" + $user.Properties.mailnickname +
        ") - $($user.Properties.title)$manager"
   
    $depth++

    ## If the user has direct reports, call this function again to show
    ## their portion of the organization.
    foreach($directReport in @($user.Properties.directreports))
    {
         $report = [ADSI] "GC://$directReport"
         GetReports ($report.mailNickname) $depth
    }
}

GetReports $user $depth

Normal output looks like this:

Some Body (somebody) - GENERAL MANAGER
  Jeffrey Snover (jsnover) - DISTINGUISHED ENGINEER
  Cool Person (coolbeans) - BUSINESS ADMINISTRATOR
  Super Developer (superdev) - DIRECTOR OF DEVELOPMENT
    Dev Manager (devmgr) - DEVELOPMENT MANAGER
      Dev Lead (devlead) _ DEVELOPMENT LEAD
    Dev Manager2 (devmgr2) - DEVELOPMENT MANAGER
(etc)

Now, if you run a weekly script to generate this report into a text file on your hard drive, you can use PowerShell to compare last week’s file with this week’s file. However, when we just compare files line-by-line, you’ll lose the indentation – and therefore the context of who their manager is. That leads to the need for the “-IncludeManager” switch, which generates output more like this:

Some Body (somebody) - GENERAL MANAGER
  Jeffrey Snover (jsnover) - DISTINGUISHED ENGINEER - Some Body
  Cool Person (coolbeans) - BUSINESS ADMINISTRATOR - Some Body
  Super Developer (superdev) - DIRECTOR OF DEVELOPMENT - Some Body
    Dev Manager (devmgr) - DEVELOPMENT MANAGER - Super Developer
      Dev Lead (devlead) _ DEVELOPMENT LEAD - Dev Manager
    Dev Manager2 (devmgr2) - DEVELOPMENT MANAGER - Dev Manager
(etc)

Here’s “Compare-OrgChart.ps1”, a script that starts to show useful differences between two files:

param($BeforePath, $AfterPath)

## Get the content from the first file and the second file, then sort them by name.
## This lets Compare-Object focus primarily on changes to people, and not be
## impacted by random orderings that might come back from Active Directory
$chart1 = Get-Content $BeforePath | % { $_.Trim() } | Sort-Object
$chart2 = Get-Content $AfterPath | % { $_.Trim() } | Sort-Object

## The format of the lines are:
## Super Developer (superdev) - DIRECTOR OF DEVELOPMENT
## So we tell Compare-Object to look for any changes, sort by only their name,
## and after that sort by "Before" then "After"
Compare-Object $chart1 $chart2 |
    Sort { ($_.InputObject -split '-')[0] },SideIndicator

Now, pretend the following happens:

  • Jeffrey gets promoted to Technical Fellow (congrats!)
  • “Dev Manager” leaves the group
  • “Dev Lead” moves under “Dev Manager2”

Here’s the org chart:

Some Body (somebody) - GENERAL MANAGER
  Jeffrey Snover (jsnover) - TECHNICAL FELLOW - Some Body
  Cool Person (coolbeans) - BUSINESS ADMINISTRATOR - Some Body
  Super Developer (superdev) - DIRECTOR OF DEVELOPMENT - Some Body
    Dev Manager2 (devmgr2) - DEVELOPMENT MANAGER - Dev Manager
      Dev Lead (devlead) _ DEVELOPMENT LEAD - Dev Manager2

(etc)

From there, we get this output:

PS C:\Users\Lee> Compare-OrgChart C:\temp\org_before.txt C:\temp\org_after.txt

InputObject                                                   SideIndicator
-----------                                                   -------------
Dev Lead (devlead) _ DEVELOPMENT LEAD - Dev Manager           <=
Dev Lead (devlead) _ DEVELOPMENT LEAD - Dev Manager2          =>
Dev Manager (devmgr) - DEVELOPMENT MANAGER - Super Developer  <=
Jeffrey Snover (jsnover) - DISTINGUISHED ENGINEER - Some Body <=
Jeffrey Snover (jsnover) - TECHNICAL FELLOW - Some Body       =>

That’s pretty cool! Now all we need is a little way to stitch it all together. Here’s a very simple “Update-OrgDiff.ps1” script. All it does is fetch the newest org chart, run the comparison, and email me the results.

$mailRecipient = "[email protected]"
$file = "OrgChart-{0}-{1}-{2}.txt" -f (Get-Date).Month,(Get-Date).Day,(Get-Date).Year
Get-OrgChart somebody -IncludeManager > "c:\temp\$file"
$results =  dir c:\temp\OrgChart-* | Sort LastWriteTime | Select -Last 2
$report = Compare-OrgChart $results[0].FullName $results[1].FullName |
    Format-Table -Auto | Out-String -Width 100

Send-MailMessage -To $mailRecipient -From $mailRecipient -Subject OrgDiff `
    -BodyAsHtml "<html><body><pre>$report</pre></body></html>" -SmtpServer smtphost

Set this up as a scheduled task, and you’re golden!