Posts RSS Comments RSS 253 Posts and 411 Comments till now

The 2011 Scripting Games

The 2011 Scripting Games begin on April 4, 2011 and run through April 15, 2011. What is the Scripting Games, you may ask? Well simply put, the Scripting Games are the premier learning event of the year for IT Pro’s and others who wish to master Windows PowerShell. Comprising 10 events, a registered contestant has seven days to create a solution to a scenario driven problem and post their code to a MVP maintained script repository for evaluation by a panel of internationally recognized judges. Daily leaderboards and prize drawings help to maintain the suspense throughout the two-week international event.

 

During the 2011 Scripting Games hundreds of contestants will submit thousands of scripts that will be reviewed by dozens of judges. Hundreds of thousands of spectators from all around the world will view the games themselves. Last year, participants from more than 100 countries participated in the Scripting Games. With an emphasis on real world scripting scenarios and problems, the scripts will be of immediate value to both the participants and to the spectators.

 

Preparation for the 2011 Scripting Games is already underway, with a learning guide, step-by-step tutorials, videos and other resources being generated on a daily basis. The 2011 Scripting Games all in one page is the portal for the games themselves. The 2010 Scripting Games all in one page is still available, as are the events from the 2009 Scripting Games.

 

Citrix News! I have join the Citrix Technology Professional ranks!

I was notified last week, but I wanted to wait till my profile was up on the site. For those of you wondering what in the heck that is… it is basically Citrix version of the MVP program. It will allow me to interact more directly with Citrix. I am sure this will be a mutually beneficial relationship. They have made a huge stake in Powershell and I want to do my best to help direct or guide their adoption.

You can read more about the program Here.

In other news and more important to you as a reader :)

Citrix is working on a set of Powershell cmdlets for XenApp 5 (most of them should work in XenApp 4.5 if you have HR3 installed.) We have been in closed beta, but I have got to tell you… this is good stuff. They did a stellar job and have been very open to feedback. These cmdlets should make my scripts obsolete and I am very much looking forward to it. I will be releasing details and demo's as soon as I am allowed.

OH! Did I mention I am doing a Geek Speak Session at Synergy? Well I am!

At this point I just know I am on Wednesday at some time, but I will be there all week if you around. Just email!

Lets show them we Powershell people are serious! Vote for my session here: VOTE!

Citrix News! I have join the Citrix Technology Professional ranks!

I was notified last week, but I wanted to wait till my profile was up on the site. For those of you wondering what in the heck that is… it is basically Citrix version of the MVP program. It will allow me to interact more directly with Citrix. I am sure this will be a mutually beneficial relationship. They have made a huge stake in Powershell and I want to do my best to help direct or guide their adoption.

You can read more about the program Here.

In other news and more important to you as a reader 🙂

Citrix is working on a set of Powershell cmdlets for XenApp 5 (most of them should work in XenApp 4.5 if you have HR3 installed.) We have been in closed beta, but I have got to tell you… this is good stuff. They did a stellar job and have been very open to feedback. These cmdlets should make my scripts obsolete and I am very much looking forward to it. I will be releasing details and demo’s as soon as I am allowed.

OH! Did I mention I am doing a Geek Speak Session at Synergy? Well I am!

At this point I just know I am on Wednesday at some time, but I will be there all week if you around. Just email!

Lets show them we Powershell people are serious! Vote for my session here: VOTE!

Functions and why they are important!

I often get asked why I use functions and when I think they should be used. Below is a guide I wrote explaining my theory on functions.


Why use functions?

Functions are, generally speaking, small single task tools (like a flathead screwdriver or hammer.) They do one thing and they do that one thing reliably well. If you take this approach when writing code you will find it easier to debug and find yourself writing less code. Why less code? Because, you’ll find you are now able to port your functions from one script to another or possibly even in your day to day life.

How you decide to write a function?

I have three basic guidelines for when to write a function.

First, if I find I am repeating the same code block over and over (like a code block that checks several services on a computer.) It makes sense to just write a function to perform the check and then run that against each server. This allows me to trouble-shoot the code more efficiently.

Second, if I find that I can use this code in other scripts. For example if I write a nice recursive parsing block. I may want to reuse that logic in another script.

Finally, if I determine the code is useful outside of this script. This may seem like the previous guideline but it is slightly different. A good example here would be a ping-server function. Not only is this useful in other scripts, but it is also useful in my day to day life.

Do you always design functions with the idea of reuse in mind?

Generally speaking it is a good idea to ALWAYS consider reuse when writing code. This is paramount when working with functions. The sole purpose of functions in life is for reuse.  This is major consideration when designing your functions. Consider how and where they will be used. This will help establish what parameters it should have and what (if any) defaults it should have.

What do you put in your functions?

Ideally, because we design code for reuse, it is best to be as verbose as possible.  Basic rule of thumb is hardcode nothing, all data should be passed by parameters. Certainly you can have default values for the parameters, but allow the function caller to specific other options without having to modify the function.  This comes back to the black box approach. One needs to consider the effect of every change the original function and how that will affect the script as a whole.

In v1 I always try to implement –verbose and –whatif with my own switches. In v2 this is handled for you.

Do you separate the logic from the function?

When designing functions one should think about the looping and processing logic. Generally you will find this is script specific and should be implemented outside of the function. Ideally you would want to restrict logic to the party that requires the logic. For example, if you have logic to process servers, keep that logic outside of the functions. There is no need to implement that logic over and over for each function call. On the other hand, if you have logic that is expressly the domain of the function do not go crazy trying to rip it out just to put in the calling script.

What makes a good function?

Great functions are born out of need, but grow out of use. As you grow in your understanding your functions will grow with you. They are like the friend that is always there when you need them, but like that friend they need attention and care. Below are some features that functions should have:

Well defined parameters:

You function needs to be very clear on what data it expects. You accomplish this by having very specific parameters (this often will include the data type as well.) If you absolutely must have a specific value to process make sure that is clear from the function. A great way to accomplish this is by assigning the parameters default value to (Throw ‘$ThisParam is required’).

Consistent and expected output:

This is absolutely critical. You do not want to guess at what data will come from the function. You want the data to be what is expected. Design the function so that it returns one or more of a single data type (i.e. string, DateTime, or Boolean.) Be very cautious not to pollute the data stream with unexpected data written with write-output or data that wasn’t captured in a variable.

Self contained:

The function should NOT rely on any variables from the script. If the function needs input from outside make it a parameter.

Portability:

The single most important job of a function is to be portable. If you do not plan to reuse the code you might as well write the code inline. A key factor to portability is to make sure your variable names will not collide with the calling script. A good rule of thumb is to preface them with $my or $func (like $MyServer or $FuncServer.)

 

Working on a BSonPosh Module for Powershell v2

I am currently working on porting the scripts/functions posted on this blog to a single module download and I need your help. With over a hundred of these on this blog it is clearly going to take some time so what I need from you is what to you think I should port first? I am going to put on the module as soon as I get a baseline (next week or so) and then I will just update it as I go. This means if you have something you wanted added sooner over later, let me know 🙂

Thanks

Dealing with Parameters in Powershell

I often get asked to review code for people and I often see them use $args for argument parsing (most often with VBScript converts.) While there is nothing wrong with that method, I do not believe it is the best way. Powershell has numerous ways to pass data to scripts/functions. Today, after helping a friend understand the differences between them, I decided it would be good to blog about it.


Let’s take a look at the different ways scripts/functions can take input.



You can pass data by position parameters:

.\myscript.ps1 filename.txt corp.lab

You can pass data by named parameters:

.\myscript.ps1 -list filename.txt -domain corp.lab

Finally, you can pipe data in:

get-content filename.txt | .\myscript.ps1 -domain corp.lab


That is great… but what would the code look like foreach of these?


There is little difference between a script and a function so I will illustrate using functions.
To use positional parameters

Example: PassByPosition filename.txt corp.lab

function PassByPosition{
  "FileName: {0}" -f $args[0]
  "Domain: {0}" -f $args[1]
}



To process Named parameters you use the Param() statement included in Powershell

Example: PassByName -list filename.txt -domain corp.lab

function PassByName{
    Param($FileName,$DomainName)
    "FileName: {0}" -f $FileName
    "Domain: {0}" -f $DomainName
}



To process piped data you can do something like

Example: get-content filename.txt | PassByPipe corp.lab

function PassByPipe{
    if($input)
    {
        foreach($val in $input)
        {
            "FileName: {0}" -f $val
            "Domain: {0}" -f $args[0]
        }
    }
}

NOTE: This is not only way to process piped input, but it is the simplest example. If you would like to see a more efficient way to process look HERE.


It gets REALY cool when using them together


Using the script below you can do any of these 

UseAllThree filename.txt corp.lab
UseAllThree -list filename.txt -domain corp.lab
get-content filename.txt | UseAllThree -domain corp.lab

function UseAllThree{
    Param($FileName,$DomainName)
    if($input)
    {
        foreach($val in $input)
        {
            "FileName: {0}" -f $val
            "Domain: {0}" -f $args[0]
        }
    }
    else
    {
        "FileName: {0}" -f $FileName
        "Domain: {0}" -f $DomainName
    }
}

 

We can even have default values for the Parameters


Using the script below you can do any of these 

UseAllThreewithDefaults filename.txt
UseAllThreewithDefaults -list filename.txt
get-content filename.txt | UseAllThreewithDefaults

function UseAllThreewithDefaults{
    Param($FileName = "FileName.txt",$DomainName = "Corp.lab")
    if($input)
    {
        foreach($val in $input)
        {
            "FileName: {0}" -f $val
            "Domain: {0}" -f $DomainName
        }
    }
    else
    {
        "FileName: {0}" -f $FileName
        "Domain: {0}" -f $DomainName
    }
}

Setting lDAPAdminLimits via Powershell

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

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

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

Some Examples of Use

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

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

The Code

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

Using Citrix MFCOM Inpersonation Remotely

I often get questions about scripts running with no errors, but not returning any valid information.

For example

$mfFarm = New-object -com "MetaframeCOM.MetaFrameFarm"
$mfFarm.Initialize(1)
$mfFarm.FarmName # will return the Farm Name, but that is it.
$mfFarm | gm # all the properties are there, but blank
# But none of the properties provide any info
$mfFarm.Admins # returns nothing



I ran into this a while back and meant to blog it but forgot. I was asked this question again today and decided to finally get to blogging it.

Reason:
Generally the cause is that MFCOM is not configure for impersonation. MFCom needs to be able to act as if it is the user using the script. This is called impersonation. To make this work, you must configure DCOM to use impersonation using the article below.

Solution:
See here for more info and how to change it.
Configuring MFCOM Access Security

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

function Get-DSPObject {
    Param(
            $filter = "(objectclass=user)",
            $base = ([ADSI]"").distinguishedName,
            $Server,
            [int]$pageSize = 1000,
            [string[]]$props = @("1.1"),
            [switch]$noHeader,
            [switch]$csv,
            [switch]$count
        )
       
    [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
       
    [int]$pageCount = 0
    [int]$objcount = 0
   
    if(!$server){$server = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=","" }
   
    $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
    $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
   
    $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$subtree,$props)  
    $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
    $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
    $searchRequest.Controls.add($pagedRequest) | out-null
    $searchRequest.Controls.Add($searchOptions) | out-null
   
    # Output Prep
    if($props -notcontains "1.1")
    {
        $MyProps = @()
        foreach($prop in $props){$MyProps += $prop.ToLower()}
        if($csv)
        {
            if(!$noHeader)
            {
                $header = "distinguishedName"
                foreach($prop in $props){$header += ",$prop"}
                $header
            }
        }
        else
        {
            $MyUserObj = New-Object System.Object
            $MyUserObj | Add-Member -name distinguishedName -MemberType "NoteProperty" -value $null
            foreach($prop in $props){$MyUserObj | Add-Member -name $prop -MemberType "NoteProperty" -value $null}
        }
    }
   
    # Process Pages
    while ($True)
    {
        # Increment the pageCount by 1
        $pageCount++
   
        # Cast the directory response into a SearchResponse object
        $searchResponse = $connection.SendRequest($searchRequest)
   
        # Display the retrieved page number and the number of directory entries in the retrieved page
        # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
        $objcount += ($searchResponse.entries).count
       
        # Display the entries within this page
        if(!$count)
        {
            if($props -notcontains "1.1")
            {
                foreach($entry in $searchResponse.Entries)
                {
                    if($csv)
                    {
                        $results = "`"{0}`"" -f $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $results += ",`"{0}`"" -f ($entry.Attributes[$prop][0])
                        }
                        $results
                    }
                    else
                    {
                        $MyUserObj.distinguishedName = $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $MyUserObj."$prop" = $null
                            $MyUserObj."$prop" = $entry.Attributes[$prop][0]
                        }
                        $MyUserObj
                    }
                }
            }
            else{$searchResponse.Entries | select distinguishedName}
        }
       
        # if this is true, there are no more pages to request
        if ($searchResponse.Controls[0].Cookie.Length -eq 0){if($count){$objcount};break}
   
        # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
        # page of data in the send request and cast the directory control into a PageResultResponseControl object
        $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
    }
}

foreach($i in (1..10))
{
    $time = Measure-Command { Get-DSPObject -prop "name","sAMAccountName" -csv }
    "Execution time: {0:n3} s" -f $time.TotalSeconds
}

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 😛 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

[System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | Out-Null

$domainDN = ([ADSI]"").distinguishedName
$domain = $domainDN -replace  ",","." -replace "dc=",""

[int]$pageCount = 0
[int]$pageSize = 1000
[int]$count = 0

$connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
$subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
$filter = "(objectclass=*)"

$searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,"1.1")  
$pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
$searchRequest.Controls.add($pagedRequest) | out-null
$searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
$searchRequest.Controls.Add($searchOptions) | out-null
 
while ($True)
{
    # Increment the pageCount by 1
    $pageCount++

    # Cast the directory response into a SearchResponse object
    $searchResponse = $connection.SendRequest($searchRequest)

    # Display the retrieved page number and the number of directory entries in the retrieved page
    # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
    $count += ($searchResponse.entries).count
   
    # Display the entries within this page
    $searchResponse.Entries | select distinguishedName
    # if this is true, there are no more pages to request
    if ($searchResponse.Controls[0].Cookie.Length -eq 0){write-Output $count;break}

    # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
    # page of data in the send request and cast the directory control into a PageResultResponseControl object
    $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
}

Script I used to automate the 60 test per Environment.

Param($count=30)

$dn = ([ADSI]"").distinguishedName

function TestOne {
    Write-Host "  + Test ${i}.1"
    Write-Host "    + Running ADFind Test"
    $joeTime      = Measure-Command { D:\Scripts\adfind -b "$dn" -c -f "(objectclass=user)" 2>&1 | out-Null }
    Write-Host "      – $($joetime.TotalSeconds)"
    Write-Host "    + Running DSP Test"
    $DSPTime11    = Measure-command { D:\Scripts\Test-DSProtocalsSP.ps1 }
    Write-Host "      – $($DSPTime11.TotalSeconds)"
    $myresults  = "" | select @{n="ADFind"           ;e={$joeTime.TotalSeconds}},
                              @{n="DSP Using 1.1"    ;e={$DSPTime11.TotalSeconds}}
    $myresults
}
function TestTwo {
    Write-Host "  + Test ${i}.2"
    Write-Host "    + Running DSP Test"
    $DSPTime11    = Measure-command { D:\Scripts\Test-DSProtocalsSP.ps1 }
    Write-Host "      – $($DSPTime11.TotalSeconds)"
    Write-Host "    + Running ADFind Test"
    $joeTime      = Measure-Command { D:\Scripts\adfind -b "$dn" -c -f "(objectclass=user)" 2>&1 | out-Null }
    Write-Host "      – $($joetime.TotalSeconds)"
    $myresults  = "" | select @{n="ADFind"           ;e={$joeTime.TotalSeconds}},
                              @{n="DSP Using 1.1"    ;e={$DSPTime11.TotalSeconds}}
    $myresults
}

Write-Host
for($i = 0 ; $i -le $count ; $i++)
{
    Write-Host "+ Test $i"
    TestOne
    TestTwo
}
Write-Host

Next »