Skip to main content

Active Directory Searcher - Part 3

In the final part of this series, I will look at dealing with the lastlogontimestamp attribute and easily outputting the data you have gathered to a file.


Part 2 left us with :
$domains = [System.DirectoryServices.ActiveDirectory.forest]::getcurrentforest().get_domains()
$ObjectCategory = "user"
$ObjectProplist = "name","samaccountname","whencreated"
$LdapQuery = "(&(objectCategory=$ObjectCategory)(name=ad*))"
foreach ($domain in $domains){
      ($domain).name
      $LDAPdomain = $domain.GetDirectoryEntry()
      $Searcher = New-Object System.DirectoryServices.DirectorySearcher($LDAPdomain, $LdapQuery, $ObjectProplist,"subtree")
      $Searcher.pagesize = 1000
      $Results = $Searcher.FindAll()
      foreach ($Object in $Results){
            foreach ($prop in $ObjectProplist){
                  $Object.Properties.$prop
                 }
      }
}
If you have tried to manipulate lastlogontimestamp in VB, you will know it is not a friendly attribute.  That is until powershell gets involved.  If you output the value of lastlogontimestamp you should get somethin like this : 128979678074825862.  Not very helpful really, unless you know how many milliseconds it has been since 1 January 1601.  Once again, .net comes in to help us. the method [DateTime]::FromFileTimeUTC() can be used to convert the value into something more readable.  The above number for example converts to "21/09/2009 12:50:07 AM".  Much better!


As we are dealing with a foreach loop to dynamically output all our properties, we can use a switch statement to deal with the lastlogontimestamp property separately from the rest of the attributes.  (NOTE : we could use an if statement but putting a switch in now makes further exceptions much easier to add later on).


so the (partial) script looks like this :

foreach ($prop in $ObjectProplist){


     switch ($prop){
          lastlogontimestamp {[DateTime]::FromFileTimeUTC($Object.Properties.lastlogontimestamp[0])}
          Default {$Object.Properties.$prop}
     }
}
NOTE : I will be posting useful ldap queries soon which will include how to use [DateTime]::ToFileTimeUTC to query on lastlogontimestamp.


As we have now converted our data to useful information, let's output it using a few of powershells built in mechanisms.  When I first started doing this searcher, i was still very much in the VB way of thinking.  I created a string with all the variables in it seperated with commas and used add-content to dump to a file throughout the script.  My friend and old work colleague Josh, showed me the way forward by using Arrays.


First we need to create an array full of the variables that we are searching for.  Here we can use $objectproplist again ( I told you it would come in handy!) :

$Store = "" | select $ObjectProplist
If you now output $Store you will see that all the $ObjectProplist values are now properties of the array $store :

Very handy!

We can now use our foreach loop and switch to populate the $Store properties :

PS C:\> $Store



name adspath lastlogontimestamp


---- ------- ------------------

We now have a single array for each returned value, that unfortunately gets overwritten with every new object.  We need a means of storing each array .... how about another array?
foreach ($prop in $ObjectProplist){
$Store = "" | select $ObjectProplist    
switch ($prop){
          lastlogontimestamp {$Store.$prop = [DateTime]::FromFileTimeUTC($Object.Properties.lastlogontimestamp[0])}
          Default {$Store.$prop = $Object.Properties.$prop}
     }
}


If we define a new array a the begining of the script $Collection = @(), then on completion of each result loop, we can output the $store into the collection :

$Collection = @()
$domains = [System.DirectoryServices.ActiveDirectory.forest]::getcurrentforest().get_domains()
$ObjectCategory = "user"
$ObjectProplist = "name","samaccountname","whencreated"
$LdapQuery = "(&(objectCategory=$ObjectCategory)(name=ad*))"
foreach ($domain in $domains){
      ($domain).name
      $LDAPdomain = $domain.GetDirectoryEntry()
      $Searcher = New-Object System.DirectoryServices.DirectorySearcher($LDAPdomain, $LdapQuery, $ObjectProplist,"subtree")
      $Searcher.pagesize = 1000
      $Results = $Searcher.FindAll()
      foreach ($Object in $Results){
         $Store = "" | select $ObjectProplist 
         foreach ($prop in $ObjectProplist){
              
               switch ($prop){

               lastlogontimestamp {$Store.$prop = [DateTime]::FromFileTimeUTC($Object.Properties.lastlogontimestamp[0])}
               Default {$Store.$prop = $Object.Properties.$prop}
          }
     }
    $Collection += $Store
        }
}
nearly done!  All we need to do is output to csv :

$Collection | export-csv "outfile.csv" -NoTypeInformation
Due to the nature of powershell, everything returned is an object, you will sometimes get an object definition in your csv file rather than the actual value.  By adding this to your default case in your switch statement, a string will be output instead.  (most objects have the tostring() method!)




Default {$Store.$prop = $($Object.Properties.$prop).tostring()}


so the whole script looks like this :

$Collection = @()
$domains = [System.DirectoryServices.ActiveDirectory.forest]::getcurrentforest().get_domains()
$ObjectCategory = "user"
$ObjectProplist = "name","samaccountname","whencreated"
$LdapQuery = "(&(objectCategory=$ObjectCategory)(name=ad*))"
foreach ($domain in $domains){
      ($domain).name
      $LDAPdomain = $domain.GetDirectoryEntry()
      $Searcher = New-Object System.DirectoryServices.DirectorySearcher($LDAPdomain, $LdapQuery, $ObjectProplist,"subtree")
      $Searcher.pagesize = 1000
      $Results = $Searcher.FindAll()
      foreach ($Object in $Results){
          $Store = "" | select $ObjectProplist
          foreach ($prop in $ObjectProplist){
              switch ($prop){

               lastlogontimestamp {$Store.$prop = [DateTime]::FromFileTimeUTC($Object.Properties.lastlogontimestamp[0])}
               Default {$Store.$prop = $($Object.Properties.$prop).tostring()}
          }
     }
    $Collection += $Store
        }
}
$Collection | export-csv "outfile.csv" -NoTypeInformation
So that's about it.  You can go further and use the trap statement to catch errors or deal with exceptions in the switch statement, or if you encounter the default 120 second LDAP timeout with a big query (as I have) you can start doing recursive calls with a function which enables you to really start controlling where you search in your OU structure.


Let me know if the scripts haven't translated to Blog 100% as I am still getting used to the editor!


Cheers!

Comments

  1. you write:
    ================
    foreach ($Object in $Results){

    foreach ($prop in $ObjectProplist){
    $Store = "" | select $ObjectProplist

    ===============
    But $Store shouldn't re-initialize itself within the $prop loop, it should be

    foreach ($Object in $Results){
    $Store = "" | select $ObjectProplist

    foreach ($prop in $ObjectProplist){
    ...

    ReplyDelete
  2. Hi George

    Thanks for the feedback, script updated!

    regards

    Adam

    ReplyDelete

Post a Comment

Popular posts from this blog

Enable Powershell Remoting (WinRM) via Group Policy

I have been doing some testing on enabling WinRM via group policy, being that WinRM is the service that Powershell v2 sets up it remoting capabilities. Here are the GPO settings that you need to configure WinRM .... set the winrm service to auto start Computer Configuration \ Policies \ Windows Settings \ Security Settings \ System Services Windows Remote Management (WS-Management)  set Startup Mode to Automatic start the service incorporated in to the above - you may need a restart. create a winrm listener Computer Configuration / Policies / Administrative Templates / Windows Components / Windows Remote Management (WinRM) / WinRM Service / Allow automatic configuration of listeners IPv4 filter: * * is listen on all addresses, or if you only want a particular IP address to respond use an iprange eg 10.1.1.1-10.1.1.254 - don't forget that this IP range has to be valid for all hosts that fall in the scope of the GPO you are creating.  You can use 1...

Assigning Permissions - AGDLP

AGDLP It seems I have been mildly distracted away from the title of this blog site.   It does say AD Admin, but I seem to have been taken away by file system stuff.   I have to say, it has all been worthwhile, but it’s probably time I got back to the real heart of what I do. There are probably a million permission assigning advice pages, but I thought I would put another one out there after referring to AGDLP in my last post. So, what is this all about – AGDLP.   Well, it is something I learned in my MCSE 2003 studies and has become ingrained into my ideals since.   As a contractor, I get to move job often.   This enables me to forge opinions on how to configure things in a domain, and more importantly how NOT to configure things. AGDLP is definitely on the to do list…for anyone in any size domain or forest, as it follows some very basic principals.   I will explain these whilst I go through what AGDPL stands for. A A is for...

Finding out what 'SearchFlags' are set on you AD attributes

Whilst doing some research into indexed attributes, I posted this  a while back on how to find your index attributes.  Since then, I have looked a little deeper into what indexing really means and found this excellent explanation on the numbers that can be found in the searchflags attribute of a schema object. Using Florian’s reference, I built the following script (which is both powershell v1 and v2 compatible) to get the schema attributes from the forest schema and return (among other things) the breakdown of your attributes search flags. $forest = [System.DirectoryServices.ActiveDirectory.forest]::getcurrentforest() $schema = [ADSI]('LDAP://CN=Schema,CN=Configuration,dc=' + ($($forest).name -replace "[.]",",dc=")) $attributes = $schema.psbase.children | where {$_.objectClass -eq "attributeSchema"} $collection = @() foreach ($attr in $attributes){ $store = "" | select "Name","lDAPDisplayName","singlev...