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!