Some months ago, I created PowerShell Script to create local administrative users on workstations – Create a local user or administrator account in Windows using PowerShell. It's a bit overcomplicated, but the goal was it should work for Windows 7 and up, and that means supporting PowerShell 2.0. As part of that exercise, I've been using Win32_UserAccount WMI based query to find local users and manage them to an extent. While Get-LocalUser exists, it's not suitable for the PowerShell 2.0 scenario. I also use the same query in GPO for WMI filtering. You can say it's been a good friend of mine – until today! Let's take a look at this basic WMI query:
Get-WmiObject -Query "SELECT * FROM Win32_UserAccount WHERE LocalAccount=true" | Format-Table
It can also give more relevant data, such as if the account is enabled or disabled.
Get-WmiObject -Query "SELECT * FROM Win32_UserAccount Where LocalAccount = true" | Format-Table Caption, Domain, Name, PasswordChangeable, PasswordRequired, Disabled
I've been using this WMI query both in PowerShell scripts that need to support PowerShell 2.0 (Windows 7) and in GPO WMI filtering when I apply GPO that only should execute if a given user exists.
Now you may be wondering why I even mention this? I was checking for the existence of a single local user on a workstation rather than asking for multiple users. So by merely adding Name = ‘Administrator' I'm making sure my query outputs only a single user.
Get-WmiObject -Query "SELECT * FROM Win32_UserAccount Where LocalAccount = true AND Name = 'Administrator'" | ft
Except now, instead of 1 second, it takes 2 minutes.
To understand why would there be a difference between those two queries
Get-WmiObject -Query 'Select * FROM Win32_UserAccount WHERE LocalAccount = true' | ft Get-WmiObject -Query "SELECT * FROM Win32_UserAccount Where LocalAccount = true AND Name = 'Administrator'" | ft
I've run those, but without any WHERE filtering.
Get-WmiObject -Query 'Select * FROM Win32_UserAccount' | ft
And suddenly, everything made sense. Without WHERE filtering, it queries not only local users but Active Directory users as well. This explains why a simple query would take 2 minutes – I have over 50000 users in my AD. Well, it only sort of makes sense because we're using an almost same query, which has a very subtle difference that shouldn't impact query in that way. If anything, it should be faster because we added condition limiting our output.
So, while I could probably find some way to work around this issue in PowerShell, it doesn't solve my problem when using the very same query in WMI filtering for Group Policies. It means that each time the GPO gets executed, it takes 2 minutes+ to do an assessment, whether it's valid for the current workstation or not. Not to mention, it impacts the performance of an AD.
What's the fix? Changing equal (=) to LIKE. Consider those two queries:
Get-WmiObject -Query "SELECT * FROM Win32_UserAccount Where LocalAccount = true AND Name Like 'Administrator'" | ft Get-WmiObject -Query "SELECT * FROM Win32_UserAccount Where LocalAccount = true AND Name = 'Administrator'" | ft
Almost the same, but the first one takes 1 second, the second one 2 minutes. So for some weird reason, the equal sign is causing WMI provider to go nuts, and changing it to LIKE resolves the issue. While I don't know why that happens, I'm pretty happy with this solution. I'm pretty sure I will forget about it in a few days so that this blog post will be my reminder to not take things for granted and that even subtle difference requires extensive testing.