PowerShell

Using Win32_UserAccount WMI filter in PowerShell/Group Policies and what to avoid

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.

WMI Win32_UserAccount - Why even mention it?

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.

WMI Win32_UserAccount - Why that happens?

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.

WMI Win32_UserAccount - Workaround

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.

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

4 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

6 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

7 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

7 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

8 months ago

Report Active Directory Accounts that are Synchronized with Azure AD

I was scrolling X (aka Twitter) today and saw this blog post, "PowerShell: Report On-Premises…

8 months ago