Posts RSS Comments RSS 253 Posts and 411 Comments till now

Blog Archives

Using the LDAP stats control with Powershell

At TEC this year I did a session with Darren the "GPO Guy" regarding using S.DS.Protocols to get back query stats from Active Directory. I mentioned my stats control code several times and I promised I would post the code so here it is:

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\3 = "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. }

Constructed Properties and LVR (Linked-Value Replication)

There was an interesting question that came up on the news group that discussed getting Active Directory replication metadata and while the question really wasn’t directly a Powershell question I found it intriguing. I knew I had the answer to the question back in my brain, but I couldn’t retrieve it.

Here is the Question:

“I’m working on a script where I need to compare the last time the member
attribute of a distribution group was modified (not the AD group object
itself) with the time stamp on a file (I’m exporting distribution group
memberships to a file, but only ones that have changed). The problem
I’m running into is that some Active Directory distribution groups
aren’t returning the member attribute when I look at the replication
meta data.

Any one have any thoughts on why some distribution groups return the
member attribute when I run GetReplicationMetadata and some don’t? This
returns all kinds of other attributes and their metadata, but naturally
not the one I’m interested in (it does some times, on some groups, but
not all). I could run my script by the actual AD object WhenChanged
attribute, but I’ll be processing a large number of lists and I want it
to run as fast as possible and since other attributes can change on a
group object, I don’t want to have to export a 8,000 member group if the
displayName changes, for example (I’m only interested in the member
attribute).
As always, any insight is appreciated.”

I thought about this for a bit and also consulted some AD friends and we determined the issue was LVR (Link Value Replication.) This was introduced in Windows 2003 (specifically 2003 Native Mode.)

Basically, LVR changed the unit of replication for some attributes to be the actual values. Prior to LVR if you changed group membership the entire attribute member would have to replicate. With LVR, just the link you added for user replicates.

As one can imagine this changed the Metadata and therefore GetReplicationMetadata() didnt get retrieve the data for you. Where does this leave us?

There were also a few constructed attributes that were added with 2003. One of which is called “msds-replvaluemetadata.” This attribute provided the metadata for these links in wonderful XML format. You will find code using this XML below.

Some Useful Links regarding LVR and Constructed Attributes:

Below is the script that resulted from the investigation


 

Get-GroupMemberMetadata.ps1

Param($GroupName)

$GroupMembers = @()

$root = [ADSI]""
$filter = "(&(objectclass=group)(name=$GroupName))"
$props = @("msDS-ReplValueMetaData")
$Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
foreach($Group in $Searcher.findall())
{
    foreach($childMember in $Group.Properties."msds-replvaluemetadata")
    {
        $GroupMembers += [xml+site:msdn.microsoft.com”>XML]$ChildMember
    }
}

foreach($Member in $GroupMembers)
{
    $Member.ChildNodes
}

Brian Desmond’s New Book (Active Directory 4th edition)

Generally I make a point to avoid this kind of stuff, but since I contributed to the Powershell chapters I wanted to mention it.

Get details about the book by clicking HERE.

You can get the book by clicking the picture below.


Functions for Active Directory Permissions

Below are some of the function I have written to work with Access Control List in Active Directory.

Here is the list and what they do
Get-ADACL: Gets the Access Control List of an AD Object
Set-ADACL: Set the Access Control List of an AD Object
New-ADACE: Creates a Access Control Entry to add to an AD Object
ConvertTo-Sid: Converts a UserName to a SID
ConvertTo-Name: Converts a SID to the UserName
Get-Self: Gets current Security Identity


function Get-ADACL {
    Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
    function HelpMe{
        Write-Host
        Write-Host " Get-ADACL.ps1:" -fore Green
        Write-Host "   Gets ACL object or SDDL for AD Object"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
        Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or $help){HelpMe;return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
   
    Write-Verbose " + Processing Object [$DNPath]"
    $DE = [ADSI]"LDAP://$DNPath"
   
    Write-Verbose "   – Getting ACL"
    $acl = $DE.psbase.ObjectSecurity
    if($SDDL)
    {
        Write-Verbose "   – Returning SDDL"
        $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    }
    else
    {
        Write-Verbose "   – Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
        $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
    }
}
function Set-ADACL {
    Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
    function HelpMe{
        Write-Host
        Write-Host " Set-ADACL.ps1:" -fore Green
        Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -ACL                   : Parameter: ACL Object"
        Write-Host "   -sddl                  : Parameter: SDDL String"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
    Write-Verbose " + Processing Object [$DNPath]"
   
    $DE = [ADSI]"LDAP://$DNPath"
    if($sddl)
    {
        Write-Verbose "   – Setting ACL using SDDL [$sddl]"
        $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
    }
    else
    {
        foreach($ace in $acl)
        {
            Write-Verbose "   – Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
            $DE.psbase.ObjectSecurity.SetAccessRule($ace)
        }
    }
    $DE.psbase.commitchanges()
    Write-Host
}
function New-ADACE {
    Param([System.Security.Principal.IdentityReference]$identity,
        [System.DirectoryServices.ActiveDirectoryRights]$adRights,
        [System.Security.AccessControl.AccessControlType]$type,
        $Guid)
   
    $help = @"
    $identity
        System.Security.Principal.IdentityReference
        http://msdn.microsoft.com/en-us/library/system.security.principal.ntaccount.aspx
   
    $adRights
        System.DirectoryServices.ActiveDirectoryRights
        http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights.aspx
   
    $type
        System.Security.AccessControl.AccessControlType
        http://msdn.microsoft.com/en-us/library/w4ds5h86(VS.80).aspx
   
    $Guid
        Object Type of the property
        The schema GUID of the object to which the access rule applies.
"
@
    $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$guid)
    $ACE
}
function ConvertTo-Sid($UserName,$domain = $env:Computername) {
   $ID = New-Object System.Security.Principal.NTAccount($domain,$UserName)
   $SID = $ID.Translate([System.Security.Principal.SecurityIdentifier])
   $SID.Value
}
function ConvertTo-Name($sid) {
   $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
   $User = $ID.Translate( [System.Security.Principal.NTAccount])
   $User.Value
}
function Get-Self{
   ([Security.Principal.WindowsIdentity]::GetCurrent())
}

Blog post by Mark Wilson on Win 2008 R2

Mark Wilson has a great blog post on some of the features expected in Win2k8 R2. You can find it HERE.

The bestpart is (of course 😉 )

On the management front: there is a greater emphasis on the command line with improved scripting capabilities with PowerShell 2 and over 200 new cmdlets for server roles as well as power, blade and chassis management – working with vendors to deliver hardware which is compatible with WS-Management – and new command line tools for migration of Active Directory, DNS, DHCP, file and print servers; Server Manager will support remote connections, with a performance counter view and best practices analyzer (similar to the ones which we have seen shipped for server products such as Exchange Server for a few years now); and a new migration portal will expose step-by-step documentation for migration of roles and operating system settings from Windows Server 2003 and 2008 servers to Windows Server 2008 R2.

The depressing part

So, when do we get to use all this Windows Server 2008 R2 goodness? Well, Microsoft is not yet ready to release a beta and, based on previous versions of Windows Server, I would expect to see at least two betas and a couple of CTPs before the release candidates – but the product team is currently not committing to a date – other than to say “early 2010″ (which, incidentally, will be 2 years after Windows Server 2008 shipped).

Setting lDAPAdminLimits via Powershell

I was having a conversation with a friend the other day and he brought up a question about updating the AD property lDAPAdminLimits.

The Problem
The property is stored as an array of string values (at least as far as Powershell is concerned.) The initial reaction was do try something like this $queryPolicies.lDAPAdminLimits.MaxNotificationPerConn = 30, but this assumed that MaxNotficationPerConn was a property of lDAPAdminLimits and not the actual value (or at least part of the value.)

The Solution
Use the ADSI method PutEX to modify the value. PutEx uses ADS_PROPERTY_OPERATION_ENUM to make selective changes to an existing property. In the script below, we add the new value using the Update operation and then use the Delete operation to remove the old value.

Some Examples of Use

D:\Scripts\Set-ldapAdminPolicy.ps1 MaxNotificationPerConn 45
D:\Scripts\Set-ldapAdminPolicy.ps1 MaxQueryDuration 360
D:\Scripts\Set-ldapAdminPolicy.ps1 MaxPageSize 500
D:\Scripts\Set-ldapAdminPolicy.ps1 MaxPoolThreads 8

Here is a link on how to view/set via NTDSUtil.exe
How to view and set LDAP policy in Active Directory by using Ntdsutil.exe

The Code

Param($policy=$(throw ‘$policy is required’),$count=30)
$rootDSE = [ADSI]"LDAP://rootDSE"
$config = $rootDSE.configurationNamingContext
$queryPolicies = [adsi]"LDAP://CN=Default Query Policy,CN=Query-Policies,cn=Directory Service,cn=Windows NT,CN=Services,$config"
$oldvalue = $queryPolicies.lDAPAdminLimits | ?{$_ -match $policy}
$queryPolicies.PutEx(3,"lDAPAdminLimits",@("$policy=$count"))
$queryPolicies.Setinfo()
$queryPolicies.PutEx(4,"lDAPAdminLimits",@("$oldvalue"))
$queryPolicies.Setinfo()

Bulk Imports using CSV (cross post from TurboChargeAD.org)

As some of you may know I am doing some guest blogging over at TurboChargeAD.org and below is a link to the second such article.

Here is the Link: Bulking Importing User from CSV file using Quest cmdlets

Here is the code

#Import-ADUser.ps1
#requires -pssnapin Quest.ActiveRoles.ADManagement
Param($file,$OU,[switch]$whatif,[switch]$verbose)

if($verbose){$VerbosePreference = "Continue"}

if(!(test-path $file))
{
   throw " File NOT found!"
   return
}

if(!$ou)
{
    # Setting the OU to the Users container
    $ou = "CN=Users,{0}" -f ([ADSI]"").distinguishedName[0]
}

Write-Verbose " Using File: $file"
Write-Verbose " Using OU: $ou"

foreach($user in (Import-Csv $file))
{
    $props = @{}
    # Getting a list property names.
    $propNames = $user | Get-Member -MemberType properties | %{$_.name}
    # Foreach of the property names add an entry in the hash table with
    # the key being the property name and value being the value from the object
    foreach($prop in $propNames)
    {
        # This removes quotes if they exist
    $value = $user.$prop -replace "’|`"",""
        $props += @{$prop=$value}
    }
    # Create User using the displayname as the CN
    $MyUser = new-qaduser -name $user.displayName `
                          -ObjectAttributes $props `
                          -parent $OU `
                          -whatif:$whatif `
                          -verbose:$verbose
    $MyUser
}

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]

# Get the Current Domain
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()
# Get the User Container for the Domain. We use this for the Metadata
$ConfigContainer = "CN=Configuration,{0}" -f ([adsi]"").distinguishedName[0]
# 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.
$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:

([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:

Param(
        $filter = "(objectclass=*)",
        $base,
        $Server,
        [int]$pageSize = 1000,
        [string[]]$props = @("1.1"),
        [switch]$StatsOnly,
        [switch]$Verbose
    )
function CreateStatsObject2008{
    Param($StatsArray)
    $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiiiiaiaiiiiiiiiiiiiii}",$StatsArray) # Win2008
    $myStatsObject = New-Object System.Object
    $myStatsObject | Add-Member -Name "ThreadCount"     -Value $DecodedArray[1]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "CallTime"        -Value $DecodedArray[3]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "EntriesReturned" -Value $DecodedArray[5]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "EntriesVisited"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "Filter"          -Value $DecodedArray[9]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "Index"           -Value $DecodedArray[11] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "PagesReferenced" -Value $DecodedArray[13] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "PagesRead"       -Value $DecodedArray[15] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "PagesPreread"    -Value $DecodedArray[17] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "PagesDirtied"    -Value $DecodedArray[19] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "PagesRedirtied"  -Value $DecodedArray[21] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "LogRecordCount"  -Value $DecodedArray[23] -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "LogRecordBytes"  -Value $DecodedArray[25] -MemberType "NoteProperty"
    $myStatsObject
}
function CreateStatsObject2003{
    Param($StatsArray)
    $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiiiiaia}",$StatsArray) # Win2003
    $myStatsObject = New-Object System.Object
    $myStatsObject | Add-Member -Name "ThreadCount"     -Value $DecodedArray[1]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "CallTime"        -Value $DecodedArray[3]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "EntriesReturned" -Value $DecodedArray[5]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "EntriesVisited"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "Filter"          -Value $DecodedArray[9]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "Index"           -Value $DecodedArray[11] -MemberType "NoteProperty"
    $myStatsObject
}
function CreateStatsObject2000{
    Param($StatsArray)
    $DecodedArray = [System.DirectoryServices.Protocols.BerConverter]::Decode("{iiiiiiii}",$StatsArray) # Win2000
    $myStatsObject = New-Object System.Object
    $myStatsObject | Add-Member -Name "ThreadCount"          -Value $DecodedArray[1]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "CoreTime"             -Value $DecodedArray[3]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "CallTime"             -Value $DecodedArray[5]  -MemberType "NoteProperty"
    $myStatsObject | Add-Member -Name "searchSubOperations"  -Value $DecodedArray[7]  -MemberType "NoteProperty"
    $myStatsObject
}

if($Verbose){$VerbosePreference = "Continue"}

Write-Verbose " – Loading System.DirectoryServices.Protocols"
[VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
   
[int]$pageCount = 0
[int]$objcount = 0

if(!$Server)
{
    $rootDSE = [ADSI]"LDAP://rootDSE"
    $Server = $rootDSE.dnsHostName
    if(!$base){$base = $rootDSE.defaultNamingContext}
    switch ($rootDSE.domainControllerFunctionality)
    {
        0 {$expression = ‘CreateStatsObject2000 $stats’}
        2 {$expression = ‘CreateStatsObject2003 $stats’}
        3 {$expression = ‘CreateStatsObject2008 $stats’}
    }
}

Write-Verbose " – Using Server:  [$Server]"
Write-Verbose " – Using Base:    [$base]"
Write-Verbose " – Using Filter:  [$filter]"
Write-Verbose " – Page Size:     [$PageSize]"
Write-Verbose " – Returning:     [$props]"
Write-Verbose " – CSV:           [$csv]"
Write-Verbose " – NoHeaders:     [$noHeader]"
Write-Verbose " – Count:         [$Count]"
Write-Verbose " – StatsOnly:     [$StatsOnly]"
Write-Verbose " – Expression:    [$expression]"

Write-Verbose " – Creating LDAP connection Object"
$connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
$Subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"

Write-Verbose " + Creating SearchRequest Object"
$SearchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$Subtree,$props)

Write-Verbose "   – Creating System.DirectoryServices.Protocols.PageResultRequestControl Object"
$PagedRequest  = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)

Write-Verbose "   – Creating System.DirectoryServices.Protocols.SearchOptionsControl Object"
$SearchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)

Write-Verbose "   – Creating System.DirectoryServices.Protocols.DirectoryControl Control for OID: [1.2.840.113556.1.4.970]"
$oid = "1.2.840.113556.1.4.970"
$StatsControl = New-Object System.DirectoryServices.Protocols.DirectoryControl($oid,$null,$false,$true)

Write-Verbose "   – Adding Controls"
[void]$SearchRequest.Controls.add($pagedRequest)
[void]$SearchRequest.Controls.Add($searchOptions)
[void]$SearchRequest.Controls.Add($StatsControl)

$start = Get-Date
while ($True)
{
    # Increment the pageCount by 1
    $pageCount++

    # Cast the directory response into a SearchResponse object
    Write-Verbose " – Cast the directory response into a SearchResponse object"
    $searchResponse = $connection.SendRequest($searchRequest)

    # Display the retrieved page number and the number of directory entries in the retrieved page
    Write-Verbose (" – Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count)

    Write-Verbose " – Returning Stats for Page:$PageCount"
    $stats = $searchResponse.Controls[0].GetValue()
    $ResultStats = invoke-Expression $expression
    if($pageCount -eq 1)
    {
        $StatsFilter = $ResultStats.Filter
        $StatsIndex = $ResultStats.Index
        Write-Verbose "   + Setting Filter to [$StatsFilter]"
        Write-Verbose "   + Setting Index  to [$StatsIndex]"
    }
   
    # If Cookie Length is 0, there are no more pages to request"
    if ($searchResponse.Controls[1].Cookie.Length -eq 0)
    {
        if($count){$objcount}
        "`nStatistics"
        "================================="
        "Elapsed Time: {0} (ms)" -f ((Get-Date).Subtract($start).TotalMilliseconds)
        "Returned {0} entries of {1} visited – ({2})`n" -f $ResultStats.EntriesReturned,$ResultStats.EntriesVisited,($ResultStats.EntriesReturned/$ResultStats.EntriesVisited).ToString(‘p’)
        "Used Filter:"
        "- {0}`n" -f $StatsFilter
        "Used Indices:"
        "- {0}`n" -f $StatsIndex
        break
    }

    # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
    # page of data in the send request and cast the directory control into a PageResultResponseControl object
    Write-Verbose " – Setting Cookie on SearchResponse to the PageReQuest"
    $pagedRequest.Cookie = $searchResponse.Controls[1].Cookie
}

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


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

Demo Text

#
# Lets start off by looking at DirectoryEntry
#
$DE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://CN=tstUsr101,OU=MyUsers,DC=corp,DC=lab")
#
# First lets see what we have access to
#
$DE | Get-Member
#’
# Hmmm.. doesn’t seem like much. OH WAIT! Remember Powershell abstracts the class… Lets add psbase
#
$DE.psbase | Get-Member
#
# Lets look at what properties are available.
#
$DE.psbase.Properties
#
# Thats more like it. You may also note that some AD properties are still missing.
# That is because that LDAP doesnt return all the properties. For these you need to "GET" them.
$DE.psbase.InvokeGet(‘msExchUMFaxID’)
#
# Using DirectoryEntry is fine if you know the DN of the object, but what if you need to search?
# Lets look at System.DirectoryServices.DirectorySearcher
#
# The Searcher needs some info so put that in variables first
#
$root = [ADSI]""  ## This is using the Type Accelerator we spoke about earlier… This is Gets the base
$filter = "(&(objectcategory=user))"
#
# Now Lets create the searcher
#
$searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
#
# That gets the searcher ready, but to execute we need to call findall() or findone()
#
$users = $searcher.findAll()
#
# Lets see what we got. We have alot so lets only pick the first 10
#
$users | select -first 10
#
# Tons of info, but notice that this is NOT the same as DirectoryEntry
#
$users | get-Member
#
# It still has the properties property, Lets look (but only the first 3)
#
$users | select -first 3 | %{$_.Properties}
#
# Finally Lets look at System.DirectoryDervices.ActiveDirectory.Domain
#
# We can use this to interactively browse around
#
[system.directoryservices.activedirectory.domain]::GetCurrentDomain()
#
# Lets assign that to variable to play with
#
$domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
$domain
#
# Lets see what this has to offer
#
$domain | get-member
#
# Tons of cool stuff here.
#
# We can find all domain controllers
$domain.FindAllDomainControllers()
#
# We Can look at our Domain FSMO
#
$domain | ft PdcRoleOwner,RidRoleOwner,InfrastructureRoleOwner
#
# I can even step the tree and get my forest root
#
$forest = $domain.Forest
$forest
#
# With our new found $forest object… what can do we do?
#
$forest | Get-Member
#
# WE can find all our GCs
#
$forest.FindAllGlobalCatalogs()
#
# We can look at the Forest Mode
#
$forest.ForestMode
#
# Look at the Forest FSMO
#
$forest | ft SchemaRoleOwner,NamingRoleOwner
#
# Even look at sites
$forest.Sites
#
# We can go on forever and ever. If you would like we can revisit this later.
#

« Prev - Next »