Более быстрый способ проверить, является ли (не текущий) пользователь администратором

Мне нужно проверить, у каких пользователей Windows есть права администратора.

Хорошо, теперь внимательно прочтите: НЕ ТЕКУЩИЙ ПОЛЬЗОВАТЕЛЬ. Я запрашиваю все локальные учетные записи пользователей, затем проверяю, какая из них имеет права администратора. Скажем, я вошел в систему как Джо, мое приложение работает в контексте пользователя Джо, но на этом самом ПК есть пользователь Тимми, который в настоящее время не вошел в систему. Мне нужно проверить, есть ли у Тимми администратор на этом компьютере. Итак, этот вопрос определенно не о привилегиях текущего пользователя;) Итак, это точно не дубликат аналогичных вопросов об определении привилегий текущего пользователя. Этот другой;)

Вот мой код:

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();

Это работает, но для выполнения IsMemberOf() требуется более 2 секунд. Есть ли более быстрый способ сделать это? Почему так медленно?

Если вам интересно, что здесь делает WMI.Query, он просто запрашивает WMI и возвращает результат в виде массива управляемых dynamic объектов вместо IDisposable типов. IDisposable типов удаляются до того, как будет возвращен результат. Хотя это не имеет отношения к вопросу.

Чтобы уточнить, я использую System.DirectoryServices.AccountManagement, чтобы получить фактическую учетную запись пользователя из SID. Я не знаю, можно ли создать WindowsIdentity из SID. AFAIK это не может. Пользователь WindowsIdentity должен войти в систему (в противном случае выдает исключение SecurityException), и я запрашиваю всех локальных пользователей, а не только текущего.


person Harry    schedule 27.01.2017    source источник
comment
Вы пробовали principal.IsInRole(WindowsBuiltInRole.Administrator)?   -  person kgzdev    schedule 27.01.2017
comment
Это не System.Security.Principal.WindowsPrincipal, это System.DirectoryServices.AccountManagement.Principal. Я знаю, что между этими двумя типами нет никакой конверсии.   -  person Harry    schedule 27.01.2017
comment
Возможный дубликат Проверьте, является ли текущий пользователь администратором   -  person Manfred Radlwimmer    schedule 27.01.2017
comment
@MatteoUmili Пожалуйста, прочтите вопрос еще раз и обратите внимание, что я не спрашиваю о ТЕКУЩЕМ пользователе. Я спрашиваю о ЛЮБОМ (не авторизованном) пользователе на ПК с Windows.   -  person Harry    schedule 27.01.2017
comment
Пожалуйста, прочтите перед тем, как комментировать: я знаю, как проверить, является ли ТЕКУЩИЙ пользователь администратором, я спрашиваю, является ли НЕ ТЕКУЩИЙ пользователь администратором, и это огромная разница!   -  person Harry    schedule 27.01.2017


Ответы (1)


Что ж, узнал, но все равно странно ...

Обновленный код: (я изменил совпадающее имя группы на соответствующее 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();

Оказывается, GetGroups() намного быстрее, чем IsMemberOf().

Обновление: на самом деле это примерно в 135 раз быстрее. GetGroups() с Any() заняло 17 мс вместо 2300 мс IsMemberOf().

В качестве бонуса поделюсь своим 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();
    }
}
person Harry    schedule 27.01.2017
comment
Насколько это было быстрее? От 2 секунд до ...? - person Fildor; 27.01.2017
comment
@Fildor От 2200 мс до 17 мс при запущенных средствах диагностики;) - person Harry; 27.01.2017
comment
GetGroups не учитывает вложенные группы. Возможно, поэтому это быстрее, но это также означает, что результаты могут быть неполными, в зависимости от контекста. - person Harry Johnston; 28.01.2017
comment
@HarryJohnston Могут ли локальные группы в Windows 10 быть вложенными? Тогда эта разница во времени, кажется, указывает на что-то большее, чем просто немного более сложный запрос. System.DirectoryServices, конечно, не ограничивается локальными группами, однако контекст Machine. Может быть, тестируется сам локальный контекст в сетевом контексте? Но нет смысла определять доступ к местным ресурсам. Однако это имеет смысл для удаленного входа в систему. - person Harry; 28.01.2017
comment
Хм. Если это локальная учетная запись и если машина не является контроллером домена, я не думаю, что группы могут быть вложенными. Таким образом (игнорируя патологические случаи, такие как INTERACTIVE нахождение в группе администраторов), вы, вероятно, получите точные результаты от .GetGroups, за исключением, конечно, проверки SID, а не просмотра строк. Но задержка в .IsMember действительно может быть связана с проверкой домена, хотя это на самом деле не объясняет задержку. Если время ожидания запроса не истекло? - person Harry Johnston; 28.01.2017