Posts RSS Comments RSS 117 Posts and 170 Comments till now

Phase 3 of S.DS.P “CSV Output” (as much as 21 sec faster!)

In the next phase I wanted to actually get some data back and display it. Because I just wanted to test performance I kept the test as close as possible. ADFind has the built in ability to output a csv file so I mimicked his output. I also added two properties to return name and sAMAccountName. For those interested, my next step will be to output objects instead of text (really the whole point.)

My expectation for this test was that ADFind would pull ahead due to the processing of each record, but I was pleasantly surprised when the script was actually faster in all environments except 700k Environment. I will let joe explain why his is slower.

Testing
- I ran each 10x in a Row (as joe Suggested) in its own environement
- ADFind I tested using CMD.exe and used ptime.exe for time measurements.
- Get-DSPObject I used Measure-Command and outputed only TotalSeconds

UPDATED!: I had several people suggest that I should have a consolidated view of the results. So here it is. I took the Average from each test and put into and Excel spreadsheet and then plotted on a chart.

Here is the screenshot (lower is better)
DSP vs ADFind Chart

400k objects in a Prod Quality VM running Win2008 RTM 64bit (local)
Average: ADFind = 90.45
Average: DSP = 68.79
Winner: DSP 21.66 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 70.455s
Execution time: 69.551s
Execution time: 68.466s
Execution time: 68.085s
Execution time: 68.611s
Execution time: 68.179s
Execution time: 68.837s
Execution time: 68.670s
Execution time: 69.105s
Execution time: 67.994s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 100.538 s
Execution time: 109.511 s
Execution time: 92.499 s
Execution time: 96.063 s
Execution time: 91.601 s
Execution time: 80.693 s
Execution time: 81.551 s
Execution time: 81.044 s
Execution time: 90.945 s
Execution time: 80.143 s

400k objects in a Prod Quality VM running Win2008 RTM 64bit (remote)
Average: ADFind = 77.18
Average: DSP = 64.23
Winner: DSP 13 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 67.065 s
Execution time: 63.871 s
Execution time: 63.330 s
Execution time: 63.027 s
Execution time: 62.630 s
Execution time: 64.692 s
Execution time: 64.451 s
Execution time: 64.450 s
Execution time: 64.594 s
Execution time: 64.219 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 77.280 s
Execution time: 77.616 s
Execution time: 77.304 s
Execution time: 76.899 s
Execution time: 77.426 s
Execution time: 76.773 s
Execution time: 76.699 s
Execution time: 77.445 s
Execution time: 77.463 s
Execution time: 76.912 s

700k objects on a Physical machine Win2k3 x86
Average: ADFind = 101.383
Average: DSP = 109.600
Winner: ADFind 8.21 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 111.088s
Execution time: 109.962s
Execution time: 110.112s
Execution time: 110.832s
Execution time: 110.167s
Execution time: 109.853s
Execution time: 109.757s
Execution time: 110.387s
Execution time: 109.070s
Execution time: 109.065s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 101.571 s
Execution time: 101.313 s
Execution time: 101.386 s
Execution time: 101.593 s
Execution time: 101.539 s
Execution time: 100.917 s
Execution time: 101.020 s
Execution time: 101.286 s
Execution time: 101.544 s
Execution time: 101.667 s

300k Objects on Physical Win2k3 x86 (Remote)
Average: ADFind = 49.90
Average: DSP = 44.44
Winner: DSP 5 secs Faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 44.782 s
Execution time: 44.316 s
Execution time: 44.473 s
Execution time: 44.322 s
Execution time: 44.380 s
Execution time: 44.440 s
Execution time: 44.394 s
Execution time: 44.544 s
Execution time: 44.428 s
Execution time: 44.247 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 51.983 s
Execution time: 51.937 s
Execution time: 51.810 s
Execution time: 49.142 s
Execution time: 48.913 s
Execution time: 49.073 s
Execution time: 48.765 s
Execution time: 49.125 s
Execution time: 49.110 s
Execution time: 49.105 s

200k Objects on my Server at Home (Win2k8 x64)
Average: ADFind = 35.50
Average: DSP = 31.74
Winner: DSP 4 secs Faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 31.887 s
Execution time: 31.888 s
Execution time: 31.044 s
Execution time: 30.746 s
Execution time: 31.549 s
Execution time: 31.601 s
Execution time: 31.168 s
Execution time: 31.528 s
Execution time: 31.481 s
Execution time: 31.269 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 37.305 s
Execution time: 34.815 s
Execution time: 34.275 s
Execution time: 34.193 s
Execution time: 39.116 s
Execution time: 34.634 s
Execution time: 39.265 s
Execution time: 33.686 s
Execution time: 33.793 s
Execution time: 33.917 s

Here is the script I used for the DSP Tests

  1. function Get-DSPObject {
  2.     Param(
  3.             $filter = "(objectclass=user)",
  4.             $base = ([ADSI]"").distinguishedName,
  5.             $Server,
  6.             [int]$pageSize = 1000,
  7.             [string[]]$props = @("1.1"),
  8.             [switch]$noHeader,
  9.             [switch]$csv,
  10.             [switch]$count
  11.         )
  12.        
  13.     [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
  14.        
  15.     [int]$pageCount = 0
  16.     [int]$objcount = 0
  17.    
  18.     if(!$server){$server = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=","" }
  19.    
  20.     $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
  21.     $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  22.    
  23.     $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$subtree,$props)  
  24.     $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  25.     $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  26.     $searchRequest.Controls.add($pagedRequest) | out-null
  27.     $searchRequest.Controls.Add($searchOptions) | out-null
  28.    
  29.     # Output Prep
  30.     if($props -notcontains "1.1")
  31.     {
  32.         $MyProps = @()
  33.         foreach($prop in $props){$MyProps += $prop.ToLower()}
  34.         if($csv)
  35.         {
  36.             if(!$noHeader)
  37.             {
  38.                 $header = "distinguishedName"
  39.                 foreach($prop in $props){$header += ",$prop"}
  40.                 $header
  41.             }
  42.         }
  43.         else
  44.         {
  45.             $MyUserObj = New-Object System.Object
  46.             $MyUserObj | Add-Member -name distinguishedName -MemberType "NoteProperty" -value $null
  47.             foreach($prop in $props){$MyUserObj | Add-Member -name $prop -MemberType "NoteProperty" -value $null}
  48.         }
  49.     }
  50.    
  51.     # Process Pages
  52.     while ($True)
  53.     {
  54.         # Increment the pageCount by 1
  55.         $pageCount++
  56.    
  57.         # Cast the directory response into a SearchResponse object
  58.         $searchResponse = $connection.SendRequest($searchRequest)
  59.    
  60.         # Display the retrieved page number and the number of directory entries in the retrieved page
  61.         # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  62.         $objcount += ($searchResponse.entries).count
  63.        
  64.         # Display the entries within this page
  65.         if(!$count)
  66.         {
  67.             if($props -notcontains "1.1")
  68.             {
  69.                 foreach($entry in $searchResponse.Entries)
  70.                 {
  71.                     if($csv)
  72.                     {
  73.                         $results = "`"{0}`"" -f $entry.distinguishedName
  74.                         foreach($prop in $MyProps)
  75.                         {
  76.                             $results += ",`"{0}`"" -f ($entry.Attributes[$prop][0])
  77.                         }
  78.                         $results
  79.                     }
  80.                     else
  81.                     {
  82.                         $MyUserObj.distinguishedName = $entry.distinguishedName
  83.                         foreach($prop in $MyProps)
  84.                         {
  85.                             $MyUserObj."$prop" = $null
  86.                             $MyUserObj."$prop" = $entry.Attributes[$prop][0]
  87.                         }
  88.                         $MyUserObj
  89.                     }
  90.                 }
  91.             }
  92.             else{$searchResponse.Entries | select distinguishedName}
  93.         }
  94.        
  95.         # if this is true, there are no more pages to request
  96.         if ($searchResponse.Controls[0].Cookie.Length -eq 0){if($count){$objcount};break}
  97.    
  98.         # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
  99.         # page of data in the send request and cast the directory control into a PageResultResponseControl object
  100.         $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
  101.     }
  102. }
  103.  
  104. foreach($i in (1..10))
  105. {
  106.     $time = Measure-Command { Get-DSPObject -prop "name","sAMAccountName" -csv }
  107.     "Execution time: {0:n3} s" -f $time.TotalSeconds
  108. }

My First Venture into S.DS.P and Powershell

There has be much debate and agony on the the slowness of System.DirectoryServices.DirectorySearcher Class. This has lead me down the path of playing with System.DirectoryServices.Protocols (aka s.ds.p.)

By far the best tool out there right now is ADFind by joe Richards. So I used this for my target (although I didnt expect to get close.)

Here is the Guide I used in my Journey:
Introduction to System.DirectoryServices.Protocols (S.DS.P)

I started by writing the script you will find below and tested in a domain with well over 700k users and one with 200K. I did the test three different times in each Domain changing the order so cache hits wouldn’t be an issue. I also use objectclass on all three so index wouldn’t be a factor. I should NOTE that my script ONLY returns the count ATM. I am going to add properties next.

As you will see by the test results below… I got pretty darn close to adfind.exe in regards to perf. I was quite impressed with S.DS.P. Now to be fair, this was just counting objects. I am sure adfind.exe will start sneaking ahead abit further when we start processing properties and such. Can’t wait to see! Understand these test were just to see what tests I should focus on. I will posting another entry on more extensive count test with just DSP Using 1.1 and Adfind.

Test 1 700K Users done Remote

DirectorySearcher : 125.9123257
ADFind : 46.3763349
DSP Using DN : 69.5628776
DSP Using 1.1 : 49.4458161

Test 2 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 46.4486472
DSP Using DN : 68.9255288
DSP Using 1.1 : 49.0780736

Test 3 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 47.9162918
DSP Using DN : 79.6885386
DSP Using 1.1 : 54.152966

Test 1 200K done Local

DirectorySearcher : 121.1063569
ADFind : 55.2775406
DSP Using DN : 67.897922
DSP Using 1.1 : 28.5441615

Test 2 200K done Local

DirectorySearcher : 80.0894455
ADFind : 23.558696
DSP Using DN : 54.3111576
DSP Using 1.1 : 42.3485998

Test 3 200K done Local

DirectorySearcher : 99.1125363
ADFind : 80.1497852
DSP Using DN : 64.3716824
DSP Using 1.1 : 64.1940421

Summary: adfind.exe was faster (by bout 4sec on Avg.) remotely and larger domain, but protocals was faster (by bout 8 sec on Avg.) local on smaller domain.
Remote

ADFind: 46.92
DSP Using 1.1: 50.89
DSP Using DN: 72.73
DirectorySearcher: 125.32

Local

DSP Using 1.1: 45.03
ADFind: 52.99
DSP Using DN: 62.19
DirectorySearcher: 100.10

Here the script I ran for the test and how I measured the commands. I am going to play with passing the stats control and see what the server says later.

  1. $SearcherExpression = @’
  2. $searcher = new-object System.DirectoryServices.DirectorySearcher([ADSI]"","(objectclass=user)",@("distinguishedName"))
  3. $searcher.pagesize = 1000
  4. $searcher.findall()
  5. ‘@
  6.  
  7. Write-Host "Test 1"
  8. Write-Host ("-"*40)
  9. $myresults1 = "" | select @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  10.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  11.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  12.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}}
  13. $myresults1 | fl
  14.  
  15. Write-Host "Test 2"
  16. Write-Host ("-"*40)
  17. $myresults2 = "" | select @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  18.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  19.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  20.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}}
  21.  
  22. $myresults2 | fl
  23.  
  24. Write-Host "Test 3"
  25. Write-Host ("-"*40)
  26. $myresults3 = "" | select @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  27.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  28.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  29.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}}
  30. $myresults3 | fl
  31.  
  32. $myresults1,$myresults2,$myresults3
  33.  

Here is what the output of that Script looks like

S.DS.P : MyTest.ps1 Output

Here is the System.DirectoryServices.Protocols Code

  1. [System.Reflection.assembly]::LoadWithPartialName("system.directoryservices.protocols") | Out-Null
  2. $domain = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=",""
  3. $DomainDN = "DC=" + $Domain -replace "\.",",DC="
  4. [int]$pageCount = 0
  5. [int]$pageSize = 1000
  6. [int]$count = 0
  7. $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
  8. $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  9. $filter = "(objectclass=user)"
  10. $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,@("1.1"))  
  11. $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  12. $searchRequest.Controls.add($pagedRequest) | out-null
  13. $searchOptions = new-object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  14. $searchRequest.Controls.Add($searchOptions) | out-null
  15.  
  16. while ($true)
  17. {
  18.     ## increment the pageCount by 1
  19.     $pageCount++
  20.     ## cast the directory response into a
  21.     ## SearchResponse object
  22.     $searchResponse = $connection.SendRequest($searchRequest)
  23.     ## verify support for this advanced search operation
  24.     if (($searchResponse.Controls.Length -lt 1) -or
  25.         !($searchResponse.Controls[0] -is [System.DirectoryServices.Protocols.PageResultResponseControl]))
  26.     {
  27.         Write-Host "The server cannot page the result set"
  28.         return;
  29.     }
  30.     ## cast the diretory control into
  31.     ## a PageResultResponseControl object.
  32.     $pageResponse = $searchResponse.Controls[0]
  33.     ## display the retrieved page number and the number of
  34.     ## directory entries in the retrieved page                    
  35.     #"Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  36.     $count += $searchResponse.entries.count
  37.     ## display the entries within this page
  38.     ## foreach($entry in $searchResponse.entries){$entry.DistinguishedName}
  39.     ## if this is true, there
  40.     ## are no more pages to request
  41.     if ($pageResponse.Cookie.Length -eq 0){write-Host $count;break}
  42.     ## set the cookie of the pageRequest equal to the cookie
  43.     ## of the pageResponse to request the next page of data
  44.     ## in the send request
  45.     $pagedRequest.Cookie = $pageResponse.Cookie
  46. }

Fun with Active Directory (Playing Around Series)

This is the first in a series of posts call “Playing Around Series.” This series will basically be demo Videos of different Snapins/.NET Classes and their use.

In this entry I run through creating a DirectoryEntry, DirectorySearcher, and using the System.DirectoryServices.ActiveDirectory.Domain Class.

Note: This is a fast run through. I STRONGLY recommend pausing and reading the Comments. Best Viewed Full Screen

Get the Flash Player to see this player.

Special Shout-out to JayKul and Jeffrey Snover for the Start-Demo script.
http://www.powershellcentral.com/scripts/302

Demo Text

  1. #
  2. # Lets start off by looking at DirectoryEntry
  3. #
  4. $DE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://CN=tstUsr101,OU=MyUsers,DC=corp,DC=lab")
  5. #
  6. # First lets see what we have access to
  7. #
  8. $DE | Get-Member
  9. #’
  10. # Hmmm.. doesn’t seem like much. OH WAIT! Remember Powershell abstracts the class… Lets add psbase
  11. #
  12. $DE.psbase | Get-Member
  13. #
  14. # Lets look at what properties are available.
  15. #
  16. $DE.psbase.Properties
  17. #
  18. # Thats more like it. You may also note that some AD properties are still missing.
  19. # That is because that LDAP doesnt return all the properties. For these you need to "GET" them.
  20. $DE.psbase.InvokeGet(‘msExchUMFaxID’)
  21. #
  22. # Using DirectoryEntry is fine if you know the DN of the object, but what if you need to search?
  23. # Lets look at System.DirectoryServices.DirectorySearcher
  24. #
  25. # The Searcher needs some info so put that in variables first
  26. #
  27. $root = [ADSI]""  ## This is using the Type Accelerator we spoke about earlier… This is Gets the base
  28. $filter = "(&(objectcategory=user))"
  29. #
  30. # Now Lets create the searcher
  31. #
  32. $searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
  33. #
  34. # That gets the searcher ready, but to execute we need to call findall() or findone()
  35. #
  36. $users = $searcher.findAll()
  37. #
  38. # Lets see what we got. We have alot so lets only pick the first 10
  39. #
  40. $users | select -first 10
  41. #
  42. # Tons of info, but notice that this is NOT the same as DirectoryEntry
  43. #
  44. $users | get-Member
  45. #
  46. # It still has the properties property, Lets look (but only the first 3)
  47. #
  48. $users | select -first 3 | %{$_.Properties}
  49. #
  50. # Finally Lets look at System.DirectoryDervices.ActiveDirectory.Domain
  51. #
  52. # We can use this to interactively browse around
  53. #
  54. [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
  55. #
  56. # Lets assign that to variable to play with
  57. #
  58. $domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
  59. $domain
  60. #
  61. # Lets see what this has to offer
  62. #
  63. $domain | get-member
  64. #
  65. # Tons of cool stuff here.
  66. #
  67. # We can find all domain controllers
  68. $domain.FindAllDomainControllers()
  69. #
  70. # We Can look at our Domain FSMO
  71. #
  72. $domain | ft PdcRoleOwner,RidRoleOwner,InfrastructureRoleOwner
  73. #
  74. # I can even step the tree and get my forest root
  75. #
  76. $forest = $domain.Forest
  77. $forest
  78. #
  79. # With our new found $forest object… what can do we do?
  80. #
  81. $forest | Get-Member
  82. #
  83. # WE can find all our GCs
  84. #
  85. $forest.FindAllGlobalCatalogs()
  86. #
  87. # We can look at the Forest Mode
  88. #
  89. $forest.ForestMode
  90. #
  91. # Look at the Forest FSMO
  92. #
  93. $forest | ft SchemaRoleOwner,NamingRoleOwner
  94. #
  95. # Even look at sites
  96. $forest.Sites
  97. #
  98. # We can go on forever and ever. If you would like we can revisit this later.
  99. #

Get/Set-ADACL (ACL and SDDLs for Active Directory!)

A friend had a need to get/set Active Directory ACLs. So I wrote these.

They will use [System.DirectoryServices.ActiveDirectoryAccessRule] objects or SDDLs strings.

Note: I put the .NET classes and MS Spec for SDDLs at the bottom. Dont miss it!

Get-ADACL.ps1

  1. # Get-ADACL.ps1
  2. Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
  3. function HelpMe{
  4.     Write-Host
  5.     Write-Host " Get-ADACL.ps1:" -fore Green
  6.     Write-Host "   Gets ACL object or SDDL for AD Object"
  7.     Write-Host
  8.     Write-Host " Parameters:" -fore Green
  9.     Write-Host "   -DNPath                : Parameter: DN of Object"
  10.     Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
  11.     Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
  12.     Write-Host "   -Help                  : [SWITCH]:  Displays This"
  13.     Write-Host
  14.     Write-Host " Examples:" -fore Green
  15.     Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
  16.     Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
  17.     Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
  18.     Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
  19.     Write-Host
  20. }
  21.  
  22. if(!$DNPath -or $help){HelpMe;return}
  23.  
  24. Write-Host
  25. if($verbose){$verbosepreference="continue"}
  26.  
  27. Write-Verbose " + Processing Object [$DNPath]"
  28. $DE = [ADSI]"LDAP://$DNPath"
  29.  
  30. Write-Verbose "   - Getting ACL"
  31. $acl = $DE.psbase.ObjectSecurity
  32. if($SDDL)
  33. {
  34.     Write-Verbose "   - Returning SDDL"
  35.     $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
  36. }
  37. else
  38. {
  39.     Write-Verbose "   - Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
  40.     $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
  41. }

Set-ADACL.ps1

  1. # Set-ADACL.ps1
  2. Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
  3. function HelpMe{
  4.     Write-Host
  5.     Write-Host " Set-ADACL.ps1:" -fore Green
  6.     Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
  7.     Write-Host
  8.     Write-Host " Parameters:" -fore Green
  9.     Write-Host "   -DNPath                : Parameter: DN of Object"
  10.     Write-Host "   -ACL                   : Parameter: ACL Object"
  11.     Write-Host "   -sddl                  : Parameter: SDDL String"
  12.     Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
  13.     Write-Host "   -Help                  : [SWITCH]:  Displays This"
  14.     Write-Host
  15.     Write-Host " Examples:" -fore Green
  16.     Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
  17.     Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
  18.     Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
  19.     Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
  20.     Write-Host
  21. }
  22.  
  23. if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}
  24.  
  25. Write-Host
  26. if($verbose){$verbosepreference="continue"}
  27. Write-Verbose " + Processing Object [$DNPath]"
  28.  
  29. $DE = [ADSI]"LDAP://$DNPath"
  30. if($sddl)
  31. {
  32.     Write-Verbose "   - Setting ACL using SDDL [$sddl]"
  33.     $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
  34. }
  35. else
  36. {
  37.     foreach($ace in $acl)
  38.     {
  39.         Write-Verbose "   - Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
  40.         $DE.psbase.ObjectSecurity.SetAccessRule($ace)
  41.     }
  42. }
  43. $DE.psbase.commitchanges()
  44. Write-Host

More Info
I used the following .NET Classes
System.DirectoryServices.DirectoryEntry
http://msdn2.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx
System.DirectoryServices.ActiveDirectoryAccessRule
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectoryaccessrule.aspx
System.DirectoryServices.ActiveDirectorySecurity
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurity.aspx
System.Security.AccessControl.AccessControlSections
http://msdn2.microsoft.com/en-us/library/system.security.accesscontrol.accesscontrolsections(vs.80).aspx

SDDL Info
MS: http://msdn2.microsoft.com/en-us/library/aa379567.aspx

Check Active Directory Latency

The other day I had a need to see how long it took for an object to be replicated to all the Domain Controllers in my Environment.

Here is the script I came up with. It does the following:
- Finds all Domain Controllers in the Domain (using .NET)
- Creates a contact object in a specified OU (Default is users container for the Domain)
- Gets the start Time
- Loops and Checks each DC for the object.
- Once all DCs have the object it gets End Time
- Outputs the results

Some extra features
- re-writes screen using $host.UI.RawUI.CursorPosition. No scrolling text :) - Outputs a custom object with Name and Time to Replicate (-table)

Parameters/Switches
-target: DC to create object on. (Default: it will find one for you)
-fqdn: Used to Find Domain Controllers (Default: Use current Domain)
-ou: DN of the path to create contact objects (Default Users Container)
-remove: If $true it removes the temp object (default is $true)
-table: switch that will return a table with the DC and Time it took to replicate (as output)

Here is the code:

  1. Param($target = (([ADSI]"LDAP://rootDSE").dnshostname),
  2.       $fqdn = (([ADSI]"").distinguishedname -replace "DC=","" -replace ",","."),
  3.       $ou = ("cn=users," + ([ADSI]"").distinguishedname),
  4.       $remove = $true,
  5.       [switch]$table
  6.       )
  7.  
  8. $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$fqdn)
  9. $dclist = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($context)
  10. if($table)
  11. {
  12.     $DCTable = @()
  13.     $myobj = "" | select Name,Time
  14.     $myobj.Name = ("$target [SOURCE]").ToUpper()
  15.     $myobj.Time = 0.00
  16.     $DCTable += $myobj
  17. }
  18.  
  19. $name = "rTest" + (Get-Date -f MMddyyHHmmss)
  20. Write-Host "`n  Creating Temp Contact Object [$name] on [$target]"
  21. $contact = ([ADSI]"LDAP://$target/$ou").Create("contact","cn=$Name")
  22. $contact.SetInfo()
  23. $dn = $contact.distinguishedname
  24. Write-Host "  Temp Contact Object [$dn] Created! `n"
  25.  
  26. $start = Get-Date
  27.  
  28. $i = 0
  29.  
  30. Write-Host "  Found [$($dclist.count)] Domain Controllers"
  31. $cont = $true
  32.  
  33. While($cont)
  34. {
  35.     $i++
  36.     $oldpos = $host.UI.RawUI.CursorPosition
  37.     Write-Host "  =========== Check $i ===========" -fore white
  38.     start-Sleep 1
  39.     $replicated = $true
  40.     foreach($dc in $dclist)
  41.     {
  42.         if($target -match $dc.Name){continue}
  43.         $object = [ADSI]"LDAP://$($dc.Name)/$dn"
  44.         if($object.name)
  45.         {
  46.             Write-Host "  - $($dc.Name.ToUpper()) Has Object [$dn]" (" "*5) -fore Green
  47.             if($table -and !($dctable | ?{$_.Name -match $dc.Name}))
  48.             {
  49.                 $myobj = "" | Select-Object Name,Time
  50.                 $myobj.Name = ($dc.Name).ToUpper()
  51.                 $myobj.Time = ("{0:n2}" -f ((Get-Date)-$start).TotalSeconds)
  52.                 $dctable += $myobj
  53.             }
  54.         }
  55.         else{Write-Host "  ! $($dc.Name.ToUpper()) Missing Object [$dn]" -fore Red;$replicated  = $false}
  56.     }
  57.     if($replicated){$cont = $false}else{$host.UI.RawUI.CursorPosition = $oldpos}
  58. }
  59.  
  60. $end = Get-Date
  61. $duration = "{0:n2}" -f ($end.Subtract($start).TotalSeconds)
  62. Write-Host "`n    Took $duration Seconds `n" -fore Yellow
  63.  
  64. if($remove)
  65. {
  66.     Write-Host "  Removing Test Object `n"
  67.     ([ADSI]"LDAP://$target/$ou").Delete("contact","cn=$Name")
  68. }
  69. if($table){$dctable | Sort-Object Time | Format-Table -auto}

AD Replication Metadata (when did that change?)

There was a discussion on the NG about determining when a user was disabled. The initial request was to determine this based on whenChanged, but I thought that could be invalid as you can easily change an account after it was disabled. I can not think of a way to be sure, but the best way I can think of is to use the replication metadata on the attribute userAccountControl (the second bit is what determines if its disabled or not.) While it is possible to change the useraccountcontrol after a user is disabled it is unlikely.

More info for UserAccountControl bits
http://support.microsoft.com/kb/305144

Of course the next question was how do you check the Replication Metadata for an attribute on and AD object?

Enter Get-ADObjectREplicationMetadata.ps1

This uses

System.DirectoryServices.ActiveDirectory.DirectoryContext
- http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectory.directorycontext.aspx
System.DirectoryServices.ActiveDirectory.DomainController
- http://msdn2.microsoft.com/en-gb/library/system.directoryservices.activedirectory.domaincontroller.aspx

  1. # Get-ADObjectREplicationMetadata.ps1
  2. # Brandon Shell (www.bsonposh.com)
  3. # Purpose: Get attribute(s) Replication Metadata from a Domain controller.
  4. Param($Domain,$objectDN,$property)
  5. # Sets Context to Domain for System.DirectoryServices.ActiveDirectory.DomainController
  6. $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$domain)
  7. # .NET Class that returns a Domain Controller for Specified Context
  8. $dc = [System.DirectoryServices.ActiveDirectory.DomainController]::findOne($context)
  9. # GetReplicationMetadata returns metadate from the DC for the DN specified.
  10. $meta = $dc.GetReplicationMetadata($objectDN)
  11. if($property){$meta | %{$_.$Property}}else{$meta}

This will return either all the metadata or just the metadata for a specific attribute. I should note that if you do not specify an attribute it returns all of them. You should expect to parse these as each attribute has a child object with the data in it.

All Attributes. The value can be found by .PropertyName

  1. PS# .\Get-ADObjectMetaData.ps1 ‘my.lab.domain’ ‘CN=TestUser,DC=my,dc=lab,dc=domain’
  2.  
  3. Name                           Value
  4. —-                           —–
  5. countrycode                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
  6. cn                             System.DirectoryServices.ActiveDirectory.AttributeMetadata
  7. mail                           System.DirectoryServices.ActiveDirectory.AttributeMetadata
  8. scriptpath                     System.DirectoryServices.ActiveDirectory.AttributeMetadata
  9. ntsecuritydescriptor           System.DirectoryServices.ActiveDirectory.AttributeMetadata
  10. accountexpires                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
  11. displayname                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
  12. profilepath                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
  13. primarygroupid                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
  14. unicodepwd                     System.DirectoryServices.ActiveDirectory.AttributeMetadata
  15. objectclass                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
  16. objectcategory                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
  17. instancetype                   System.DirectoryServices.ActiveDirectory.AttributeMetadata
  18. homedrive                      System.DirectoryServices.ActiveDirectory.AttributeMetadata
  19. samaccounttype                 System.DirectoryServices.ActiveDirectory.AttributeMetadata
  20. homedirectory                  System.DirectoryServices.ActiveDirectory.AttributeMetadata
  21. whencreated                    System.DirectoryServices.ActiveDirectory.AttributeMetadata
  22. useraccountcontrol