Suppose you need to count the number of certain objects (e.g. users in your domain) and you know that the number (and object size) is big, what’s the best way of doing that?
The first one that pops to mind is: just get the collection and check the Count
property:
(Get-QADUser -SizeLimit 0).Count
The problem with that is that you are getting (and keeping) all the objects until the enumeration is over, and only check the count after that. This means that in large environments you can easily run out of memory. In my domain (which is not huge at all), this command uses 153 MB of memory. And if I go for a bigger collection by retrieving all AD objects (running Get-QADObject
) I run out of memory before the command finishes.
So, let’s look for alternatives. Why don’t we just pipe the output to the Measure-Object
cmdlet which by default simply counts the number:
Get-QADUser -SizeLimit 0 | Measure-Object
The cmdlet will be getting the objects through the pipeline and thus will not need to keep them all, so it should use less memory, right?
Wrong. It looks like due to lack of optimization Measure-Object
is actually keeping the whole collection of real objects instead of just counting them and letting them go. Thus, it produces the exact same hit on your system and uses awful amounts of memory (hope that this gets fixed in v2! anyone from the PowerShell team reading this?)
So the right way of counting objects is actually to do that yourself by simply incrementing a counter variable:
Get-QADUser -SizeLimit 0 | ForEach-Object {$count++} $count
On my system this used only 27 MB instead of 153 MB used by Count
and Measure-Object
– a 6 time reduction!
And, the ratio is going to be even bigger if the number of object being counted grows.
Summary: standard solutions ain’t necessarily the best ones. In this particular case, manual $count++
works more optimally than standard Measure-Object
. PowerShell provides multiple ways to solve the same tasks and the performance/resource-intensiveness differences between them can be huge.
For other optimization tips see also: Optimizing PowerShell Performance and Memory Consumption
Tags: AD, AD cmdlets, Active Directory, KB, Knowledge Base, Known Issues, PowerShell, cmdlets
> anyone from the PowerShell team reading this?)
Of course we read your blog Dmitry!
I’ll go beat the appropriate engineer. 🙂
Seriously though, thanks for bring that to our attention, we’ll look into why that is the case.
Keep up the great work.
Jeffrey Snover [MSFT]
Windows Management Partner Architect
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
In case of QAD cmdlets, this might reduce memory consumption as well 🙂
Get-QADUser -SizeLimit 0 -DontUseDefaultIncludedProperties | ForEach-Object {$count++}
—–
Shay Levi
$cript Fanatic
http://scriptolog.blogspot.com
Jeffrey, don’t beet the engineer too much. The cmdlet is great, and when it becomes optimized it will become absolutely indispensable.
Shay, thanks for the comment. This makes the call even faster and consumes even less resources.
Hi Dmitry
I was wondering if you could give me a hand with a bit of code….I’m sure you’ve never heard that request before! 🙂
First up, I’ve succeeded on my first task of searching through all Active Directory OUs, and listing OUs with no users in them:
Get-QADObject -Type ‘organizationalUnit’ | ForEach-Object {
$count = 0
get-QADUser -SizeLimit 0 -SearchRoot $_.DN | ForEach-Object {$count++}
if ($count -eq 0) { $_.DN }
}
But I would like to now extend the script to print a table, showing the total number of users per Active Directory OU, sorted from highest to lowest user count.
The first column should contain the user count per OU, and the second column should contain the OU distinguished name, and should ideally be sorted by user count.
Any tips you can suggest would be greatly appreciated.
Thanks for this….and thanks for this very useful blog.
Ok, I got it figured out….at least here’s one solution.
Get-QADObject -Type ‘organizationalUnit’ | ForEach-Object {
$UserCount = (get-QADUser -SizeLimit 0 -SearchRoot $_.DN).count
@{$_.DN = $UserCount}
} | Format-Table
I now need to tweak the sort order and table format.
Almost there…
Ron,
Just modify the last line to:
} | Sort Count -descending | Format-Table
And you should be done.
Also, here’s the AD PowerShell forum which is normally very helpful for questions like this: http://powergui.org/forum.jspa?forumID=173
Dmitry
See, that’s my problem…
I’m creating a custom hash table and passing this through the pipe.
Therefore the sort-object cmdlet can’t reference a “count” property, as in your example, since that property name doesn’t exist.
But I’ll take your advice and post this issue through the forum.
Thanks.
Ah, found the magic parameter.
All working now
Get-QADObject -Type ‘organizationalUnit’ -SearchRoot “OU=test,OU=Corporate Office,DC=MHSINC,DC=CORP” | ForEach-Object {
$UserCount = (get-QADUser -SizeLimit 0 -SearchRoot $_.DN).count
@{$_.DN = $UserCount}
} | Sort-Object values -descending | Format-Table
And here’s the final code, including a cleaned up table format.
Note the weird discrepancy between using “$_.value” (plural) in the table format hash table vs having to use “sort-object value” (singular).
Weird.
$TableFormat = @{Expression={$_.Name};Label=”OU”}, @{Expression={$_.value};Label=”User Count”}
Get-QADObject -Type ‘organizationalUnit’ -SearchRoot “OU=test,OU=Corporate Office,DC=MHSINC,DC=CORP” | ForEach-Object {
$UserCount = (get-QADUser -SizeLimit 0 -SearchRoot $_.DN).count
@{$_.DN = $UserCount}
} | Sort-Object values | Format-Table $TableFormat -AutoSize
Very cool!
You can actually tweak the performance a bit by using:
Get-QADUser -DontUseDefaultIncludedProperties
Instead of just Get-QADUser. After all, you don’t need all the account properties – just their count.
On Ron’s last post I get an error “Unexpected token ‘@{‘ in expression or statement.”
Just does not like the ).count@{
Sorry I am a beginner with PS, this has me puzzled.
Max, what specifically are you trying to do?
Exactly what Ron was, I want to query how many user objects per OU (bonus would be not to count disabled user objects) and get back the results (Count & OU DN).
Then i would also like to learn how to do this for only top level OUs (read each top level OU automated in the domain), so count every user object starting from the top level OU on down, return total user object count from top OU down but show complete total not for each sub OU.
Just incase, this is the code it is complaining about (below)
My understanding is this line is to setup the variable for the result format, etc…
$TableFormat = @{Expression={$_.Name};Label=”OU”}, @{Expression={$_.value};Label=”User Count”}
The following is a separate line for the actual code below, yes?
Get-QADObject -DontUseDefaultIncludedProperties -Type ‘organizationalUnit’ | ForEach-Object { $UserCount = (get-QADUser -SizeLimit 0 -SearchRoot $_.DN).count @{$_.DN = $UserCount} } | Sort-Object values | Format-Table $TableFormat -AutoSize
This is the code last listed by Ron on your blog here, I just added the -DontUseDefaultIncludedProperties as you recommended.
But I get an error “Unexpected token ‘@{‘ in expression or statement.”
Max, I believe this got resolved in this discussion thread: http://www.powergui.org/thread.jspa?messageID=22036
A statement and a question:
1) don’t forget to set: $count = $null
as I discovered that ‘somthing’ had placed a value in their throwing off my results (as ++ is incrementing $count)
2) Were the performance concerns addressed in PowerShell 2.0? This might be a good follow-up blog post.
Thanks for clearing this up, I’m still at newbie stage and counting was something that confused me!
I was wondering if you know if the memory optimization ever happened in v2 or v3 (or the upcoming v4!)
There definitely was some optimization in v3 compared to v2 – I saw a lot of improvements. I have not experimented with these particular examples though. You are welcome to do that yourself! 🙂