1

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.

Harry
  • 4,524
  • 4
  • 42
  • 81
  • Did you try `principal.IsInRole(WindowsBuiltInRole.Administrator)`? – kgzdev Jan 27 '17 at 11:07
  • This is not a `System.Security.Principal.WindowsPrincipal`, its `System.DirectoryServices.AccountManagement.Principal`. There is no conversion between those 2 types I know of. – Harry Jan 27 '17 at 11:13
  • 1
    Possible duplicate of [Check if the current user is administrator](http://stackoverflow.com/questions/3600322/check-if-the-current-user-is-administrator) – Manfred Radlwimmer Jan 27 '17 at 11:22
  • @MatteoUmili Please read the question again, and notice that I don't ask about CURRENT user. I ask about ANY (not logged on) user on a Windows PC. – Harry Jan 27 '17 at 11:26
  • Please kindly read before commenting: I know how to check if CURRENT user is an administrator, what I'm asking is whether NOT THE CURRENT user is an administrator and this is a huge difference! – Harry Jan 27 '17 at 11:30

1 Answers1

1

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();
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Harry
  • 4,524
  • 4
  • 42
  • 81
  • 1
    How much faster was it? From 2 seconds to ... ? – Fildor Jan 27 '17 at 11:36
  • @Fildor From 2200ms to 17ms with diagnostic tools running ;) – Harry Jan 27 '17 at 11:41
  • `GetGroups` doesn't take nested groups into account. That might be why it is faster, but it also means that the results may be incomplete, depending on the context. – Harry Johnston Jan 27 '17 at 23:49
  • @HarryJohnston Can local groups on Windows 10 be nested? Then, this magnitude of time difference seems to indicate something more happening than just a little more complex query. `System.DirectoryServices` is surely not limited to local groups, however the context is `Machine`. Maybe the local context itself is tested in network context? But it would make no sense in determining the access to local resources. It would make sense for a remote logon though. – Harry Jan 28 '17 at 05:37
  • Hmmm. If it's a local account, and assuming the machine is not a domain controller, I don't think the groups can be nested. So (ignoring pathological cases such as `INTERACTIVE` being in the Administrators group) you'll probably get accurate results from `.GetGroups`, except of course you should be checking the SIDs rather than looking at the strings. But the delay in `.IsMember` might indeed be because it is checking the domain, though that doesn't really explain the delay. Unless the query is timing out? – Harry Johnston Jan 28 '17 at 08:57