Wednesday, February 23, 2011

Export Manager's Nested Direct Reports

In the code sample below, I create an export in two flavors for the nested direct reports of managers. By taking a text file listing the e-mail addresses of user objects in a forest, this code proceeds to drill recursively through the mutli-valued string directReports attribute of the object until it reaches a nested user object with that attribute set to null. After obtaining all the distinguished names, the code proceeds to perform LDAP lookups to obtain relevant attribute data. At the conclusion, it saves two files, a comma separated values text file with all the relevant attributes and an HTML file with a subset of those attributes.

The concept for this script was done before PowerShell V2 was released and provided better typing including 64 bit integers. Like in my older Perl and VBScript code dealing with Active Directory attributes that stored 64 bit integers like accountExpires, it required manipulating the return data in order to perform comparisons. The code below contains a Function written by Adam Weigert which has saved me the time translating the same conversion I've done before in Perl and VBScript. A link to his blog entry on the topic is commented in the code for your review.
param([string]$file)

Function Get-DirectReports($objectDn, $distinguishedNames) {
 $distinguishedNames += $objectDn
 $managerObject = Get-ActiveDirectoryObject $objectDn
 if($managerObject.directReports.count -gt 0) {
  foreach($directReport in $managerObject.directReports) {
   $distinguishedNames = Get-DirectReports $directReport $distinguishedNames
  }
 }
 return $distinguishedNames
}
Function Read-UserStatus($userFlags) {
 if ($userFlags -band 0x0002) {
  $enabled = $false
    } else {
  $enabled = $true
 } 
 return $enabled
}
Function Convert-ADSLargeInteger([object]$adsLargeInteger) { # http://weblogs.asp.net/adweigert/archive/2007/03/23/powershell-convert-active-directory-iadslargeinteger-to-system-int64.aspx
 $highPart = $adsLargeInteger.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)
 $lowPart  = $adsLargeInteger.GetType().InvokeMember("LowPart",  [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)

 $bytes = [System.BitConverter]::GetBytes($highPart)
 $temp   = [System.Byte[]]@(0,0,0,0,0,0,0,0)
 [System.Array]::Copy($bytes, 0, $temp, 4, 4)
 $highPart = [System.BitConverter]::ToInt64($temp, 0)

 $bytes = [System.BitConverter]::GetBytes($lowPart)
 $lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
 
 return $lowPart + $highPart
}
Function Get-LocalDomainController($objectDomain) {
 return ([System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()).Servers | Where-Object { $_.Domain.Name -eq $objectDomain } | ForEach-Object { $_.Name } | Select-Object -first 1
}
  
Function Get-ObjectADDomain($distinguishedName) {
 return ((($distinguishedName -replace "(.*?)DC=(.*)",'$2') -replace "DC=","") -replace ",",".")
}
  
Function Get-ActiveDirectoryObject($distinguishedName) {
 return [ADSI]("LDAP://" + (Get-LocalDomainController (Get-ObjectADDomain $distinguishedName)) + "/" + ($distinguishedName -replace "/","\/"))
}
#--------------------------------------------------------------------------------------------------#
Set-Variable -name forestRootDn -option Constant -value ([ADSI]("LDAP://" + (([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).name) + "/rootDSE")).defaultNamingContext
#--------------------------------------------------------------------------------------------------#
if(![bool]$file) {
 Write-Host "You are missing the `"-file`" command line argument" -foregroundColor Red
 Write-Host ""
 Write-Host "This is the file that contains the list of employee e-mail addresses"
 Write-Host ""
 while((![bool]$file)) {
  $file = Read-Host -prompt "`tFile"
 }
 Write-Host ""
}
if(!(Test-Path -path $file)) {
 Write-Host "Unable to locate: $file" -foregroundColor Red
 Write-Host ""
 Write-Host "Please review the correct path"
 Write-Host ""
 while(!(Test-Path -path $file)) {
  $file = Read-Host -prompt "`tFile"
 }
 Write-Host ""
}
   
$answer = $null
while($answer -ne "Y" -and $answer -ne "N") {
 $answer = Read-Host -prompt "Are you sure you want to use $file (Y/N)?"
}
   
if($answer -eq "N") {
 Write-Host ""
 Write-Host "Quiting..." -foregroundColor Yellow
 exit
}
 
$outputFile = (((Get-Item -path $file).name).Replace(((Get-Item -path $file).extension),".csv"))
 
$managers = Get-Content -path $file

$objectConnection = New-Object -comObject "ADODB.Connection"
$objectCommand = New-Object -comObject "ADODB.Command"
$objectConnection.Open("Provider=ADsDSOObject;")
$objectCommand.ActiveConnection = $objectConnection

$distinguishedNames = @()
$users = @()

foreach($manager in $managers) {

 $ldapBase = "GC://$forestRootDn"
 $ldapAttr = "distinguishedName"
 $ldapScope = "subtree"
 $ldapFilter = "(&(objectClass=user)(ProxyAddresses=smtp:$manager))"
 $ldapQuery= "<$ldapBase>;$ldapFilter;$ldapAttr;$ldapScope"
 $objectCommand.CommandText = $ldapQuery
 $objectRecordSet = $objectCommand.Execute()

 while(!$objectRecordSet.EOF) {
  $distinguishedNames = Get-DirectReports $objectRecordSet.Fields.Item('distinguishedName').Value $distinguishedNames
  $objectRecordSet.MoveNext()
 }
}

foreach($distinguishedName in $distinguishedNames) {
 $userObject = Get-ActiveDirectoryObject $distinguishedName

 if($userObject.objectClass -eq "User") {
  $accountExpires = (Convert-ADSLargeInteger $userObject.accountExpires[0])

  if($accountExpires -ne 0 -and $accountExpires -ne 9223372036854775807) {
   $account_expiration = (Get-Date ($userObject.psbase.invokeget("AccountExpirationDate")) -Format d)
  } else {
   $account_expiration = "N/A"
  }
 
  if($userObject.manager) {
   $manager_object = Get-ActiveDirectoryObject $userObject.manager
   if($manager_object.displayName) {
    $manager = ($manager_object.displayName).ToString()
   } else {
    $manager = ($manager_object.name).ToString()
   }
  } else {
   $manager = "N/A"
  }
  
  $user = New-Object -typeName PSObject
  Add-Member -inputObject $user -type NoteProperty -name "domain" -value ((Get-ObjectADDomain $userObject.distinguishedName).Split(".")[0]).ToUpper()
  Add-Member -inputObject $user -type NoteProperty -name "sAMAccountName" -value (($userObject.sAMAccountName).ToString()).ToLower()
  Add-Member -inputObject $user -type NoteProperty -name "givenName" -value ($userObject.givenName).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "sn" -value ($userObject.sn).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "displayName" -value ($userObject.displayName).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "mail" -value ($userObject.mail).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "title" -value ($userObject.title).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "department" -value ($userObject.department).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "manager" -value $manager
  Add-Member -inputObject $user -type NoteProperty -name "streetAddress" -value (($userObject.streetAddress).ToString() -Replace "`r`n",", ")
  Add-Member -inputObject $user -type NoteProperty -name "l" -value ($userObject.l).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "st" -value ($userObject.st).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "postalCode" -value ($userObject.postalCode).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "c" -value ($userObject.c).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "telephoneNumber" -value ($userObject.telephoneNumber).ToString()
  Add-Member -inputObject $user -type NoteProperty -name "accountExpiration" -value $account_expiration
  Add-Member -inputObject $user -type NoteProperty -name "enabled" -value (Read-UserStatus $userObject.userAccountControl[0])
  Add-Member -inputObject $user -type NoteProperty -name "organizationalUnit" -value ($userObject.psBase.parent.distinguishedName).ToString()
  $users += $user
 }
}

$users | Export-Csv -path $outputFile -noTypeInformation
$users | ConvertTo-Html -property givenName,sn,mail,telephoneNumber,title,department -title "Important Contacts" > "contacts.html"

No comments:

Post a Comment