There is little difference between the performance of a PowerShell script
and an equivalent VBScript program. In both cases there are often several ways to
perform the same task. Usually, if efficiency or speed is a concern, it is more
important to select a method that minimizes the need to bind to objects in
Active Directory. This is usually the slowest step in the script.
Several scripts were developed to compare the relative performance of
PowerShell and VBScript. The task selected was to retrieve the Distinguished Names of 75 objects from their corresponding sAMAccountName values. In
VBScript we used ADO and NameTranslate. In PowerShell we also used ADO and
NameTranslate, to see if performance would be similar. In PowerShell we also
used System.DirectoryServices.DirectorySearcher and the AD Modules in Windows Server 2008 R2.
Specifically, we used Get-ADObject. The amount of time required for each
script to convert the 75 names was compared.
In addition, each script was run in two ways. First, each sAMAccountName was
converted into the corresponding Distinguished Name in a separate query, so
that 75 searches of Active Directory were required. Next, a query was
constructed so that the 75 names would be converted in bulk, in one
operation.
This was always faster.
Each script was run ten times (with a 15 minute pause between runs) on a Windows 7 client with RSAT. The average time in seconds, plus or
minus one standard deviation,
required to convert the 75 names using
each method is tabulated below.
Language |
PowerShell |
PowerShell |
PowerShell |
PowerShell |
VBScript |
VBScript |
Method |
Searcher |
Get-ADObject |
ADO |
NameTranslate |
ADO |
NameTranslate |
75 X 1 |
0.344 ± 0.149 |
1.307 ± 0.384 |
1.011 ± 0.088 |
0.191 ± 0.072 |
0.338 ± 0.135 |
0.171 ± 0.125 |
1 X 75 |
0.231 ± 0.079 |
0.607 ± 0.398 |
0.442 ± 0.148 |
|
0.136 ± 0.092 |
0.102 ± 0.105 |
The row labeled "75 X 1" means that 75 queries were used. The row labeled "1
X 75" means that one query was used to convert the 75 names in bulk. I was not able
to figure out how to convert an array of 75 names using NameTranslate in
PowerShell.
Many
factors influence the time required to retrieve the 75 Distinguished Names, including the performance of the local
client, the performance of the Domain Controller that handles the queries,
the speed of the network, the size of the Active Directory database, and how busy the local client and the Domain
Controller are with other tasks. In addition, if a script is run twice in
succession, the second run is always faster, most likely because of caching
on the DC. To be fair, tests should be repeated after enough of a pause to
reduce this affect.
The above tests were conducted in a test domain
with no other activity. There are about 2,100 user objects in Active
Directory, although performance was the same even in a domain with only 100
users. One Domain Controller is a 3 GHz dual core computer
with Windows Server 2008 R2 and no FSMO roles (although it is a GC). Other
DC's are Windows Server 2003 and Windows 2000 Server. I ran
all of the scripts on a 2.5 GHz dual core computer joined to the domain with Windows 7
Professional 64-bit and RSAT installed. All tests would have been faster if
they had been run on a Domain Controller, but I don't believe that is the
usual situation.
For comparison, tests were repeated on a computer with Windows XP Pro SP2.
The is a 631 MHz machine with 256 MB
RAM and PowerShell V 1.0. The Get-ADObject cmdlet is not available in this
version. The results follow:
Language |
PowerShell |
PowerShell |
PowerShell |
PowerShell |
VBScript |
VBScript |
Method |
Searcher |
Get-ADObject |
ADO |
NameTranslate |
ADO |
NameTranslate |
75 X 1 |
1.350 ± 0.184 |
N/A |
3.994 ± 0.389 |
0.977 ± 0.221 |
0.725 ± 0.253 |
0.618 ± 0.245 |
1 X 75 |
0.297 ± 0.007 |
N/A |
1.093 ± 0.048 |
|
0.220 ± 0.114 |
0.240 ± 0.099 |
The results are similar on the slower computer. However, the NameTranslate
object seems to have less advantage, and using one query with the
DirectorySearcher class makes a bigger difference. In fact, one query for 75
names using the DirectorySearcher class is almost as fast on the slow
computer as on the faster.
The
programs linked below were used to make all of the above tests. The first is a
PowerShell script that uses the System.ActiveDirectory.DirectorySearcher
class. The PropertiesToLoad method was used to specify that just the
distinguishedName attribute is being retrieved. The script was slower if this method
was not used. This script makes 75 queries of Active Directory to retrieve
the 75 names.
Get75Users1.txt <<-- Click here to view or download the PowerShell script
The next PowerShell script
uses the Get-ADObject cmdlet, part of the AD Modules included with Windows
Server 2008 R2 and RSAT on Windows 7. This script makes 75 queries of Active
Directory to retrieve the 75 names.
Get75Users2.txt <<-- Click here to view or download the PowerShell script
The following PowerShell script uses ADO to make 75 queries of Active
Directory. The code is very similar to a VBScript solution using ADO. Notice
that you specify the attribute values to be retrieved, and you can turn on
paging, specify a timeout value, and disable caching.
Get75Users3.txt <<-- Click here to view or download the PowerShell script
The next PowerShell script uses the NameTranslate object 75 times to convert
the sAMAccountName values into Distinguished Names. The syntax required is
not intuitive, but it works and is fast.
Get75Users4.txt <<-- Click here to view or download the PowerShell script
The fifth PowerShell script, linked below, is identical to the first, using
the System.ActiveDirectory.DirectorySearcher class, except that a single
query is constructed to retrieve all 75 Distinguished Names at once. As
expected, this is more efficient.
Get75Users5.txt <<-- Click here to view or download the PowerShell script
The next PowerShell script is the same as the second, using the Get-ADObject
cmdlet, but again a single query is constructed to retrieve all 75
Distinguished Names in bulk.
Get75Users6.txt <<-- Click here to view or download the PowerShell script
The final PowerShell script linked below uses ADO, like the third script
above, but constructs a single filter to retrieve all 75 Distinguished Names
in one query.
Get75Users7.txt <<-- Click here to view or download the PowerShell script
Next is a VBScript program that uses ADO to query Active Directory 75 times
for the 75 Distinguished Names.
Find75Users1.txt <<-- Click here to view or download the VBScript program
The second VBScript program, linked below, uses the NameTranslate object 75
times to convert the sAMAccountName values into distinguishedName values.
Find75Users2.txt <<-- Click here to view or download the VBScript program
The next VBScript program is identical to the first VBScript program above,
using ADO, except that a
single filter is constructed to query Active Directory once for all 75
Distinguished Names in bulk. As expected, this is faster.
Find75Users3.txt <<-- Click here to view or download the VBScript program
Finally, the following VBScript program employs NameTranslate to convert an
array of the 75 sAMAccountNames into Distinguished Names at once. This is
the fasted method, but it has the drawback that if even one sAMAccountName is
not found in Active Directory, an error is raised and no names are
converted.
Find75Users4.txt <<-- Click here to view or download the VBScript program
Two other PowerShell scripts have been compared to their VBScript
equivalents. For example, the programs
PSCircularNestedGroups.ps1 and
CircularNestedGroups.vbs
were compared. Both of these programs query all groups in Active Directory,
and their direct memberships, to find all instances of circular nested groups. The time required, not counting the
time required to output the results, were compared on two computers, one
relatively fast and the other slower. The average time in seconds, plus or
minus one standard deviation, after 5 trials, 15 minutes apart, is tabulated
below.
Computer |
cpu Speed |
RAM |
PS Version |
PowerShell |
VBScript |
Windows 7 64-bit |
2.5 GHz |
2 GB |
2 |
0.262 ± 0.105 |
0.248 ± 0.059 |
Windows XP SP2 |
631 MHz |
256 MB |
1 |
0.994 ± 0.063 |
0.614 ± 0.055 |
Next, we compare the programs
PSLastLogon.ps1 and
LastLogon.vbs. Both of these programs query every Domain Controller in
the domain for the largest (latest) value of the lastLogon attribute for all
users in the domain. My test domain has three Domain Controllers and 2,109
users. The time required for these queries, not counting the
time required to output the results, were compared. The average time in
seconds, plus or minus one standard deviation, after 5 trials, 15 minutes
apart, is tabulated below.
Computer |
cpu Speed |
RAM |
PS Version |
PowerShell |
VBScript |
W2k8 R2 |
3 GHz |
1 GB |
2 |
4.649 ± 0.071 |
3.250 ± 0.122 |
Windows XP SP2 |
631 MHz |
256 MB |
1 |
27.718 ± 0.604 |
7.328 ± 1.030 |
The program
PSLastLogon2.ps1, which uses the Get-ADDomainController and Get-ADUser
cmdlets in PowerShell V2, was also tested. However, this program requires that all Domain
Controllers be running on Windows Server 2008 R2, with the Active Directory Web Services running. Only one of the Domain Controllers in my test domain
meets these requirements, so I modified the script to repeatedly query the
same Domain Controller. The results were very slow. The program required 2
minutes 32 seconds (152.311 ± 0.788 seconds).
The
conclusion that should be drawn is that in most cases you should use
whatever language and whatever method you are most comfortable with. Other
factors, such as the availability of PowerShell or the AD Modules, may be
more important. If you are transitioning from VBScript to PowerShell, it may
help to use ADO in PowerShell scripts, since the code is familiar. It
employs older COM technology, but it works and is not inefficient. If you
have a task where performance is an issue, you can use some of the code
techniques demonstrated in the linked scripts to perform tests in your own
environment.
Update August 2012:
The Active Directory module cmdlets, such as Get-ADUser and Get-ADObject,
always retrieve a default set of properties, in addition to any others
requested. It has been noted that this might account for these modules
taking longer than an equivalent VBScript program that only retrieves
distinguished names. The Get-ADUser cmdlet, for example, always retrieves
DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID,
SamAccountName, SID, Surname, and UserPrincipalName properties. The Get-ADObject
cmdlet always retrieves the default Parameters DistinguishedName, Name,
ObjectClass, and ObjectGUID. Notice also that some of these properties
(Enabled, ObjectGUID, and SID) require code to convert the values in Active
Directory into strings for display. To check if this makes a difference, the
VBScript programs Get75Users1.vbs and Get75Users3.vbs linked on this page
were modified to retrieve the attributes corresponding to the default
properties retrieved by Get-ADUser. The programs were also modified to
convert all of the values into strings. However, the time required was
unchanged from the results shown in the first table on this page.
Update December 2021:
PowerShell scripts were recently completed to duplicate the VBScript programs
on this site to generate pseudo random integers. In particular, the PsCombo.ps1
script duplicates the Combo.vbs program posted on this web site. I verified that
using the same initial seed and carry values, the two scripts generate identical
values when I request 1,000 pseudo random integers.
I added statements to each script to document how long it takes each to generate
the 1,000 integers. I commented out the statements to display the numbers at the
prompt, to avoid the time that takes. I found that the PowerShell script takes
about 24 times as long to generate the integers. The VBScript program took about
31.25 milliseconds, while the PowerShell script took about 760 milliseconds. I
attribute the difference to the time required by the .NET framework. Both scripts
are now on the page linked by clicking the button above.