Reputation: 1455
We have a process that requires to check whether a particular user is a member of local Administrators group.
The code that checks that looks like the following:
using (PrincipalContext context = new PrincipalContext(ContextType.Machine, null))
{
UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, sUserName);
if (user != null)
{
SecurityIdentifier adminsGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
GroupPrincipal group = GroupPrincipal.FindByIdentity(context, IdentityType.Sid, adminsGroupSID.Value);
if (group != null)
{
if (user.IsMemberOf(group))
return 0;
}
}
}
When the group has accounts (e.g. domain accounts) that were removed, we're getting a PrincipalOperationException and a message "An error (1332) occurred while enumerating the group membership. The member's SID could not be resolved."
Is there any way to overcome this without: a) Removing manually the orphaned SIDs from the group b) Not ignoring it?
Thanks
Upvotes: 9
Views: 17008
Reputation: 2146
Almost a decade later and even in .NET 4.x and 5.x, this is still a problem. One other way of working around this error is to basically deconstruct the code behind the foreach
statement and make your own list to search. You need to get the Enumerator, then call MoveNext in a Try/Catch. I was originally afraid it would not move to the next one if it threw the exception, but it does, so this works. It's not pretty, but I have tested it and it works for me.
PrincipalContext principalContext;
GroupPrincipal groupPrincipal;
UserPrincipal userPrincipal;
bool hasItem;
// `members` is the new list that can be searched without errors.
List<Principal> members = new List<Principal>();
using (principalContext = new PrincipalContext(ContextType.Machine))
{
SecurityIdentifier adminsGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
using (groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, IdentityType.Sid, adminsGroupSID.Value))
{
/*
* We will get our Enumerator. At this point, we are positioned before
* the current item, so we have to call MoveNext to get a valid Current
*/
var e = groupPrincipal.Members.GetEnumerator();
hasItem = false;
do
{
try
{
/*
* Try and move next. If it failes, it will be caught by the catch
* and ignored. At which point, we can try and call MoveNext again
* and get to the next one.
*/
hasItem = e.MoveNext();
if (hasItem)
{
members.Add(e.Current);
}
}
catch (PrincipalOperationException)
{
// We don't care about doing anything here--we just want to ignore the error
}
} while (hasItem);
}
}
Upvotes: 1
Reputation: 151
public static bool UserHasLocalAdminPrivledges(this UserPrincipal up)
{
SecurityIdentifier id = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
return up.GetAuthorizationGroups().Any(g => g.Sid == id)
}
Upvotes: 0
Reputation: 7547
This is based heavily on my finding at http://www.seirer.net/blog/2013/9/12/how-to-deal-with-localized-or-renamed-administrators-in-net written by Michael Seirer. He was attempting to get the SID of the local Admin account, while all we need are the names in that group. The reason for the error "The member's SID could not be resolved." is because there are accounts that are no longer recognized in Active Directory - likely relics that point to deleted user accounts. You can either do what Microsoft says and just delete them and hope your app never crashes again (though it will, the next time an account is deleted that is sitting in that Administrators group), or solve it permanently with this code I slightly modified from Mike.
using System.DirectoryServices;
using System.Collections;
using System.Runtime.InteropServices;
[DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
private static string GetTextualSID(DirectoryEntry objGroup)
{
string sSID = string.Empty;
byte[] SID = objGroup.Properties["objectSID"].Value as byte[];
IntPtr sidPtr = Marshal.AllocHGlobal(SID.Length);
sSID = "";
System.Runtime.InteropServices.Marshal.Copy(SID, 0, sidPtr, SID.Length);
ConvertSidToStringSid((IntPtr)sidPtr, out sSID);
System.Runtime.InteropServices.Marshal.FreeHGlobal(sidPtr);
return sSID;
}
public static List<string> GetLocalAdministratorsNames()
{
List<string> admins = new List<string>();
DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName);
string adminsSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).ToString();
string localizedAdmin = new System.Security.Principal.SecurityIdentifier(adminsSID).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
localizedAdmin = localizedAdmin.Replace(@"BUILTIN\", "");
DirectoryEntry admGroup = localMachine.Children.Find(localizedAdmin, "group");
object adminmembers = admGroup.Invoke("members", null);
DirectoryEntry userGroup = localMachine.Children.Find("users", "group");
object usermembers = userGroup.Invoke("members", null);
//Retrieve each user name.
foreach (object groupMember in (IEnumerable)adminmembers)
{
DirectoryEntry member = new DirectoryEntry(groupMember);
string sidAsText = GetTextualSID(member);
admins.Add(member.Name);
}
return admins;
}
It will return a List<string>
of members of the local Administrators group on the local machine. You can even alter Environment.MachineName
to be any computer name in your domain, if you don't want the local machine.
Then you can iterate the list to see if they're in it:
private static bool isAdmin(string user)
{
//string user = @"DOMAIN\doej";
user = user.Split(@'\')[1];
List<string> admins = GetLocalAdministratorsNames();
foreach (string s in admins)
{
if (s == user)
return true; // admin found
}
return false; // not an admin
}
Upvotes: 7
Reputation: 59
One way to avoid the error is to go other way around. Instead of checking if an user is member of a group, retrieve first all the groups and check the list for your target group. One drawback: is slower....
var groups = UserPrincipal.Current.GetAuthorizationGroups();
var found = groups.FirstOrDefault(principal => principal.Name == "Administrators");
var isMemberOfAdminGroup = found != null;
Thanks arus for your help :)
Upvotes: 0
Reputation: 6268
There are three possibile solutions (all untested, using the last myself for all sort of domain groups):
1) Load the group and enumerate the members yourself
2) Load the underlying object of the group and use properties["Members"], which is a list of SIDs.
3) Use GetAuthorizationGroups() of the user (which will also use your non-direct groups, Service-Account has to be member of "Windows Authorization Group" and "PreWindows 2000 Comaptible...." eventually) and use the group list to lookup your Admin group.
Upvotes: -1