Showing posts with label User Object. Show all posts
Showing posts with label User Object. Show all posts

Wednesday, March 30, 2011

Creating Dynamic User Objects in Active Directory

Account management in Active Directory comes at a cost of time and effort for an Administrator. This is especially burdensome for objects that are only needed for a limited timeframe such as testing objects. Active Directory provides a low cost method for dealing with temporary objects through the dynamic object class which provides automatic garbage collection based on a time-to-live value in seconds. This benefit does come with several limitations:
  • The maximum TTL of a dynamic object is 31,556,926 seconds (1 year).
  • A deleted dynamic object due to its TTL expiring does not leave a tombstone behind.
  • All DCs holding replicas of dynamic objects must run on Windows Server 2003 or greater.
  • Dynamic entries with TTL values are supported in all partitions except the Configuration partition and Schema partition.
  • Active Directory Domain Services do not publish the optional dynamicSubtrees attribute, as described in the RFC 2589, in the root DSE object.
  • Dynamic entries are handled similar to non-dynamic entries when processing search, compare, add, delete, modify, and modifyDN operations.
  • There is no way to change a static entry into a dynamic entry and vice-versa.
  • A non-dynamic entry cannot be added subordinate to a dynamic entry.
In the code sample below, I take the advantage of the dynamic object class to create 50 test accounts that will expire and delete themselves from the forest on May 1, 2011 at 2:30 pm. The accounts created for a hypothetical finance application testing are placed in a specific OU, provided a unique sAMAccountName (tested to ensure this) and a pseudo-random password created for them. All this information is placed in a comma separated values text file so you can pass it over to your Quality Assurance team.

And remember, this script creates objects in Active Directory! Use at your own risk! I might have the best of intentions but my skill may betray you. Test, test and further test before implementing this code in a production environment.
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 "/","\/"))
}

Function Get-DomainDistinguishedName($domain) {
 return ("dc=" + $domain.Replace(".",",dc="))
}

Function Test-sAMAccountName($sAMAccountName,$domain) {
 $objectConnection = New-Object -comObject "ADODB.Connection"
 $objectConnection.Open("Provider=ADsDSOObject;")
 $objectCommand = New-Object -comObject "ADODB.Command"
 $objectCommand.ActiveConnection = $objectConnection

 $ldapBase = ("LDAP://" + (Get-LocalDomainController $domain) + "/" + (Get-DomainDistinguishedName $domain))
 $ldapAttr = "sAMAccountName"
 $ldapScope = "subtree"
 $ldapFilter = "(&(objectClass=user)(sAMAccountName=$sAMAccountName))"
 $ldapQuery= "<$ldapBase>;$ldapFilter;$ldapAttr;$ldapScope"
 $objectCommand.CommandText = $ldapQuery
 $objectRecordSet = $objectCommand.Execute()
 if($objectRecordSet.RecordCount -gt 0) {
  $found = $true
 } else {
  $found = $false
 }
 return $found 
}
#--------------------------------------------------------------------------------------------------#
Set-Variable -name destinationOu -option Constant -value "ou=Finance Testing,dc=americas,dc=ad,dc=mycompany,dc=local"
Set-Variable -name totalNumberOfAccounts -option Constant -value 50
Set-Variable -name givenName -option Constant -value "Test"
Set-Variable -name sn -option Constant -value "Account"
Set-Variable -name passwordSize -option Constant -value 12
#--------------------------------------------------------------------------------------------------#
$randomNumber = New-Object System.Random
$accounts = @()
$endDate = "05/01/2011 14:30:00"
$entryTtlSeconds = [int]((Get-Date $endDate) - (Get-Date)).TotalSeconds
#######################################################################
# Or you can do a time span instead for the account time to live i.e.,
# $entryTtlSeconds = [int](New-TimeSpan -days 90).TotalSeconds 
# $entryTtlSeconds = [int](New-TimeSpan -minutes 60).TotalSeconds 
#######################################################################
if($entryTtlSeconds -gt 31556926) {
 Write-Host "Time-to-live greater than 1 year. Exiting!" -foregroundColor Red
 exit
}

$destinationOuObject = Get-ActiveDirectoryObject $destinationOu

if($destinationOuObject.distinguishedName -ne $destinationOu) {
 Write-Host "Unable to connect to $destinationOu. Exiting!" -foregroundColor Red
 exit
}

for($i = 1;$i -le $totalNumberOfAccounts;$i++) {
 $accountName = ($givenName + $sn + "$i")
 Write-Host "Creating $accountName..."
 
 if(Test-sAMAccountName $accountName (Get-ObjectADDomain $destinationOu)) {
  Write-Host "$accountName already exists, skipping..." -foregroundColor Yellow
  continue
 }
 
 $userObject = $destinationOuObject.Create("user","CN=$accountName")
 $userObject.PutEx(2,"objectClass",@("dynamicObject","user"))
 $userObject.Put("entryTTL",$entryTtlSeconds)
 $userObject.Put("sAMAccountName", $accountName) # Mandatory attribute
 $userObject.Put("givenName",$givenName)
 $userObject.Put("sn",($sn + "$i"))
 $userObject.Put("displayName",($givenName + " " + $sn + "$i"))
 $userObject.Put("description","Account Used for Application Testing")
 $userObject.Put("wWWHomePage","http://sharepointsite/projects/newfinancesystem")
 $userObject.Put("userPrincipalName",($accountName + "@" + (Get-ObjectADDomain $destinationOu)))
 $userObject.SetInfo()
 
 $password = ""
 for($x = 1;$x -le $passwordSize;$x++) {
  $password += [char]($randomNumber.Next(33,126))
 }
 
 $userObject.SetPassword($password)
 $userObject.Put("userAccountControl", 512)
 $userObject.SetInfo()

 $account = New-Object -typeName PSObject
 Add-Member -inputObject $account -type NoteProperty -name "domain" -value (Get-ObjectADDomain $destinationOu).Split(".")[0]
 Add-Member -inputObject $account -type NoteProperty -name "sAMAccountName" -value ($userObject.sAMAccountName).ToString()
 Add-Member -inputObject $account -type NoteProperty -name "givenName" -value ($userObject.givenName).ToString()
 Add-Member -inputObject $account -type NoteProperty -name "sn" -value ($userObject.sn).ToString()
 Add-Member -inputObject $account -type NoteProperty -name "userPrincipalName" -value ($userObject.userPrincipalName).ToString() 
 Add-Member -inputObject $account -type NoteProperty -name "password" -value $password
 $accounts += $account
}

$accounts | Export-Csv -path "$givenName $sn List.csv" -noTypeInformation