Showing posts with label ldap. Show all posts
Showing posts with label ldap. Show all posts

Monday, November 26, 2012

It's Scripting Time Again! AD Server Descriptions

This issue has come up a LOT in my career, but I don't know why.  It seems like something that Microsoft should address with some "feature" or utility or something.  What I'm blabbering about is updating the description for each Active Directory server account to match whatever the local computer description is.  The local computer description is what you see (and can update) from the Computer "Properties" form.

Local Computer Description

Active Directory Computer Description

It came up again tonight when a friend (called asking for help.  I happened to have the pieces of code on my server and glued them together in a few minutes (the mess below).  Every time I do something like this, I see horrifically bad coding habits from years past and do my best to clean them up before sharing them.

Is this Earth-shatteringly unique?  No.  Is it the only script of its kind? No.  Can you find alternatives on the web that will do just as well?  Absolutely.  If I get a little spare time, I will try to post this in PowerShell format (unless you want to submit that and I will post it, giving you full credit).

In any case, I hope this helps someone out there.  Read the WARNING and DISCLAIMER at the bottom!

'****************************************************************
' Filename..: server_descriptions.vbs
' Author....: David M. Stein
' Date......: 11/26/2012
' Purpose...: update AD computer descriptions from local descriptions
' Usage.....: cscript server_descriptions.vbs >output.log
' (note: the above redirect to output.log is optional)
'****************************************************************

Set objRootDSE = GetObject("LDAP://rootDSE")
ldapRoot = objRootDSE.Get("defaultNamingContext")

Const ADS_SCOPE_SUBTREE = 2
Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D

' parse out NetBIOS domain name (e.g. "CONTOSO.COM")
nbDomain = Mid(Split(ldapRoot,",")(0),4)

wscript.echo "info: LDAP root is " & ldapRoot
wscript.echo "info: NetBIOS domain is " & nbDomain

serverlist = GetServerList()

For each strServer in Split(serverlist, ",")
  wscript.echo "server_name...: " & strServer
  strOUpath  = ComputerOU(strServer)
  localDesc  = GetLocalDescription(strServer)
  domainDesc = ADComputerDescription(strOUpath)

  If localDesc = "" Then
    localDesc = "NOT_DEFINED"
  End If

  wscript.echo "ou_path.......: " & strOUPath
  wscript.echo "local_descrip.: " & localDesc
  wscript.echo "domain_descrip: " & domainDesc

  If localDesc <> "NOT_DEFINED" Then
    try = ChangeADDescription(strOUPath, localDesc)
    wscript.echo "desc_updated..: " & try

  End If

  wscript.echo "----------------------------------------"
Next

'----------------------------------------------------------------
' function: get list of servers from domain using OS captions
'----------------------------------------------------------------

Function GetServerList()
  Dim conn, cmd, query, retval : retval = ""
  Dim rs, strOS, strName, counter : counter = 0

  wscript.echo "info: querying server names from active directory..."

  Set cmd = CreateObject("ADODB.Command")
  Set conn = CreateObject("ADODB.Connection")
  conn.Provider = "ADsDSOObject"
  conn.Open "Active Directory Provider"
  cmd.ActiveConnection = conn
  
  query = ";(objectCategory=computer);" & _
    "name,distinguishedName,operatingSystem;subtree"

  cmd.CommandText = query
  cmd.Properties("Page Size") = 100
  cmd.Properties("Timeout") = 30
  cmd.Properties("Cache Results") = False

  Set rs = cmd.Execute

  Do Until rs.EOF
    strOS = rs.Fields("operatingSystem").value
    If InStr(UCase(strOS), "SERVER") > 0 Then   
      strName = rs.Fields("name").value   
      If retval <> "" Then
        If InStr(retval, strName) < 1 Then
          retval = retval & "," & strName
          counter = counter + 1
        End If
      Else
        retval = strName
        counter = counter + 1
      End If
    End If
    rs.MoveNext
  Loop

  rs.Close
  conn.Close
  Set rs = Nothing
  Set cmd = Nothing
  Set conn = Nothing

  wscript.echo "info: " & counter & " servers were found"
  GetServerList = retval

End Function

'----------------------------------------------------------------
' function: get current computer OU from active directory
'----------------------------------------------------------------
 
Function ComputerOU(netBiosName)
  Dim objConnection, objCommand, objRecordSet, strQuery
  Set objConnection = CreateObject("ADODB.Connection")
  Set objCommand = CreateObject("ADODB.Command")
  objConnection.Provider = "ADsDSOObject"
  objConnection.Open "Active Directory Provider"
  Set objCommand.ActiveConnection = objConnection
  objCommand.Properties("Page Size") = 1000
  objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
  strQuery = "Select ADsPath From 'LDAP://" & ldapRoot & _
    "' WHERE objectCategory='computer'" & _
    " AND name='" & netBiosName & "'"
  
  On Error Resume Next
  objCommand.CommandText = strQuery
  Set objRecordSet = objCommand.Execute
  objRecordSet.MoveFirst
  Do Until objRecordSet.EOF
    strResult = objRecordSet.Fields("ADsPath").Value
    objRecordSet.MoveNext
  Loop
  ComputerOU = strResult
End Function

'----------------------------------------------------------------
' description: get local description from remote computer via WMI
'----------------------------------------------------------------

Function GetLocalDescription(strName)
  Dim objWMI, colItems, objItem
  Dim query, retval : retval = ""
  On Error Resume Next
  Set objWMIService = GetObject("winmgmts:\\" & strName & "\root\CIMV2") 
  If err.Number = 0 Then
    query = "SELECT * FROM Win32_OperatingSystem"
    Set colItems = objWMIService.ExecQuery(query,,48) 
    For Each objItem in colItems 
      retval = objItem.Description
    Next
    If IsNull(retval) or Trim(retval) = "" Then
      retval = ""
    End If
  Else
    wscript.echo "error: " & strName & " is offline or inaccessible"
  End If
  GetLocalDescription = retval
End Function

'----------------------------------------------------------------
' function: get AD computer description
'----------------------------------------------------------------

Function ADComputerDescription(strLDAP)
  Dim objComputer, retval, try, ldapstring
  ldapstring = strLDAP
  On Error Resume Next
  Set objComputer = GetObject(ldapstring)
  try = objComputer.Get("description")
  If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
    retval = ""
    Err.Clear
  Else
    retval = try
  End If
  ADComputerDescription = retval
End Function

'----------------------------------------------------------------
' function: set AD computer description (limit 48 chars)
' refer to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa394239(v=vs.85).aspx
'----------------------------------------------------------------

Function ChangeADDescription(strLdapName, strDesc)
  Dim objPC, retval
  wscript.echo "info: modifying domain description..."
  On Error Resume Next
  Set objPC = GetObject(strLdapName)
  objPC.Description = strDesc
  objPC.SetInfo
  retval = err.Number
  If retval <> 0 Then
    retval = retval & " / " & err.Description
  Else
    retval = "SUCCESS"
  End If
  Set objPC = Nothing
  ChangeADDescription = retval
End Function

Warning

This script example includes MINIMAL error handling.  Always TEST, TEST, TEST, and when you think it works properly, TEST it some more.

Disclaimer

Use this script code AT YOUR OWN RISK.  Always test thoroughly in an isolated "test" or "development" environment to avoid negatively impacting production computers.  The author assumes/accepts NO LIABILITY for any direct or derivative use or consequential damages, however, the author wouldn't mind a little constructive feedback if it helps you in any way.

Thursday, March 22, 2012

LDAP User Account Properties: The WMI way

This is just something I ran across working on a recent project.  You can replace "." (local computer) with any valid NetBIOS name on your network (as long as you execute the script code with sufficient privileges to access the WMI Namespace)

[code]
strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\directory\LDAP") 
Set colItems = objWMIService.ExecQuery("SELECT * FROM ds_user",,48) 
For Each objItem in colItems 
    Wscript.Echo "-----------------------------------"
    Wscript.Echo "ds_user instance"
    Wscript.Echo "-----------------------------------"
    'Wscript.Echo "DS_objectGUID: " & objItem.DS_objectGUID
    'Wscript.Echo "DS_objectSid: " & objItem.DS_objectSid
    Wscript.Echo "DS_pwdLastSet: " & objItem.DS_pwdLastSet
    Wscript.Echo "DS_sAMAccountName: " & objItem.DS_sAMAccountName
    Wscript.Echo "DS_sAMAccountType: " & objItem.DS_sAMAccountType
    Wscript.Echo "DS_whenCreated: " & objItem.DS_whenCreated
    Wscript.Echo "DS_whenChanged: " & objItem.DS_whenChanged
Next
[/code]

Note that I commented out the GUID and SID properties.  That was for simplicity. You can fetch them to a variable, convert them to an appropriate data type, so that WSH / VBScript can correctly "echo" the results.  Cheers!

Wednesday, February 23, 2011

Launch IE and Wait for it to be Closed

I really thought this was going to be easier than it turns out to be.  First off, the Wscript.Shell RUN() method is useless with IE when it comes to specifying Wait = True.  It ignores it.  If you make one script call another (via Wscript.Run or Execute, doesn't matter) it works, but not if you deploy the script "per-user" via SCCM, in which case, again, the Wait = True is ignored in the parent script.  So, script A launches script B, but instead of waiting for B to complete, script A just continues on while B is still working.  NOT what I want to happen.  The net result is crap and breaks the workflow entirely.  The solution is WMI and the Win32_ProcessStartup class (and a little scripting and some coffee).


The way this works is that it assumes the web page (running on a trusted intranet server) takes user input via a form and then evokes some sort of modification to the user account in Active Directory via an LDAP expression.  As an example, I'm using an account attribute called "customAttrib" and checking if it is empty (is-null or empty-string) or contains a string value (more than one character).  If the value is empty, the web form is launched and the script waits until the user closes the IE session.  It doesn't matter how many other IE windows or tabs are open.  It fetches the processID for the one it launches and watches to see when it vanishes from the process stack (using a do-while loop).  When the process is closed, the script continues and simply re-checks the attribute to see if it was modified.  The end-game being that it checks if the user successfully completed the form, or simply closed it and tried to ignore it (bad news for the user).  Enjoy!


'****************************************************************
' Filename..: IE_Wait.vbs
' Author....: skatterbrainz.blogspot.com (you know who)
' Date......: 02/23/2011
' Purpose...: launch web page and wait for it to close
'****************************************************************
' comment: check for previous attrib in AD (LDAP query)
' comment: launch ie and navigate to the web page
' comment: check AD again to see if user completed the form
' comment: if not, force a logoff
'****************************************************************


Const enableLogoff = True
Const dndc = "LDAP://DC=contoso,DC=msft"


'----------------------------------------------------------------
' comment: DO NOT MODIFY ANY CODE BELOW THIS POINT!!!
'----------------------------------------------------------------


Const ADS_SCOPE_SUBTREE = 2
Const SW_HIDE = 0
Const SW_NORMAL = 1
Const SW_SHOWMINIMIZED = 2
Const SW_SHOWMAXIMIZED = 3


Const wbemFlagForwardOnly = 32
Const wbemFlagBidirectional = 0
Const wbemFlagReturnImmediately = 16
Const wbemFlagReturnWhenComplete = 0
Const wbemQueryFlagPrototype = 2
Const wbemFlagUseAmendedQualifiers = 131072


Const osLogoff = 0
Const osForcedLogoff = 4
Const osShutdown = 1
Const osForcedShutdown = 5
Const osRestart = 2
Const osForcedRestart = 6


Const strCommand = "C:\Program Files\Internet Explorer\iexplore.exe http://intranet.contoso.msft/stuff"


Dim wshNetwork, uid, objShell, groupPriority, wmi_flags


wmi_flags = wbemFlagForwardOnly + wbemFlagReturnImmediately


Set wshNetwork = CreateObject("Wscript.Network")
Set objShell   = CreateObject("Wscript.Shell")


uid = wshNetwork.UserName
Set wshNetwork = Nothing


custVal = GetAttribute(uid, "customAttrib")


If IsNull(groupPriority) Then   
    LaunchWebForm()


    custVal = GetAttribute(uid, "customAttrib")


    If IsNull(custVal) Then
        MsgBox "Form was not filled out properly!" & _
            vbCRLF & "You will now be logged off...", vbOkOnly+vbCritical, "Web Form"
            
        If enableLogoff = True Then
            Logoff()
        End If
        
    End If
End If


Sub LaunchWebForm()
    Dim objWMIService, objStartup, objConfig, objProcess
    Dim intReturn, query, colItems, objItem, intProcessID
    Dim colMonitoredProcesses, objLatestProcess, processEnded
    
    Set objWMIService = GetObject("winmgmts:" _
        & "{impersonationLevel=impersonate}!\\.\root\cimv2")


    ' comment: configure the new process as visible
    Set objStartup = objWMIService.Get("Win32_ProcessStartup")
    Set objConfig = objStartup.SpawnInstance_
    objConfig.ShowWindow = SW_NORMAL


    ' comment: create a new process (iexplore.exe)
    Set objProcess = objWMIService.Get("Win32_Process")
    intReturn = objProcess.Create(strCommand, Null, objConfig, intProcessID)


    If intReturn <> 0 Then
        'wscript.echo "fail: unable to launch process!"
        wscript.quit(1)
    End If



    'wscript.echo "info: process id is " & intProcessID


    Set objWMIService = GetObject("winmgmts:\\.\root\CIMV2") 


    query = "SELECT ProcessId FROM Win32_Process WHERE ProcessId='" & intProcessID & "'"


    Set colItems = objWMIService.ExecQuery(query,,wmi_flags) 
    For Each objItem in colItems
        intProcessID = objItem.ProcessId
    Next
        
    If intProcessID <> "" Then
        'wscript.echo "info: waiting for process terminate..."
        
        Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _
            ("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'")


        Do Until processEnded = True
            Set objLatestProcess = colMonitoredProcesses.NextEvent
            If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
                processEnded = True
            End If
        Loop


        If processEnded = True Then
            'wscript.echo "info: process was terminated"
        End If
    Else
        'wscript.echo "fail: unable to obtain process id..."
    End If
End Sub


'----------------------------------------------------------------
' function: get LDAP user attribute from AD
'----------------------------------------------------------------


Function GetAttribute(uid, att)
    Dim query, objConnection, objCommand, objRecordSet, retval
    On Error Resume Next
    
    query = "SELECT " & att & " FROM '" & dndc & "' " & _
        "WHERE objectCategory='user' AND sAMAccountName='" & uid & "'"
    
    Set objConnection = CreateObject("ADODB.Connection")
    Set objCommand    = CreateObject("ADODB.Command")
    
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    
    Set objCommand.ActiveConnection = objConnection
    
    objCommand.Properties("Page Size") = 1000
    objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
    
    objCommand.CommandText = query
    
    Set objRecordSet = objCommand.Execute
    
    objRecordSet.MoveFirst
    Do Until objRecordSet.EOF
        retval = objRecordSet.Fields(att).value    
        objRecordSet.MoveNext
    Loop
    GetAttribute = retval
End Function


'----------------------------------------------------------------
' function: force logoff from local computer
'----------------------------------------------------------------


Function Logoff()
    Logoff = -1
    wscript.Echo "Logging off..."
    On Error Resume Next
    Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate,(Shutdown)}!\\.\root\cimv2")
    Set colOs = objWMI.ExecQuery("Select * from Win32_OperatingSystem")
    If err.Number = 0 Then
        For Each objOs in colOs
            ' See: http://msdn.microsoft.com/en-us/library/aa394058(VS.85).aspx
            Logoff = objOs.Win32Shutdown(osForcedLogoff,0)
            ' WScript.Echo objOs.Name
        Next
    End If
End Function

Thursday, February 10, 2011

LDAP / AD script stuff

Given an LDAP distinguishedName (dn) value, such as "CN=JohnDoe,OU=Sales,OU=Users,OU=Corporate,DC=contoso,DC=msft" you can strip out just the value assigned to the first CN= set using VBscript as follows:

CleanName = Replace(Split(DN,",")(0), "CN=", "")

This will return "JohnDoe"

Wednesday, August 19, 2009

Querying Active Directory vs SQL Server

Warning: This is a meandering rant.  Drink plenty of caffeine to avoid crashing into a guard rail.

I’ve had a few friends/co-workers hit me up about this subject recently.  For what reason – I don’t know.  Why me?  I’m only guessing it’s something to do with my extensive work gluing together web services and web apps using SQL Server, Active Directory and plumbing in things like WMI/SWBEM/WQL and so on.  My background is more in software development than in systems engineering, so I look at something like Active Directory and immediately want to take it apart and see what makes it tick.  Kerberos, KCC topology, links and replication processes, DFSR, events and logs, WMI and ADSI, and LDP.  Everything in the software world looks like Legos to me.  Microsoft really doesn’t get the credit they deserve for, as Paul Thurrott says: building a business of building platforms.  They are the king of platforms.  And nobody goes to the extent they do to document their numerous platforms.

Where was I?  Oh yeah, sorry.  I was blabbering about myself, as if anyone cares.

So, the issue is what are the concerns and what are the trade-offs between querying Active Directory directly as opposed to using SQL Server as a middle man (between the data and the consuming service or application).  The answer is (drum roll please):  it depends.

For most general information gathering, it’s fine to query AD directly.  The AD schema and various tentacles into it are actually tuned more for read/fetch operations than for write/modify operations.  That’s because the nature of AD is authentication and look-up.  Both of those chores involve far more reading than writing.  Don’t believe me?  Just think about how often you logon to you network as opposed to how often you change your password or some property within your user account.  Think about how often you boot up a computer as opposed to how often you join it to the domain.  That’s one of the reasons Microsoft didn’t push harder to move the AD partition suite into a relational database.  Some argued for doing that, but really, the cost factor just doesn’t offset the gains in performance or capability.

That said (yes, all of that… phew!) there are times when it makes sense to introduce a data store in the middle.  This can be XML, SQL, or just about anything.  Why?  Because maybe you want to maintain a faster performing cache with a subset of the overall data store.  You can dynamically filter LDAP, WMI, and so on, sure.  But sometimes it helps to at least tape things together a bit.  An example?  Ok.  One project I worked on involved pulling aggregate information about employees from several data stores, including AD.  The end result needed to be able to instantly display employee data, as well as AD and Exchange data, and things about the computer that employee uses.  Rather than having the web app reach out and query all of the domain controllers (to get the last logon data, for example), I used a SQL process to fetch and update that periodically and that allowed the web application to fetch the end result much quicker and with more reliability.

A relational database is also great for storing historical information in ways that are often cumbersome to do with native platform APIs.  One example is monitoring AD replication status.  A project I worked on involved running REPADMIN, NETDIAG and DCDIAG, on each of the clients domain controllers and feeding parsed results into a SQL database on a recurring schedule.  This is very similar to what MOM/SCOM does obviously, but you can do much of the same yourself with a little elbow grease (and much cheaper).  The EVENTTRIGGERS command also helps in this regard.  When collected into one data store like this, it is much more efficient to analyze it, query it and run secondary processes from it (alerts, reports, automated diagnostics and repair, etc.).

As I said, this article is really a frame of thought about how to approach these two avenues, more than giving discrete examples.  I may post some real examples if anyone is interested.  Some of them might take some serious prep work to shoehorn into a blog post.

Monday, August 10, 2009

ASP: Secure LDAP Query for User Properties

For those of you working in an environment where anonymous LDAP queries are disabled, you may find that trying to perform “standard” queries against Active Directory from an ASP page doesn’t work.  This is usually the result of the IIS service account not having domain access.  The default configuration of IIS is to run as IUSR_Name which is a local account and has no domain group memberships. 

You could modify IIS to use a domain “service” (aka “proxy”) account, but that’s not a panacea either.  To get around this safely, you can use a secure LDAP query with a domain proxy account instead.  This way, your web server remains secure, while you allow your application to open up specific connections for specific needs.  If you secure the folder and share permissions to your web application code you should be in good shape.  If you pick through this code, you’ll notice I picked a pipe symbol “|” as my delimiter within each field=value pairing.  That was arbitrary and there’s nothing wrong with picking a different character.  In fact, you can use whatever you like as long as you separate each pair with a different symbol that whatever you use to separate the field=value matching pairs.  I don’t recommend a comma or semi-colon since those can often occur with data values and could cause weirdness (nothing wrong with weirdness, but not within programming results).

'----------------------------------------------------------------
' example:
' x = GetUserData("abc00", "Name, ADsPath, mail, department, givenName, sn")
'
' For each v in Split(x, vbTab)
' response.write v & "
"
' Next
'----------------------------------------------------------------


Const ldap_user = "domain\proxy_user"
Const ldap_pwd = "P@ssw0rD##123"
Const ADS_SCOPE_SUBTREE = 2

Function GetUserData(uid, fields)
Dim objConnection, objComment, objRecordSet
Dim retval : retval = ""
Dim i, fieldname, strvalue
On Error Resume Next
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Properties("User ID") = ldap_user
objConnection.Properties("Password") = ldap_pwd
objConnection.Properties("Encrypt Password") = TRUE
objConnection.Properties("ADSI Flag") = 1
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = "SELECT " & fields & _
" FROM 'LDAP://DC=nns,DC=com' " & _
"WHERE objectCategory='user' AND sAMAccountName='" & uid & "'"
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
Do Until objRecordSet.EOF
For i = 0 to objRecordSet.Fields.Count -1
fieldname = objRecordSet.Fields(i).Name
strvalue = objRecordSet.Fields(i).Value
If retval <> "" Then
retval = retval & vbTab & fieldname & "|" & strValue
Else
retval = fieldname & "|" & strValue
End If
Next
objRecordSet.MoveNext
Loop
GetUserData = retval
End Function

Tuesday, January 20, 2009

Samba vs Active Directory: NFL vs High School?

Don Jones posted a nice blog comment about some of the issues involved with choosing an open source alternative to Active Directory while maintaining authentication interoperability (say that ten times fast!) with Windows clients.  I agree with this comments, but he forgot one of THE most powerful aspects of AD: Group Policy.  

There's nothing truly like Group Policy in the Linux world that I've found.  There are some open source projects that mimick certain aspects, but nothing matches it completely.  People try to put Group Policy into a logical "box" as a one-trick pony.  Usually saying that it's only used to "deploy settings" or "install software", but it's much more than that.  

The integration of Group Policy with LDAP structures (OU's) and object classes, WMI filtering, Inheritence and Blocking, Modeling tools,  RSoP tools, and the more recent addition of Group Policy Preferences and Powershell cmdlets, all make Group Policy a serious force to recon with on its own.  It's unfair, and woefully inaccurate, to compare AD with Samba unless you restrict the discussion to nothing more than authentication (e.g. Kerberos and NTLM, etc.).  I mean, as far as using Samba for AD authentication is concerned: you can, but why?  If you're working in an environment that feels the need for central authentication (and more) and you can't afford a license of Windows Server, you're in really bad shape.

Tuesday, September 2, 2008

Sysinternals AD Insight and DSQUERY

Just out of curiosity, I ran the ADInsight utility from Microsoft Sysinternals to see what goes on when you run DSQUERY to fetch information from an LDAP call to Active Directory. If you're not familiar with ADInsight, and you have an Active Directory environment to play with, go download the Sysinternals toolkit and check it out. Basically, it watches communications using LDAP and shows each event as it transpires. As with Process Explorer, Process Monitor, RegMon and the others, you can pause the realtime captures and analyze them more closely.

The command I executed was "DSQUERY COMPUTER" which dumps the LDAP distinguishedName values for every computer in my Active Directory domain. Here's the actual command query and results I got back.

C:\>dsquery computer
"CN=dc1,OU=Domain Controllers,DC=steinfamily,DC=home"
"CN=comp1,OU=Desktops,DC=steinfamily,DC=home"
"CN=comp2,OU=Desktops,DC=steinfamily,DC=home"
"CN=laptop,OU=Desktops,DC=steinfamily,DC=home"
"CN=comp3,CN=Computers,DC=steinfamily,DC=home"

There was a noticable time lag after I hit ENTER before it actually returned the results. But I wanted to know where the lag was occurring. Was it during the connection request, the bind request, the query submission or the results return? It was during the initial LDAP connection request, which is line 9 in the capture results (see below) which paused for 366 milliseconds...

# Timestamp Process:PID Request Type Session Event ID Domain Controller User Input Output Result Duration
1 62.591s dsquery.exe:4508 initialize Sync Session 1 Event 1 OURHOME.home:389 301.186ms
2 62.892s dsquery.exe:4508 get option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_GETDSNAME_FLAGS 71.517us
3 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_GETDSNAME_FLAGS 34.292us
4 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_PROMPT_CREDENTIALS 31.638us
5 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_SIGN 30.870us
6 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_ENCRYPT 30.870us
7 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_PROTOCOL_VERSION 33.803us
8 62.892s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home LDAP_OPT_HOST_NAME 36.667us
9 62.893s dsquery.exe:4508 connect Sync Session 1 Event 1 OURHOME.home Anonymous User Timeout INFINITE 366.592ms
10 63.259s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home Anonymous User LDAP_OPT_REFERRAL_CALLBACK 28.076us
11 63.259s dsquery.exe:4508 set option Sync Session 1 Event 1 OURHOME.home Anonymous User LDAP_OPT_REFERRALS 20.743us
12 63.259s dsquery.exe:4508 bind Sync Session 1 Event 1 OURHOME.home OURHOME\user100 OURHOME\user100 126.909ms
13 63.386s dsquery.exe:4508 search Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:LDAP_SCOPE_BASE:(objectClass=*) 160.709ms
15 63.547s dsquery.exe:4508 first entry Sync Session 1 Event 2 OURHOME.home OURHOME\user100 0x003badf0 0x003badf0 21.790us
16 63.547s dsquery.exe:4508 first attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 currentTime 25.771us
17 63.547s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:currentTime 09/02/2008 13:58:26 + 0Z 28.495us
19 63.547s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 subschemaSubentry 25.283us
20 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:subschemaSubentry CN=Aggregate,CN=Schema,CN=Configuration,DC=OURHOME,DC=home 27.029us
22 63.548s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 dsServiceName 27.797us
23 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:dsServiceName CN=NTDS Settings,CN=DC1,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=OURHOME,DC=home 28.006us
25 63.548s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 namingContexts 26.330us 26 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:namingContexts DC=OURHOME,DC=home 42.603us
28 63.548s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 defaultNamingContext 25.981us
29 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:defaultNamingContext DC=OURHOME,DC=home 26.260us
31 63.548s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 schemaNamingContext 24.235us
32 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:schemaNamingContext CN=Schema,CN=Configuration,DC=OURHOME,DC=home 30.660us
34 63.548s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 configurationNamingContext 28.356us 35 63.548s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:configurationNamingContext CN=Configuration,DC=OURHOME,DC=home 25.283us
37 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 rootDomainNamingContext 24.794us
38 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE: rootDomainNamingContext DC=OURHOME,DC=home 25.632us
40 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 supportedControl 24.375us
41 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:supportedControl 1.2.840.113556.1.4.319 63.556us
43 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 supportedLDAPVersion 24.305us
44 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:supportedLDAPVersion 3 25.981us
46 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 supportedLDAPPolicies 24.724us 47 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:supportedLDAPPolicies MaxPoolThreads 35.829us
49 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 highestCommittedUSN 29.263us
50 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:highestCommittedUSN 74539 35.549us
52 63.549s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 supportedSASLMechanisms 29.263us
53 63.549s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:supportedSASLMechanisms GSSAPI 32.197us
55 63.550s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 dnsHostName 28.914us
56 63.550s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:dnsHostName dc1.OURHOME.home 29.962us
58 63.550s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 ldapServiceName 28.565us
59 63.550s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:ldapServiceName OURHOME.home:DC1$@OURHOME.HOME 36.178us
61 63.550s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 serverName 27.937us
62 63.550s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:serverName CN=DC1,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=OURHOME,DC=home 29.613us
64 63.550s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 supportedCapabilities 28.076us 65 63.550s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:supportedCapabilities 1.2.840.113556.1.4.800 31.917us
67 63.550s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 isSynchronized 26.610us
68 63.550s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:isSynchronized TRUE 33.733us 70 63.551s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 isGlobalCatalogReady 26.749us
71 63.551s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:isGlobalCatalogReady TRUE 27.657us
73 63.551s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 domainFunctionality 26.959us
74 63.551s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:domainFunctionality 3 34.292us
76 63.551s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 forestFunctionality 25.422us
77 63.551s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:forestFunctionality 3 31.429us
79 63.551s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 domainControllerFunctionality 25.702us
80 63.551s dsquery.exe:4508 get values Sync Session 1 Event 2 OURHOME.home OURHOME\user100 RootDSE:domainControllerFunctionality 3 26.679us
82 63.551s dsquery.exe:4508 next attribute Sync Session 1 Event 2 OURHOME.home OURHOME\user100 No more attributes 24.095us
83 63.552s dsquery.exe:4508 search Sync Session 1 Event 3 OURHOME.home OURHOME\user100 OURHOME.home\Configuration:LDAP_SCOPE_BASE:(objectClass=*) 1.107ms
84 63.553s dsquery.exe:4508 first entry Sync Session 1 Event 3 OURHOME.home OURHOME\user100 0x003bae70 0x003bae70 35.898us
85 63.553s dsquery.exe:4508 get values Sync Session 1 Event 3 OURHOME.home OURHOME\user100 OURHOME.home\Configuration:objectClass top 42.952us
87 63.558s dsquery.exe:4508 search Sync Session 1 Event 4 OURHOME.home OURHOME\user100 OURHOME.home\Configuration:LDAP_SCOPE_BASE:(objectClass=*) 1.669ms
88 63.560s dsquery.exe:4508 first entry Sync Session 1 Event 4 OURHOME.home OURHOME\user100 0x003badf0 0x003badf0 27.517us
89 63.560s dsquery.exe:4508 get option Sync Session 1 Event 1 OURHOME.home OURHOME\user100 LDAP_OPT_HOST_NAME 23.187us
90 73.560s dsquery.exe:4508 initialize Sync Session 2 Event 5 dc1.OURHOME.home:389 198.698us
91 73.561s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_GETDSNAME_FLAGS 133.117us
92 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_GETDSNAME_FLAGS 21.022us
93 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_PROMPT_CREDENTIALS 20.254us
94 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_SIGN 25.003us
95 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_ENCRYPT 29.403us
96 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_PROTOCOL_VERSION 21.232us
97 73.561s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home LDAP_OPT_AREC_EXCLUSIVE 21.581us
98 73.561s dsquery.exe:4508 connect Sync Session 2 Event 5 dc1.OURHOME.home Anonymous User Timeout INFINITE 2.476ms 99 73.564s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home Anonymous User LDAP_OPT_REFERRAL_CALLBACK 39.321us 100 73.564s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home Anonymous User LDAP_OPT_REFERRALS 31.010us 101 73.564s dsquery.exe:4508 bind Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 OURHOME\user100 3.825ms 102 73.568s dsquery.exe:4508 search Sync Session 2 Event 6 dc1.OURHOME.home OURHOME\user100 OURHOME.home:LDAP_SCOPE_BASE:(objectClass=*) 5.063ms
103 73.573s dsquery.exe:4508 first entry Sync Session 2 Event 6 dc1.OURHOME.home OURHOME\user100 0x003bb630 0x003bb630 42.044us 104 73.573s dsquery.exe:4508 get values Sync Session 2 Event 6 dc1.OURHOME.home OURHOME\user100 OURHOME.home:objectClass top 49.168us 1
06 73.574s dsquery.exe:4508 search Sync Session 2 Event 7 dc1.OURHOME.home OURHOME\user100 RootDSE:LDAP_SCOPE_BASE:(objectClass=*) 2.757ms 107 73.577s dsquery.exe:4508 first entry Sync Session 2 Event 7 dc1.OURHOME.home OURHOME\user100 0x003bb630 0x003bb630 43.022us
108 73.577s dsquery.exe:4508 get values Sync Session 2 Event 7 dc1.OURHOME.home OURHOME\user100 RootDSE:subschemaSubentry CN=Aggregate,CN=Schema,CN=Configuration,DC=OURHOME,DC=home 50.914us
110 73.577s dsquery.exe:4508 get values Sync Session 2 Event 7 dc1.OURHOME.home OURHOME\user100 RootDSE:supportedControl 1.2.840.113556.1.4.319 70.749us
112 73.577s dsquery.exe:4508 get values Sync Session 2 Event 7 dc1.OURHOME.home OURHOME\user100 RootDSE:supportedCapabilities 1.2.840.113556.1.4.800 54.057us
114 73.587s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 33.175us
115 73.587s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 29.054us
116 73.587s dsquery.exe:4508 init search page Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home:LDAP_SCOPE_SUBTREE:(&(objectCategory=Computer)) 46.375us
117 73.587s dsquery.exe:4508 get next page Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 Timeout: INFINITE; Limit to 50 entries/page Est. result set count = 0 85.781ms
118 73.673s dsquery.exe:4508 first entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003bab70 0x003bab70 36.806us
119 73.673s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 27.168us
120 73.673s dsquery.exe:4508 search Sync Session 2 Event 9 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Configuration\Schema\Aggregate:LDAP_SCOPE_BASE:objectClass=* 3.141ms
121 73.676s dsquery.exe:4508 first entry Sync Session 2 Event 9 dc1.OURHOME.home OURHOME\user100 0x003baa70 0x003baa70 49.657us
122 73.676s dsquery.exe:4508 get values Sync Session 2 Event 9 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Configuration\Schema\Aggregate:modifyTimeStamp 08/16/2008 14:52:40 + 0Z 41.206us
124 73.850s dsquery.exe:4508 get values Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Domain Controllers\DC1:distinguishedName CN=DC1,OU=Domain Controllers,DC=OURHOME,DC=home 133.467us
126 73.854s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 92.121us 127 73.854s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 33.105us 128 73.854s dsquery.exe:4508 next entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003bab70 0x003baa30 33.524us
129 73.854s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 23.816us 130 73.855s dsquery.exe:4508 get values Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Desktops\comp1:distinguishedName CN=comp1,OU=Desktops,DC=OURHOME,DC=home 40.927us 132 73.856s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 87.022us
133 73.856s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 76.127us 134 73.857s dsquery.exe:4508 next entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003baa30 0x003ba9f0 25.352us
135 73.857s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 21.371us 136 73.857s dsquery.exe:4508 get values Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Desktops\comp2:distinguishedName CN=comp2,OU=Desktops,DC=OURHOME,DC=home 35.619us 138 73.858s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 75.010us
139 73.859s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 22.908us
140 73.859s dsquery.exe:4508 next entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003ba9f0 0x003ba9b0 22.070us
141 73.859s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 21.651us
142 73.859s dsquery.exe:4508 get values Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Desktops\laptop:distinguishedName CN=laptop,OU=Desktops,DC=OURHOME,DC=home 36.737us
144 73.860s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 88.629us
145 73.860s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 42.184us
146 73.860s dsquery.exe:4508 next entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003ba9b0 0x003ba830 44.698us
147 73.861s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 30.870us
148 73.861s dsquery.exe:4508 get values Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 OURHOME.home\Computers\comp3:distinguishedName CN=comp3,CN=Computers,DC=OURHOME,DC=home 39.670us 150 73.863s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 97.708us
151 73.863s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 30.870us
152 73.863s dsquery.exe:4508 next entry Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003ba830 No more entries 35.200us
153 73.863s dsquery.exe:4508 get next page Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 Timeout: INFINITE; Limit to 50 entries/page NO_RESULTS_RETURNED NO_RESULTS_RETURNED 32.895us
154 73.863s dsquery.exe:4508 get option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_SERVER_ERROR 33.175us
155 73.863s dsquery.exe:4508 set option Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 LDAP_OPT_REFERRALS 33.803us
156 73.863s dsquery.exe:4508 abandon search page Sync Session 2 Event 8 dc1.OURHOME.home OURHOME\user100 0x003b7ec8 41.346us
157 73.863s dsquery.exe:4508 unbind Sync Session 2 Event 5 dc1.OURHOME.home OURHOME\user100 Session 0x003ce424 549.791us
158 73.864s dsquery.exe:4508 unbind Sync Session 1 Event 1 OURHOME.home OURHOME\user100 Session 0x003b3494 2.556ms

As you can see, for such a seemingly simple request there's a lot going on under the hood. This is very common in any operating system environment actually. If you perform a network capture, or a file access transaction, you will see very similar behaviors. In case you're wondering, I didn't strip out any line items. It actually reports the line numbering in the exact way shown above. Some numbers are skipped and I'm still not 100% as to why, but I suspect it's reporting a subset of events out of a larger collective.

If you went to college for a CE, CS or IS degree, this stuff will bring back painful memories of inspecting things. Much like doing proofs in a Calculus course. Ugh. Makes me want to drink more just thinking about it.