I need to test which Windows users have administrator privileges.
OK, now read carefully: NOT CURRENT USER. I query all local user accounts, then I test which one of them has administrator privileges. Let's say I'm logged as Joe, my application runs in Joe user's context, but there is a user Timmy on this very PC, who is not currently logged on. I need to test if Timmy has admin on this PC. So, this question is definitely not about current user privileges ;) So, this is definitely not a duplicate of similar questions about determining the privileges of the current user. This one is different ;)
Here's my code:
public static dynamic[] Users => WMI.Query("SELECT * FROM Win32_UserAccount WHERE Disabled = 0").Select<dynamic, dynamic>(d => {
var machineContext = new PrincipalContext(ContextType.Machine);
Principal principal = Principal.FindByIdentity(machineContext, d.SID);
d.IsAdmin = principal.IsMemberOf(machineContext, IdentityType.Name, "Administrators");
principal.Dispose();
machineContext.Dispose();
return d;
}).ToArray();
This works, but it takes more than 2 seconds to execute IsMemberOf()
.
Is there a faster way to do this?
Why is it so slow?
If you wonder what WMI.Query
does here, it just queries the WMI and returns result as an array of managed dynamic
objects instead of IDisposable
types. IDisposable
types are disposed before the result is returned. Irrelevant to the question, though.
To clarify, I use System.DirectoryServices.AccountManagement
to get an actual user account from SID. I don't know if WindowsIdentity
can be created from SID. AFAIK it can't. The user for WindowsIdentity
needs to be logged on (throws a SecurityException if not), and I query all local users, not just the current one.
Well, I found it out, however it's still weird...
Updated code: (I changed matching group name to matching group SID).
public static dynamic[] Users => WMI.Query("SELECT * FROM Win32_UserAccount WHERE Disabled = 0").Select<dynamic, dynamic>(d => {
using (var machineContext = new PrincipalContext(ContextType.Machine))
using (Principal principal = Principal.FindByIdentity(machineContext, d.SID))
d.IsAdmin = principal.GetGroups().Any(i => i.Sid.IsWellKnown(System.Security.Principal.WellKnownSidType.BuiltinAdministratorsSid));
return d;
}).ToArray();
It turns out GetGroups()
is way faster than IsMemberOf()
.
Update: It's actually roughly 135 times faster. GetGroups()
with Any()
took 17ms instead of 2300ms IsMemberOf()
took.
As a bonus I'll share with my WMI.Query
;)
/// <summary>
/// Safe, managed WMI queries support.
/// </summary>
static class WMI {
/// <summary>
/// Queries WMI and returns results as an array of dynamic objects.
/// </summary>
/// <param name="q"></param>
/// <returns></returns>
public static dynamic[] Query(string q) {
using (var s = new ManagementObjectSearcher(q))
return
s
.Get()
.OfType<ManagementObject>()
.Select(i => {
var x = new ExpandoObject();
using (i) foreach (var p in i.Properties) (x as IDictionary<string, object>).Add(p.Name, p.Value);
return x;
})
.ToArray();
}
}