Posts RSS Comments RSS 117 Posts and 170 Comments till now

Phase 3 of S.DS.P “CSV Output” (as much as 21 sec faster!)

In the next phase I wanted to actually get some data back and display it. Because I just wanted to test performance I kept the test as close as possible. ADFind has the built in ability to output a csv file so I mimicked his output. I also added two properties to return name and sAMAccountName. For those interested, my next step will be to output objects instead of text (really the whole point.)

My expectation for this test was that ADFind would pull ahead due to the processing of each record, but I was pleasantly surprised when the script was actually faster in all environments except 700k Environment. I will let joe explain why his is slower.

Testing
- I ran each 10x in a Row (as joe Suggested) in its own environement
- ADFind I tested using CMD.exe and used ptime.exe for time measurements.
- Get-DSPObject I used Measure-Command and outputed only TotalSeconds

UPDATED!: I had several people suggest that I should have a consolidated view of the results. So here it is. I took the Average from each test and put into and Excel spreadsheet and then plotted on a chart.

Here is the screenshot (lower is better)
DSP vs ADFind Chart

400k objects in a Prod Quality VM running Win2008 RTM 64bit (local)
Average: ADFind = 90.45
Average: DSP = 68.79
Winner: DSP 21.66 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 70.455s
Execution time: 69.551s
Execution time: 68.466s
Execution time: 68.085s
Execution time: 68.611s
Execution time: 68.179s
Execution time: 68.837s
Execution time: 68.670s
Execution time: 69.105s
Execution time: 67.994s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 100.538 s
Execution time: 109.511 s
Execution time: 92.499 s
Execution time: 96.063 s
Execution time: 91.601 s
Execution time: 80.693 s
Execution time: 81.551 s
Execution time: 81.044 s
Execution time: 90.945 s
Execution time: 80.143 s

400k objects in a Prod Quality VM running Win2008 RTM 64bit (remote)
Average: ADFind = 77.18
Average: DSP = 64.23
Winner: DSP 13 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 67.065 s
Execution time: 63.871 s
Execution time: 63.330 s
Execution time: 63.027 s
Execution time: 62.630 s
Execution time: 64.692 s
Execution time: 64.451 s
Execution time: 64.450 s
Execution time: 64.594 s
Execution time: 64.219 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 77.280 s
Execution time: 77.616 s
Execution time: 77.304 s
Execution time: 76.899 s
Execution time: 77.426 s
Execution time: 76.773 s
Execution time: 76.699 s
Execution time: 77.445 s
Execution time: 77.463 s
Execution time: 76.912 s

700k objects on a Physical machine Win2k3 x86
Average: ADFind = 101.383
Average: DSP = 109.600
Winner: ADFind 8.21 secs faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 111.088s
Execution time: 109.962s
Execution time: 110.112s
Execution time: 110.832s
Execution time: 110.167s
Execution time: 109.853s
Execution time: 109.757s
Execution time: 110.387s
Execution time: 109.070s
Execution time: 109.065s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 101.571 s
Execution time: 101.313 s
Execution time: 101.386 s
Execution time: 101.593 s
Execution time: 101.539 s
Execution time: 100.917 s
Execution time: 101.020 s
Execution time: 101.286 s
Execution time: 101.544 s
Execution time: 101.667 s

300k Objects on Physical Win2k3 x86 (Remote)
Average: ADFind = 49.90
Average: DSP = 44.44
Winner: DSP 5 secs Faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 44.782 s
Execution time: 44.316 s
Execution time: 44.473 s
Execution time: 44.322 s
Execution time: 44.380 s
Execution time: 44.440 s
Execution time: 44.394 s
Execution time: 44.544 s
Execution time: 44.428 s
Execution time: 44.247 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 51.983 s
Execution time: 51.937 s
Execution time: 51.810 s
Execution time: 49.142 s
Execution time: 48.913 s
Execution time: 49.073 s
Execution time: 48.765 s
Execution time: 49.125 s
Execution time: 49.110 s
Execution time: 49.105 s

200k Objects on my Server at Home (Win2k8 x64)
Average: ADFind = 35.50
Average: DSP = 31.74
Winner: DSP 4 secs Faster

- Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 31.887 s
Execution time: 31.888 s
Execution time: 31.044 s
Execution time: 30.746 s
Execution time: 31.549 s
Execution time: 31.601 s
Execution time: 31.168 s
Execution time: 31.528 s
Execution time: 31.481 s
Execution time: 31.269 s
- adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 37.305 s
Execution time: 34.815 s
Execution time: 34.275 s
Execution time: 34.193 s
Execution time: 39.116 s
Execution time: 34.634 s
Execution time: 39.265 s
Execution time: 33.686 s
Execution time: 33.793 s
Execution time: 33.917 s

Here is the script I used for the DSP Tests

  1. function Get-DSPObject {
  2.     Param(
  3.             $filter = "(objectclass=user)",
  4.             $base = ([ADSI]"").distinguishedName,
  5.             $Server,
  6.             [int]$pageSize = 1000,
  7.             [string[]]$props = @("1.1"),
  8.             [switch]$noHeader,
  9.             [switch]$csv,
  10.             [switch]$count
  11.         )
  12.        
  13.     [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
  14.        
  15.     [int]$pageCount = 0
  16.     [int]$objcount = 0
  17.    
  18.     if(!$server){$server = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=","" }
  19.    
  20.     $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
  21.     $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  22.    
  23.     $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$subtree,$props)  
  24.     $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  25.     $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  26.     $searchRequest.Controls.add($pagedRequest) | out-null
  27.     $searchRequest.Controls.Add($searchOptions) | out-null
  28.    
  29.     # Output Prep
  30.     if($props -notcontains "1.1")
  31.     {
  32.         $MyProps = @()
  33.         foreach($prop in $props){$MyProps += $prop.ToLower()}
  34.         if($csv)
  35.         {
  36.             if(!$noHeader)
  37.             {
  38.                 $header = "distinguishedName"
  39.                 foreach($prop in $props){$header += ",$prop"}
  40.                 $header
  41.             }
  42.         }
  43.         else
  44.         {
  45.             $MyUserObj = New-Object System.Object
  46.             $MyUserObj | Add-Member -name distinguishedName -MemberType "NoteProperty" -value $null
  47.             foreach($prop in $props){$MyUserObj | Add-Member -name $prop -MemberType "NoteProperty" -value $null}
  48.         }
  49.     }
  50.    
  51.     # Process Pages
  52.     while ($True)
  53.     {
  54.         # Increment the pageCount by 1
  55.         $pageCount++
  56.    
  57.         # Cast the directory response into a SearchResponse object
  58.         $searchResponse = $connection.SendRequest($searchRequest)
  59.    
  60.         # Display the retrieved page number and the number of directory entries in the retrieved page
  61.         # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  62.         $objcount += ($searchResponse.entries).count
  63.        
  64.         # Display the entries within this page
  65.         if(!$count)
  66.         {
  67.             if($props -notcontains "1.1")
  68.             {
  69.                 foreach($entry in $searchResponse.Entries)
  70.                 {
  71.                     if($csv)
  72.                     {
  73.                         $results = "`"{0}`"" -f $entry.distinguishedName
  74.                         foreach($prop in $MyProps)
  75.                         {
  76.                             $results += ",`"{0}`"" -f ($entry.Attributes[$prop][0])
  77.                         }
  78.                         $results
  79.                     }
  80.                     else
  81.                     {
  82.                         $MyUserObj.distinguishedName = $entry.distinguishedName
  83.                         foreach($prop in $MyProps)
  84.                         {
  85.                             $MyUserObj."$prop" = $null
  86.                             $MyUserObj."$prop" = $entry.Attributes[$prop][0]
  87.                         }
  88.                         $MyUserObj
  89.                     }
  90.                 }
  91.             }
  92.             else{$searchResponse.Entries | select distinguishedName}
  93.         }
  94.        
  95.         # if this is true, there are no more pages to request
  96.         if ($searchResponse.Controls[0].Cookie.Length -eq 0){if($count){$objcount};break}
  97.    
  98.         # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
  99.         # page of data in the send request and cast the directory control into a PageResultResponseControl object
  100.         $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
  101.     }
  102. }
  103.  
  104. foreach($i in (1..10))
  105. {
  106.     $time = Measure-Command { Get-DSPObject -prop "name","sAMAccountName" -csv }
  107.     "Execution time: {0:n3} s" -f $time.TotalSeconds
  108. }

Further Testing with S.DS.P (~1 sec slower)

I have to say I am in awe. I never expected to get so close to ADFind.exe in performance, but if you look below you will notice how close s.ds.p really got.

Before I show the results I want to respond to some of joe’s comments on his blog entry here: PowerShell + S.DS.Protocols Versus AdFind…
btw… I STRONGLY recommend reading this entry… actually subscribing to his blog. While he does spout off random quotes… he provides some incredibly useful info from time to time.

note: References to joe as joe (case intentional) is not ment as disrespect, but simply the way joe refers to himself. If you have the pleasure of getting to know him… you will understand this.

[1] I understand fully that joe really wants .NET and Powershell to succeed, but finds it unlikely.

[2] My testing (although possibly not perfect) was much more precise and I assume his testing had to be skewed by something. Its just not possible. I tried this on 4 completely different systems. Different OS’s, x64 and x86, Hyper-v and ESX, and different domain sizes. By the end of the testing.. I ran approximately 600 test (although I only documented 240.)

[3] While I can appreciate his lack of faith in measure-command. It was the only way I could remove human error and maintain an unbiased count for each test. Measure-command’s overhead is maintained completely outside of the measured expression.

[4] MOST IMPORTANTLY: Perhaps the reason joe questions my purpose of doing these tests is because I did not make it clear. My purpose in these test was to determine if it is feasible to reach the same performance of adfind.exe in Powershell. Why? adfind.exe works great, why not just that. Simply put, if I can achieve close to the same performance, I’m able to get the speed while maintaining the benefit of dealing with objects. While this concept may escape people, once you grasp it… it is key to easy and fast scripts or commands. Nothing more, nothing less. While I will not argue I like a good debate, that is not my soul reason for being.

To be clear, this is only the first step. I now need to process some properties and see if s.ds.p/powershell can compete in that area. As I was with the count I am skeptical about achieving performance even close to adfind, but perhaps that will surprise me as the search did.

[5] My count was NOT off :P The logic of using userclass=* vs userclass=user was.

For those inclined to do so… Here is the COMPLETE output of the results without the average
Prod700kResults
Results Prod 400k VM
Small 200k Domain on Local Hardware
Small 200k Domain on VM

This first test was done in a LARGE environment with 700k+ userclass objects.

Prod 700k

This test was done on a production quality VM with approx 400k users.

Prod 400k

This test was on my Home Win2k8 DC. I only have about 200k userclass objects at home, but it is a beefy box.

Local 200k

This is on my laptop that has about 200k userclass objects as well.

VM 200k

Here are the scripts I ran for the testing

This is the actual worker code

  1. [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | Out-Null
  2.  
  3. $domainDN = ([ADSI]"").distinguishedName
  4. $domain = $domainDN -replace  ",","." -replace "dc=",""
  5.  
  6. [int]$pageCount = 0
  7. [int]$pageSize = 1000
  8. [int]$count = 0
  9.  
  10. $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
  11. $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  12. $filter = "(objectclass=*)"
  13.  
  14. $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,"1.1")  
  15. $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  16. $searchRequest.Controls.add($pagedRequest) | out-null
  17. $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  18. $searchRequest.Controls.Add($searchOptions) | out-null
  19.  
  20. while ($True)
  21. {
  22.     # Increment the pageCount by 1
  23.     $pageCount++
  24.  
  25.     # Cast the directory response into a SearchResponse object
  26.     $searchResponse = $connection.SendRequest($searchRequest)
  27.  
  28.     # Display the retrieved page number and the number of directory entries in the retrieved page
  29.     # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  30.     $count += ($searchResponse.entries).count
  31.    
  32.     # Display the entries within this page
  33.     $searchResponse.Entries | select distinguishedName
  34.     # if this is true, there are no more pages to request
  35.     if ($searchResponse.Controls[0].Cookie.Length -eq 0){write-Output $count;break}
  36.  
  37.     # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
  38.     # page of data in the send request and cast the directory control into a PageResultResponseControl object
  39.     $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
  40. }

Script I used to automate the 60 test per Environment.

  1. Param($count=30)
  2.  
  3. $dn = ([ADSI]"").distinguishedName
  4.  
  5. function TestOne {
  6.     Write-Host "  + Test ${i}.1"
  7.     Write-Host "    + Running ADFind Test"
  8.     $joeTime      = Measure-Command { D:\Scripts\adfind -b "$dn" -c -f "(objectclass=user)" 2>&1 | out-Null }
  9.     Write-Host "      - $($joetime.TotalSeconds)"
  10.     Write-Host "    + Running DSP Test"
  11.     $DSPTime11    = Measure-command { D:\Scripts\Test-DSProtocalsSP.ps1 }
  12.     Write-Host "      - $($DSPTime11.TotalSeconds)"
  13.     $myresults  = "" | select @{n="ADFind"           ;e={$joeTime.TotalSeconds}},
  14.                               @{n="DSP Using 1.1"    ;e={$DSPTime11.TotalSeconds}}
  15.     $myresults
  16. }
  17. function TestTwo {
  18.     Write-Host "  + Test ${i}.2"
  19.     Write-Host "    + Running DSP Test"
  20.     $DSPTime11    = Measure-command { D:\Scripts\Test-DSProtocalsSP.ps1 }
  21.     Write-Host "      - $($DSPTime11.TotalSeconds)"
  22.     Write-Host "    + Running ADFind Test"
  23.     $joeTime      = Measure-Command { D:\Scripts\adfind -b "$dn" -c -f "(objectclass=user)" 2>&1 | out-Null }
  24.     Write-Host "      - $($joetime.TotalSeconds)"
  25.     $myresults  = "" | select @{n="ADFind"           ;e={$joeTime.TotalSeconds}},
  26.                               @{n="DSP Using 1.1"    ;e={$DSPTime11.TotalSeconds}}
  27.     $myresults
  28. }
  29.  
  30. Write-Host
  31. for($i = 0 ; $i -le $count ; $i++)
  32. {
  33.     Write-Host "+ Test $i"
  34.     TestOne
  35.     TestTwo
  36. }
  37. Write-Host

My First Venture into S.DS.P and Powershell

There has be much debate and agony on the the slowness of System.DirectoryServices.DirectorySearcher Class. This has lead me down the path of playing with System.DirectoryServices.Protocols (aka s.ds.p.)

By far the best tool out there right now is ADFind by joe Richards. So I used this for my target (although I didnt expect to get close.)

Here is the Guide I used in my Journey:
Introduction to System.DirectoryServices.Protocols (S.DS.P)

I started by writing the script you will find below and tested in a domain with well over 700k users and one with 200K. I did the test three different times in each Domain changing the order so cache hits wouldn’t be an issue. I also use objectclass on all three so index wouldn’t be a factor. I should NOTE that my script ONLY returns the count ATM. I am going to add properties next.

As you will see by the test results below… I got pretty darn close to adfind.exe in regards to perf. I was quite impressed with S.DS.P. Now to be fair, this was just counting objects. I am sure adfind.exe will start sneaking ahead abit further when we start processing properties and such. Can’t wait to see! Understand these test were just to see what tests I should focus on. I will posting another entry on more extensive count test with just DSP Using 1.1 and Adfind.

Test 1 700K Users done Remote

DirectorySearcher : 125.9123257
ADFind : 46.3763349
DSP Using DN : 69.5628776
DSP Using 1.1 : 49.4458161

Test 2 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 46.4486472
DSP Using DN : 68.9255288
DSP Using 1.1 : 49.0780736

Test 3 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 47.9162918
DSP Using DN : 79.6885386
DSP Using 1.1 : 54.152966

Test 1 200K done Local

DirectorySearcher : 121.1063569
ADFind : 55.2775406
DSP Using DN : 67.897922
DSP Using 1.1 : 28.5441615

Test 2 200K done Local

DirectorySearcher : 80.0894455
ADFind : 23.558696
DSP Using DN : 54.3111576
DSP Using 1.1 : 42.3485998

Test 3 200K done Local

DirectorySearcher : 99.1125363
ADFind : 80.1497852
DSP Using DN : 64.3716824
DSP Using 1.1 : 64.1940421

Summary: adfind.exe was faster (by bout 4sec on Avg.) remotely and larger domain, but protocals was faster (by bout 8 sec on Avg.) local on smaller domain.
Remote

ADFind: 46.92
DSP Using 1.1: 50.89
DSP Using DN: 72.73
DirectorySearcher: 125.32

Local

DSP Using 1.1: 45.03
ADFind: 52.99
DSP Using DN: 62.19
DirectorySearcher: 100.10

Here the script I ran for the test and how I measured the commands. I am going to play with passing the stats control and see what the server says later.

  1. $SearcherExpression = @’
  2. $searcher = new-object System.DirectoryServices.DirectorySearcher([ADSI]"","(objectclass=user)",@("distinguishedName"))
  3. $searcher.pagesize = 1000
  4. $searcher.findall()
  5. ‘@
  6.  
  7. Write-Host "Test 1"
  8. Write-Host ("-"*40)
  9. $myresults1 = "" | select @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  10.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  11.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  12.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}}
  13. $myresults1 | fl
  14.  
  15. Write-Host "Test 2"
  16. Write-Host ("-"*40)
  17. $myresults2 = "" | select @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  18.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  19.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  20.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}}
  21.  
  22. $myresults2 | fl
  23.  
  24. Write-Host "Test 3"
  25. Write-Host ("-"*40)
  26. $myresults3 = "" | select @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  27.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  28.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  29.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}}
  30. $myresults3 | fl
  31.  
  32. $myresults1,$myresults2,$myresults3
  33.  

Here is what the output of that Script looks like

S.DS.P : MyTest.ps1 Output

Here is the System.DirectoryServices.Protocols Code

  1. [System.Reflection.assembly]::LoadWithPartialName("system.directoryservices.protocols") | Out-Null
  2. $domain = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=",""
  3. $DomainDN = "DC=" + $Domain -replace "\.",",DC="
  4. [int]$pageCount = 0
  5. [int]$pageSize = 1000
  6. [int]$count = 0
  7. $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
  8. $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  9. $filter = "(objectclass=user)"
  10. $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,@("1.1"))  
  11. $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  12. $searchRequest.Controls.add($pagedRequest) | out-null
  13. $searchOptions = new-object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  14. $searchRequest.Controls.Add($searchOptions) | out-null
  15.  
  16. while ($true)
  17. {
  18.     ## increment the pageCount by 1
  19.     $pageCount++
  20.     ## cast the directory response into a
  21.     ## SearchResponse object
  22.     $searchResponse = $connection.SendRequest($searchRequest)
  23.     ## verify support for this advanced search operation
  24.     if (($searchResponse.Controls.Length -lt 1) -or
  25.         !($searchResponse.Controls[0] -is [System.DirectoryServices.Protocols.PageResultResponseControl]))
  26.     {
  27.         Write-Host "The server cannot page the result set"
  28.         return;
  29.     }
  30.     ## cast the diretory control into
  31.     ## a PageResultResponseControl object.
  32.     $pageResponse = $searchResponse.Controls[0]
  33.     ## display the retrieved page number and the number of
  34.     ## directory entries in the retrieved page                    
  35.     #"Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  36.     $count += $searchResponse.entries.count
  37.     ## display the entries within this page
  38.     ## foreach($entry in $searchResponse.entries){$entry.DistinguishedName}
  39.     ## if this is true, there
  40.     ## are no more pages to request
  41.     if ($pageResponse.Cookie.Length -eq 0){write-Host $count;break}
  42.     ## set the cookie of the pageRequest equal to the cookie
  43.     ## of the pageResponse to request the next page of data
  44.     ## in the send request
  45.     $pagedRequest.Cookie = $pageResponse.Cookie
  46. }

Fun with Active Directory (Playing Around Series)

This is the first in a series of posts call “Playing Around Series.” This series will basically be demo Videos of different Snapins/.NET Classes and their use.

In this entry I run through creating a DirectoryEntry, DirectorySearcher, and using the System.DirectoryServices.ActiveDirectory.Domain Class.

Note: This is a fast run through. I STRONGLY recommend pausing and reading the Comments. Best Viewed Full Screen

Get the Flash Player to see this player.

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

Demo Text

  1. #
  2. # Lets start off by looking at DirectoryEntry
  3. #
  4. $DE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://CN=tstUsr101,OU=MyUsers,DC=corp,DC=lab")
  5. #
  6. # First lets see what we have access to
  7. #
  8. $DE | Get-Member
  9. #’
  10. # Hmmm.. doesn’t seem like much. OH WAIT! Remember Powershell abstracts the class… Lets add psbase
  11. #
  12. $DE.psbase | Get-Member
  13. #
  14. # Lets look at what properties are available.
  15. #
  16. $DE.psbase.Properties
  17. #
  18. # Thats more like it. You may also note that some AD properties are still missing.
  19. # That is because that LDAP doesnt return all the properties. For these you need to "GET" them.
  20. $DE.psbase.InvokeGet(‘msExchUMFaxID’)
  21. #
  22. # Using DirectoryEntry is fine if you know the DN of the object, but what if you need to search?
  23. # Lets look at System.DirectoryServices.DirectorySearcher
  24. #
  25. # The Searcher needs some info so put that in variables first
  26. #
  27. $root = [ADSI]""  ## This is using the Type Accelerator we spoke about earlier… This is Gets the base
  28. $filter = "(&(objectcategory=user))"
  29. #
  30. # Now Lets create the searcher
  31. #
  32. $searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
  33. #
  34. # That gets the searcher ready, but to execute we need to call findall() or findone()
  35. #
  36. $users = $searcher.findAll()
  37. #
  38. # Lets see what we got. We have alot so lets only pick the first 10
  39. #
  40. $users | select -first 10
  41. #
  42. # Tons of info, but notice that this is NOT the same as DirectoryEntry
  43. #
  44. $users | get-Member
  45. #
  46. # It still has the properties property, Lets look (but only the first 3)
  47. #
  48. $users | select -first 3 | %{$_.Properties}
  49. #
  50. # Finally Lets look at System.DirectoryDervices.ActiveDirectory.Domain
  51. #
  52. # We can use this to interactively browse around
  53. #
  54. [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
  55. #
  56. # Lets assign that to variable to play with
  57. #
  58. $domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
  59. $domain
  60. #
  61. # Lets see what this has to offer
  62. #
  63. $domain | get-member
  64. #
  65. # Tons of cool stuff here.
  66. #
  67. # We can find all domain controllers
  68. $domain.FindAllDomainControllers()
  69. #
  70. # We Can look at our Domain FSMO
  71. #
  72. $domain | ft PdcRoleOwner,RidRoleOwner,InfrastructureRoleOwner
  73. #
  74. # I can even step the tree and get my forest root
  75. #
  76. $forest = $domain.Forest
  77. $forest
  78. #
  79. # With our new found $forest object… what can do we do?
  80. #
  81. $forest | Get-Member
  82. #
  83. # WE can find all our GCs
  84. #
  85. $forest.FindAllGlobalCatalogs()
  86. #
  87. # We can look at the Forest Mode
  88. #
  89. $forest.ForestMode
  90. #
  91. # Look at the Forest FSMO
  92. #
  93. $forest | ft SchemaRoleOwner,NamingRoleOwner
  94. #
  95. # Even look at sites
  96. $forest.Sites
  97. #
  98. # We can go on forever and ever. If you would like we can revisit this later.
  99. #

Citrix Script Repo (aka Exchange)

I was recently directed to a new website (for me at least) that contains a repository of Citrix related scripts. There are some pretty useful scripts posted (and I started adding some Powershell ones.) You should check it out

Script Exchange
http://community.citrix.com/display/cdn/Script+Exchange

I would also recommend RSS’ing Vishal blog (Dev at Citrix.) He has a passion for Powershell and I hope to see some awesome things from him in the future.

Vishal Ganeriwala’s Blog
http://community.citrix.com/blogs/citrite/vishalg/

Get-CitrixAppServer.ps1 (Citrix Top 10)

This is pretty simple script. It is actually just an adaption from my Get-CitrixApplication.ps1 script posted at the bottom. There is a moment in time when you start thinking in terms of objects and stop thinking in terms of text output you are looking for.

This will make a huge difference in productivity and take you from constantly writing scripts to interactively getting the information you are after, but until you make the transition I will continue to provide scripts that do both :)

  1. # Get-CitrixAppServer.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Gets All the Servers for Specific App
  5. Param($app)
  6. $mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
  7. $mfApp.Initialize(3,$app)
  8. $mfApp.LoadData(1)
  9. $mfApp.Servers | %{$_.ServerName}

Here is the script that I use to return an Application object.

  1. # Get-CitrixApplication.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Gets a Citrix Application Object.
  5. Param($app)
  6. $mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
  7. $mfApp.Initialize(3,$app)
  8. $mfApp.LoadData(1)
  9. $mfApp

Then from the commandline I would just do this
PS> Get-CitrixApplication.ps1 “Applications\MyApp1″ | %{$_.Servers} | %{$_.ServerName}

While this looks more complicated it is more versatile and easy to change. Lets say I want current User for this app instead.
PS> Get-CitrixApplication.ps1 “Applications\MyApp1″ | %{$_.Sessions} | %{$_.UserName}

Find-CitrixIdleUser.ps1 (Citrix Top 10)

This is one of the more useful scripts (at least for me.) It will query the farm and tell you all the user who have exceeded the idle time specified. I also combined another script that logged off users.

Name: Find-CitrixIdleUser.ps1
Purpose: Finds users with idle time greater than value passed and logs them off if -logoff is passed

  1. # Find-CitrixIdleUser.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Finds users with idle time greater than value passed
  5. Param($time,[switch]$day,[switch]$hour,[switch]$minute,[switch]$logoff,[switch]$verbose,[switch]$help)
  6. function HelpMe{
  7.     Write-Host
  8.     Write-Host " Find-CitrixIdleUser.ps1:" -fore Green
  9.     Write-Host "   Finds users with idle time greater than value passed"
  10.     Write-Host
  11.     Write-Host " Parameters:" -fore Green
  12.     Write-Host "   -Time                  : Optional. Server to Get Print Info From "
  13.     Write-Host "   -Day                   : Optional. Checks Days of Idle Time "
  14.     Write-Host "   -Hour                  : Optional. Checks Hours of Idle Time (Default)"
  15.     Write-Host "   -Minute                : Optional. Checks Minutes of Idle Time"
  16.     Write-Host "   -Logoff                : Optional. Logs User off if Idle Time exceeds Time"
  17.     Write-Host "   -Verbose               : Optional. Show Verbose Output"
  18.     Write-Host "   -Help                  : Optional. Displays This"
  19.     Write-Host
  20.     Write-Host " Examples:" -fore Green
  21.     Write-Host "   Finds Users who have been idle 2 hours and return AppName, UserName, SessionID, and ClientName" -fore White
  22.     Write-Host "     Find-CitrixIdleUser.ps1 2 -hour | ft AppName,UserName,SessionID,ClientName -auto" -fore Yellow
  23.     Write-Host
  24.     Write-Host "   Finds Users who have been idle 2 hours and Logs them Off" -fore White
  25.     Write-Host "     Find-CitrixIdleUser.ps1 2 -hour -logoff" -fore Yellow
  26.     Write-Host
  27. }
  28. if($verbose){$verbosepreference = "Continue"}
  29. Write-Host
  30. if(!$time -or $help){helpme;Write-Host;Return}
  31.  
  32. # Get Citrix Farm Object
  33. Write-Verbose " - Creating MFFarm Object"
  34. $farm = new-Object -com "MetaframeCOM.MetaframeFarm"
  35. $farm.Initialize(1)
  36.  
  37. # Parse Sessions
  38. Write-Verbose " - Parsing Sessions. Total of [$($farm.Sessions.count)] Sessions"
  39. foreach($session in $farm.Sessions)
  40. {
  41.     Write-Verbose "   - Processing Session ID [$($Session.SessionId)]"
  42.     $shouldLogOff = $false
  43.  
  44.     # Getting Citrix Session Idle Time and Convert to System.DateTime
  45.     Write-Verbose "   - Getting LastInputTime"
  46.     $ctxDate = $session.LastInputTime(1)
  47.     Write-Verbose "   - Checking if idle time is -gt 0"
  48.     if(($ctxDate.HighPart -ne 0) -and ($ctxDate.LowPart -ne 0))
  49.     {
  50.         $date = "{0,4}{1,2:00}{2,2:00}{3,2:00}{4,2:00}" -f $ctxDate.year,$ctxDate.Month,$ctxDate.Day,$ctxDate.Hour,$ctxDate.Minute
  51.         Write-Verbose "   - Converted LastInputTime to [$date]"
  52.         $SessionIdleTime = [system.DateTime]::ParseExact($date,‘yyyyMMddHHmm’,$null)
  53.  
  54.         # Get Current Time in System.DateTime
  55.         Write-Verbose "   - Getting Current Date"
  56.         $now = Get-Date
  57.         Write-Verbose "   - Current Date is [$now]"
  58.  
  59.         # Find Difference
  60.         Write-Verbose "   + Getting Time Difference"
  61.         $diff = $now - $SessionIdleTime
  62.         Write-Verbose "     - Found Days [$($diff.TotalDays)] Hours [$($diff.Totalhours)] Minutes [$($diff.TotalMinutes)]"
  63.  
  64.         # Output Sessions that match
  65.         if($day)   {    if($diff.TotalDays    -gt $time)    {$session;$shouldLogOff = $true}  }
  66.         if($hour)  {    if($diff.Totalhours   -gt $time)    {$session;$shouldLogOff = $true}  }
  67.         if($minute){    if($diff.TotalMinutes -gt $time)    {$session;$shouldLogOff = $true}  }
  68.  
  69.         Write-Verbose "     - Set `$shouldLogOff to [$shouldLogOff]"
  70.  
  71.         # Logging Off User
  72.         if($logoff -and $shouldLogOff)
  73.         {
  74.             Write-Verbose "   - Logging Off Session ID [$($Session.SessionId)]"
  75.             $session.Logoff($false)
  76.         }
  77.     }
  78.     else
  79.     {
  80.         Write-Verbose "   - Session ID [$($Session.SessionId)] NOT Idle"
  81.     }
  82.     Write-Host
  83. }