Posts RSS Comments RSS 117 Posts and 170 Comments till now

What Domain Controllers were IFM’d and from whom.

This is to provide Context to joe’s post and a Powershell script I used to get the values to determine if our DC’s were Installed from Media.

I manage a very large Active Directory (~380k users) which causes a predictably large DIT (click here to Learn about DITs.) We recently encountered one specific DC exhibiting odd behavior: (LSASS) was churning an unusually large amount of disk IO.  This problem was quickly resolved and is of little interest but resulted in another good-to-know fact.  As we begun our investigation, one of the first things we looked at was how large the DIT was; roughly 4.8GB for this particular DC.  Dean (who consults for us full-time) and I first tried to determine if the churn was a product of our environment combined with DIT Bloat [1] or some kind of other whacky effect/limitation.  Dean was told that it was a newly-born DC and initially discounted DIT bloat since AD does NOT replicate white space.  Just a few minutes later, I recollected and piped up that I had promoted this particular DC via IFM [2] – this made a huge difference to our earlier and incorrect (albeit brief) conclusion: during an IFM, the DIT of the originating DC (white-space and all) is literally copied bit for bit and serves as the template from which the new database is built (necessary modifications acknowledged.)

This brings us to the moral of my story: how do we determine if a DC was promoted via IFM – a great question and an even better answer.  joe’s® post (HERE) was the result of a lengthy and possibly pointless [3] IM between him and Dean.  The short answer is there are some things that DCs create locally during replication causing the metadata to indicate that the local DC was the originating writer of certain attributes.  As a result, it is likely that a DC that lists itself as the originator of the RDN attribute for an object that existed prior to this DC’s promotion, (RDN attribute equating to CN, OU or DC / CN for say the Users container) was either the first DC to host this partition (or, typically, the DC that created this domain) or was promoted via replication (either way, not IFM’d.)  If, however, the attribute’s metadata indicates that another DC originated the RDN attribute, we can assume (though not yet without question since none of us have sufficient data to confirm that) that this DC was IFM’d and that the metadata indicates from whom the IFM’s backup-data was originally sourced (this is not necessarily the same DC from which this particular IFM was taken since it too may have been promoted from yet another IFM.)  HOW COOL IS THAT?

Here is a little [4] Script I used to get me the info:[5]

  1. # Get the Current Domain
  2. $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()
  3. # Get the User Container for the Domain. We use this for the Metadata
  4. $ConfigContainer = "CN=Configuration,{0}" -f ([adsi]"").distinguishedName[0]
  5. # The final line enums each DC and check the MetaData for the CN of the Users Container. If IFM’d this should be a remote DC. If local it was replicated.
  6. $domain.DomainControllers | Select Name,@{n="USN";e={$_.GetReplicationMetadata($ConfigContainer).cn | select name,LocalChangeUsn,OriginatingChangeUsn,OriginatingServer}} | ft -auto

Here is what the output of the script looks

Name                          USN
====                         ===
HomeDC1.corp.lab     @{Name=cn; LocalChangeUsn=4100; OriginatingChangeUsn=4100; OriginatingServer=HomeDC1.corp.lab}
HomeDC2.corp.lab     @{Name=cn; LocalChangeUsn=5552; OriginatingChangeUsn=5552; OriginatingServer=HomeDC2.corp.lab}
Lab0DC2.corp.lab       @{Name=cn; LocalChangeUsn=5552; OriginatingChangeUsn=5552; OriginatingServer=Lab0DC2.corp.lab}
Lab0DC1.corp.lab       @{Name=cn; LocalChangeUsn=5552; OriginatingChangeUsn=5552; OriginatingServer=Lab0DC1.corp.lab}
Home1dc1.corp.lab  @{Name=cn; LocalChangeUsn=5691; OriginatingChangeUsn=5691; OriginatingServer=Home1dc1.corp.lab}
Home1dc2.corp.lab    @{Name=cn; LocalChangeUsn=5691; OriginatingChangeUsn=5691; OriginatingServer=Home1dc1.corp.lab}
Lab1dc1.corp.lab       @{Name=cn; LocalChangeUsn=5691; OriginatingChangeUsn=5691; OriginatingServer=Home1dc1.corp.lab}
Lab1dc2.corp.lab       @{Name=cn; LocalChangeUsn=5691; OriginatingChangeUsn=5691; OriginatingServer=Home1dc1.corp.lab}

[1] Here is one kind of DIT bloat scenario. (HERE.)
[2] More info on IFM (Install From Media) (HERE)
[3] Comment from Dean
[4] joe posted a “one liner” perl script, I could have easily posted one as well, but I wanted my script to be clear in intent. You could simply do this:

  1. ([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()).DomainControllers | Select Name,@{n="USN";e={$_.GetReplicationMetadata("CN=Configuration,{0}" -f ([adsi]"").distinguishedName[0]).cn | select name,LocalChangeUsn,OriginatingChangeUsn,OriginatingServer}} | ft -auto

[5] I used the configuration container for this. This script assumes you are in the forest root.

Working with LDAP Stats Control in Powershell

What: The stats control is a LDAP control that you can pass that will tell the server to return its internal stats on a query.

Why: The stats control is a great way to see what the Domain Controller does with your filter. Like what indexes it hits, how many entries it had to visit, how much time the DC spent, and entries visited. It is very useful in creating the most efficient LDAP Query possible.

How: I Used System.DirectoryServices.Protocols.DirectoryControl to pass the LDAP control to the Server and I used System.DirectoryServices.Protocols.BERConverter along with the protocol spec here: LDAP_SERVER_GET_STATS_OID: 1.2.840.113556.1.4.970 to decode the Byte Array that was returned.

Here is what is Returned:
For 2000
- threadCount: Number of threads that were processing LDAP requests on the DC at the time the search operation was performed.
- coreTime: The time in milliseconds which core logic in the DC spent processing the request.
- callTime: The overall time in milliseconds that the DC spent processing the request.
- searchSubOperations: The number of individual operations which the DC performed in processing the request.

For 2003/2008
- threadCount: Number of threads that were processing LDAP requests on the DC at the time the search operation was performed.
- callTime: The overall time in milliseconds that the DC spent processing the request
- entriesReturned: The number of objects returned in the search result.
- entriesVisited: The number of objects that the DC considered for inclusion in the search result.
- filter: String which represents the optimized form of the search filter used by the DC to perform a search. This very well may be different than the filter that was passed.
- index: String which indicates which database indexes were used by the DC to perform the search.

For 2008 Only
- pagesReferenced: The number of database pages referenced by the DC in processing the search.
- pagesRead: The number of database pages read from disk.
- pagesPreread: The number of database pages preread from disk by the DC in processing the search.
- pagesDirtied: The number of clean database pages modified by the DC in processing the search.
- pagesRedirtied: The number of previously modified database pages that were modified by the DC in processing the search.
- logRecordCount: The number of database log records generated by the DC in processing the search.
- logRecordBytes: The size in bytes of database log records generated by the DC in processing the search.

Note:
- Must have SE_DEBUG_PRIVILEGE
- I did NOT implement SO_EXTENDED_FMT flag.
- I did NOT test 2000.
- The functions that decodes Byte Array actually return objects, but for this test I just outputed the test to mimic ADFind.exe
- Special thanks to Robin Caron, joe Richards, and Dmitri Gavrilov for help with the decoding.
- Here is GREAT Doc on the Controls (and everything else AD) [MS-ADTS]: Active Directory Technical Specification

Code:

  1. Param(
  2.         $filter = "(objectclass=*)",
  3.         $base,
  4.         $Server,
  5.         [int]$pageSize = 1000,
  6.         [string[]]$props = @("1.1"),
  7.         [switch]$StatsOnly,
  8.         [switch]$Verbose
  9.     )
  10. function CreateStatsObject2008{
  11.     Param($StatsArray)
  12.     $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiiiiaiaiiiiiiiiiiiiii}",$StatsArray) # Win2008
  13.     $myStatsObject = New-Object System.Object
  14.     $myStatsObject | Add-Member -Name "ThreadCount"     -Value $DecodedArray[1]  -MemberType "NoteProperty"
  15.     $myStatsObject | Add-Member -Name "CallTime"        -Value $DecodedArray[3]  -MemberType "NoteProperty"
  16.     $myStatsObject | Add-Member -Name "EntriesReturned" -Value $DecodedArray[5]  -MemberType "NoteProperty"
  17.     $myStatsObject | Add-Member -Name "EntriesVisited"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
  18.     $myStatsObject | Add-Member -Name "Filter"          -Value $DecodedArray[9]  -MemberType "NoteProperty"
  19.     $myStatsObject | Add-Member -Name "Index"           -Value $DecodedArray[11] -MemberType "NoteProperty"
  20.     $myStatsObject | Add-Member -Name "PagesReferenced" -Value $DecodedArray[13] -MemberType "NoteProperty"
  21.     $myStatsObject | Add-Member -Name "PagesRead"       -Value $DecodedArray[15] -MemberType "NoteProperty"
  22.     $myStatsObject | Add-Member -Name "PagesPreread"    -Value $DecodedArray[17] -MemberType "NoteProperty"
  23.     $myStatsObject | Add-Member -Name "PagesDirtied"    -Value $DecodedArray[19] -MemberType "NoteProperty"
  24.     $myStatsObject | Add-Member -Name "PagesRedirtied"  -Value $DecodedArray[21] -MemberType "NoteProperty"
  25.     $myStatsObject | Add-Member -Name "LogRecordCount"  -Value $DecodedArray[23] -MemberType "NoteProperty"
  26.     $myStatsObject | Add-Member -Name "LogRecordBytes"  -Value $DecodedArray[25] -MemberType "NoteProperty"
  27.     $myStatsObject
  28. }
  29. function CreateStatsObject2003{
  30.     Param($StatsArray)
  31.     $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiiiiaia}",$StatsArray) # Win2003
  32.     $myStatsObject = New-Object System.Object
  33.     $myStatsObject | Add-Member -Name "ThreadCount"     -Value $DecodedArray[1]  -MemberType "NoteProperty"
  34.     $myStatsObject | Add-Member -Name "CallTime"        -Value $DecodedArray[3]  -MemberType "NoteProperty"
  35.     $myStatsObject | Add-Member -Name "EntriesReturned" -Value $DecodedArray[5]  -MemberType "NoteProperty"
  36.     $myStatsObject | Add-Member -Name "EntriesVisited"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
  37.     $myStatsObject | Add-Member -Name "Filter"          -Value $DecodedArray[9]  -MemberType "NoteProperty"
  38.     $myStatsObject | Add-Member -Name "Index"           -Value $DecodedArray[11] -MemberType "NoteProperty"
  39.     $myStatsObject
  40. }
  41. function CreateStatsObject2000{
  42.     Param($StatsArray)
  43.     $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiii}",$StatsArray) # Win2000
  44.     $myStatsObject = New-Object System.Object
  45.     $myStatsObject | Add-Member -Name "ThreadCount"          -Value $DecodedArray[1]  -MemberType "NoteProperty"
  46.     $myStatsObject | Add-Member -Name "CoreTime"             -Value $DecodedArray[3]  -MemberType "NoteProperty"
  47.     $myStatsObject | Add-Member -Name "CallTime"             -Value $DecodedArray[5]  -MemberType "NoteProperty"
  48.     $myStatsObject | Add-Member -Name "searchSubOperations"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
  49.     $myStatsObject
  50. }
  51.  
  52. if($Verbose){$VerbosePreference = "Continue"}
  53.  
  54. Write-Verbose " - Loading System.DirectoryServices.Protocols"
  55. [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
  56.    
  57. [int]$pageCount = 0
  58. [int]$objcount = 0
  59.  
  60. if(!$Server)
  61. {
  62.     $rootDSE = [ADSI]"LDAP://rootDSE"
  63.     $Server = $rootDSE.dnsHostName
  64.     if(!$base){$base = $rootDSE.defaultNamingContext}
  65.     switch ($rootDSE.domainControllerFunctionality)
  66.     {
  67.         0 {$expression = ‘CreateStatsObject2000 $stats’}
  68.         2 {$expression = ‘CreateStatsObject2003 $stats’}
  69.         3 {$expression = ‘CreateStatsObject2008 $stats’}
  70.     }
  71. }
  72.  
  73. Write-Verbose " - Using Server:  [$Server]"
  74. Write-Verbose " - Using Base:    [$base]"
  75. Write-Verbose " - Using Filter:  [$filter]"
  76. Write-Verbose " - Page Size:     [$PageSize]"
  77. Write-Verbose " - Returning:     [$props]"
  78. Write-Verbose " - CSV:           [$csv]"
  79. Write-Verbose " - NoHeaders:     [$noHeader]"
  80. Write-Verbose " - Count:         [$Count]"
  81. Write-Verbose " - StatsOnly:     [$StatsOnly]"
  82. Write-Verbose " - Expression:    [$expression]"
  83.  
  84. Write-Verbose " - Creating LDAP connection Object"
  85. $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
  86. $Subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  87.  
  88. Write-Verbose " + Creating SearchRequest Object"
  89. $SearchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$Subtree,$props)
  90.  
  91. Write-Verbose "   - Creating System.DirectoryServices.Protocols.PageResultRequestControl Object"
  92. $PagedRequest  = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  93.  
  94. Write-Verbose "   - Creating System.DirectoryServices.Protocols.SearchOptionsControl Object"
  95. $SearchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  96.  
  97. Write-Verbose "   - Creating System.DirectoryServices.Protocols.DirectoryControl Control for OID: [1.2.840.113556.1.4.970]"
  98. $oid = "1.2.840.113556.1.4.970"
  99. $StatsControl = New-Object System.DirectoryServices.Protocols.DirectoryControl($oid,$null,$false,$true)
  100.  
  101. Write-Verbose "   - Adding Controls"
  102. [void]$SearchRequest.Controls.add($pagedRequest)
  103. [void]$SearchRequest.Controls.Add($searchOptions)
  104. [void]$SearchRequest.Controls.Add($StatsControl)
  105.  
  106. $start = Get-Date
  107. while ($True)
  108. {
  109.     # Increment the pageCount by 1
  110.     $pageCount++
  111.  
  112.     # Cast the directory response into a SearchResponse object
  113.     Write-Verbose " - Cast the directory response into a SearchResponse object"
  114.     $searchResponse = $connection.SendRequest($searchRequest)
  115.  
  116.     # Display the retrieved page number and the number of directory entries in the retrieved page
  117.     Write-Verbose (" - Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count)
  118.  
  119.  
  120.     Write-Verbose " - Returning Stats for Page:$PageCount"
  121.     $stats = $searchResponse.Controls[0].GetValue()
  122.     $ResultStats = invoke-Expression $expression
  123.     if($pageCount -eq 1)
  124.     {
  125.         $StatsFilter = $ResultStats.Filter
  126.         $StatsIndex = $ResultStats.Index
  127.         Write-Verbose "   + Setting Filter to [$StatsFilter]"
  128.         Write-Verbose "   + Setting Index  to [$StatsIndex]"
  129.     }
  130.    
  131.     # If Cookie Length is 0, there are no more pages to request"
  132.     if ($searchResponse.Controls[1].Cookie.Length -eq 0)
  133.     {
  134.         if($count){$objcount}
  135.         "`nStatistics"
  136.         "================================="
  137.         "Elapsed Time: {0} (ms)" -f ((Get-Date).Subtract($start).TotalMilliseconds)
  138.         "Returned {0} entries of {1} visited - ({2})`n" -f $ResultStats.EntriesReturned,$ResultStats.EntriesVisited,($ResultStats.EntriesReturned/$ResultStats.EntriesVisited).ToString(‘p’)
  139.         "Used Filter:"
  140.         "- {0}`n" -f $StatsFilter
  141.         "Used Indices:"
  142.         "- {0}`n" -f $StatsIndex
  143.         break
  144.     }
  145.  
  146.     # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
  147.     # page of data in the send request and cast the directory control into a PageResultResponseControl object
  148.     Write-Verbose " - Setting Cookie on SearchResponse to the PageReQuest"
  149.     $pagedRequest.Cookie = $searchResponse.Controls[1].Cookie
  150. }

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. #

CMDLet Design Suggestion

I wanted to make a case for using Task Base CMDLETs instead of methods when designing Snapins. There have been several “Vendors” that have done CMDLets for Powershell: Exchange, Citrix, VMWare, and IBM to name a few. Most have done very well here, but this is one thing I think the VMware team excelled at.

< sidebar >
As some of you may know, I have a long background in Active Directory. As an Ex-Microsoft Support Professional in the Directory Services Team and later a Rapid Response Engineer specializing in Active Directory, let’s just say I have passion for all things AD.
I was lucky enough to spend a significant amount of time with the DS team at MS responsible for the Powershell CMDLets they will release at some time in the future. While I cannot give any details I can say… OMG! I CANNOT WAIT to be able to talk about them.
< / sidebar >

Here is the basic Goal

  1. Get-Something | Filter | Change-Something | Change-SomethingElse | Save-Something

The thing to avoid: Depending on methods for object task.
Get-Something | %{$_.DoSomething()}

Here is an Example of what I mean
Lets say we have a Car Object (class). The Car object has Properties like Make, Model, Color, TireCount, Size, and Type.
We also have things we can do with a car like start , turn off, stop, turn, load, and unload.

We could approach this by creating a Car class with the set properties and methods. This may seem simpler, but it is not intuitive for your typical Admin. Your typical admin does not want to do this

  1. Get-Car | ?{$_.Type -eq "MiniVan"} | %{$_.LoadPeople()} | %{$_.Start()} | %{$_.Turn("Right")} | %{$_.Stop()} | %{$_.UnLoadPeople()}

Ideally from an Admin perspective a bunch of Task oriented CMDLets would be your best bet. Let’s assume you had these CMDLets instead of Methods:
Get-Car
New-Car
Remove-Car
Start-Car
Stop-Car
Invoke-TurnCar
Invoke-LoadCar
Invoke-UnLoadCar
Set-Car

Your admin can now do this

  1. Get-Car |  ?{$_.Type -eq "MiniVan"} | Invoke-LoadCar | Start-Car | Invoke-TurnCar -Right | Stop-Car | Invoke-UnloadCar

This reads more like a sentence than a script syntax.

Back From the MVP Summit

Well… Just got back from a week in Redmond. It was awesome!

First I want to thank MS for putting this all together. It was a testament to their commitment to the community and desire to see their products succeed.

While this was my first MVP summit, I did hear several others comment on how this was the best one yet.

I was VERY lucky to have a seasoned MVP vet (Dean Wells) show me the ropes and introduce me to some of the (IMO) smartest technical people on the planet. These guys/gals are not only incredibly smart… but they were an absolute blast to hang out with.

I doubt they subscribe to my blog but I wanted to do a shout out and link their blogs/Sites

Dean Wells: Absolute Great Trainer/Consultant http://www.msetechnology.com
joe Richards (joeware): http://blog.joeware.net/
Joe Kaplan : http://www.joekaplan.net/
Brian Desmond: http://briandesmond.com/blog/default.aspx
Laura Hunter: http://www.shutuplaura.com/
Mr Hunter (Mark Arnold) : http://markarnold.blogspot.com/
Gil Kirkpatrick (NetPro): http://www.gilsblog.com/
Ulf B. Simon-Weidner’s: http://msmvps.com/blogs/UlfBSimonWeidner/Default.aspx
Tony Murray (founder activedir.org): www.ActiveDir.org Blog: http://www.open-a-socket.com/
Little Jimmy: http://www.jimmytheswede.blogspot.com/
Darren Mar-Elia (SDMSoftware): http://www.sdmsoftware.com/blog/
Princess Jorge!: http://blogs.dirteam.com/blogs/jorge/
Brett Shirley: http://blogs.msdn.com/brettsh/
Eric Fleischman: http://blogs.technet.com/efleis/default.aspx (currently VERY slow to post)

I can not tell you (literally I am under NDA :) ) How much I learned this past week.

What I can say is that for Powershell… the future is VERY BRIGHT!

The time with the Powershell Dev team was great. Again… can’t say much, but they have great plans.

I spent about 5hrs with the Active Directory team discussing their plans. If the AD Team can pull off what they have planned… OMG it is awesome. AD administration will never be the same.

The next version of SCVMM looks great.

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

A collection of LDAP Filter Info

I often find myself googling for LDAP filter info. This time I decided to post the resulting set of websites I hit for this info.

NOTE: MS release the Specs for Active Directory’s LDAP Compliance here. GREAT DOC!
http://download.microsoft.com/download/d/c/8/dc83e0b8-fc2c-4af4-bd27-45b5963ad98d/AD%20LDAP%20Compliance.doc

Blog Entry on LDAP Filters
————————-
http://bsonposh.com/modules/wordpress/?p=78

LDAP Filter Articles
——————-
query Active Directory by using a bitwise filter
http://support.microsoft.com/kb/269181

Search Filter Syntax
http://msdn2.microsoft.com/en-us/library/aa746475.aspx

Mastering the LDAP search filter
http://searchwinit.techtarget.com/tip/0,289483,sid1_gci1191071,00.html

userAccountControl
——————-
UserAccountControl flags
http://support.microsoft.com/kb/305144

User-Account-Control Attribute (Windows)
http://msdn2.microsoft.com/en-us/library/ms680832.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}

Active Directory Permission Inheritance (The Glories of Consistency!)

Someone asked a question (on experts-exchange) about how to Enable Permission Inheritance on an Active Directory Object.

Here is what I came up with.

  1. # Enable AD Permission Inheritance on an Object
  2. Param($DN)
  3. $user = [ADSI]"LDAP://$dn"
  4. $user.psbase.ObjectSecurity.SetAccessRuleProtection($false,$true)
  5. $user.psbase.CommitChanges