Organizational Awareness with PowerShell
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!