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

PowerShell 3 behavioural change

It's taken me way too long to get into PowerShell 3, I guess opportunity hasn't shown it's self until now and so, here, my V3 journey begins.

I was asked to debug a script that would run fine in PS v2 and not in v3.  The issue was a that a variable length was being checked and was failing in v3.  This is why...

In v2 if a variable is undefined, this test returns false

PS C:\windows\system32> $var.length -eq 0
False

In v3 the same test returns true....

PS C:\windows\system32> $var.length -eq 0
True

Not a biggie, but as in this case, a script has broken so something to consider!

cheers

Adam

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 10.1.1.1 - 10.1.1.254,10.1.1.3 - 10.1.4.254 …

compare-object in Powershell - comparing mulitple values

I'm starting to use compare-object more and more, and one thing I noticed, is that you can compare 2 objects based on multiple attributes. here is how it is constructed...
Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -Property a,b,c,d,eIf a,b,c and d are the same, but e is different, compare object will return a difference. In the following example, I use "-eq $null" as a check because by default compare-object returns $null if the objects are the same.
#create an array of objects to check against

$collection = @()
foreach ($entry in ("aaaaa","bbbbb","ccccc","ddddd")){
   $store = "" | select "a","b","c","d","e"
   $store.a = $entry*1
   $store.b = $entry*2
   $store.c = $entry*3
   $store.d = $entry*4
   $store.e = $entry*5
   $collection += $store
}

#create an object similar to those in the array
$object = "" | select "a","b…