Active Directory - Adding a user to a group from a non domain-joined computer throws PrincipalException
When using the new System.DirectoryServices.AccountManagement namespace, one might be inclined (as was I) to use the following code to add a user to a group (exception and comments handling removed):
using (GroupPrincipal groupPrincipal =
GroupPrincipal.FindByIdentity(
domainContext,
System.DirectoryServices.AccountManagement.IdentityType.Name,
groupName))
{
if (groupPrincipal != null)
{
using (UserPrincipal userPrincipal =
UserPrincipal.FindByIdentity(
domainContext,
System.DirectoryServices.AccountManagement.IdentityType.UserPrincipalName,
UPN))
{
if (userPrincipal != null)
{
groupPrincipal.GroupScope = GroupScope.Global;
groupPrincipal.Members.Add(userPrincipal);
groupPrincipal.Save();
}
}
}
}
This works just fine if your machine is joined to the domain you're trying to provision. However, if your machine is not joined, the groupPrincipal.Save(); call throws a PrincipalException with an error code 1355 ("Information about the domain could not be retrieved (1355)").
Joining the domain solves this issue.
So what if joining the domain isn't an option?
In this case I found resorting to good old System.DirectoryServices fixes the issue, using the following code fragment:
using (UserPrincipal userPrincipal =
UserPrincipal.FindByIdentity(domainContext, UPN))
{
if (userPrincipal != null)
{
using (GroupPrincipal groupPrincipal =
GroupPrincipal.FindByIdentity(domainContext, groupName))
{
if (groupPrincipal != null)
{
if (!userPrincipal.IsMemberOf(groupPrincipal))
{
string userSid = string.Format("<SID={0}>", userPrincipal.ToSidString());
DirectoryEntry groupDirectoryEntry =
(DirectoryEntry)groupPrincipal.GetUnderlyingObject();
groupDirectoryEntry.Properties["member"].Add(userSid);
groupDirectoryEntry.CommitChanges();
}
}
}
}
}
ToSidString is an extension method which translates the "objectSid" property. Thanks to Richard for the way better implementation! (ignore "Enforce")
public static string ToSidString(this Principal principal)
{
Enforce.IsNotNull<Principal>(principal, "principal");
SecurityIdentifier sid = principal.Sid;
if (sid == null || sid.BinaryLength == 0)
{
return null;
}
byte[] buffer = new byte[sid.BinaryLength];
sid.GetBinaryForm(buffer, 0);
string[] hexBytes = Array.ConvertAll(buffer, b => b.ToString("X2", NumberFormatInfo.InvariantInfo));
return string.Concat(hexBytes);
}
HTH!
Comments
Anonymous
January 05, 2010
You don't need to access the underlying object to get the Sid - it's exposed as a property on the Principal class: public static string ToSidString(this Principal principal) { if (null == principal) throw new ArgumentNullException("principal"); SecurityIdentifier sid = principal.Sid; if (null == sid || 0 == sid.BinaryLength) return null; byte[] buffer = new byte[sid.BinaryLength]; sid.GetBinaryForm(buffer, 0); string[] hexBytes = Array.ConvertAll(buffer, b => b.ToString("X2", NumberFormatInfo.InvariantInfo)); return string.Concat(hexBytes); }Anonymous
January 05, 2010
The comment has been removedAnonymous
August 28, 2013
Thanks for the info. I also found that groupDirectoryEntry.Properties["member"].Add(userPrincipal.DistinguishedName); also works.