/// <summary>
        /// Get a user in a principal context
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="pc"></param>
        /// <returns></returns>
        private static UserPrincipal FindUser(string userName, PrincipalContext pc)
        {
            UserPrincipal     user;
            PrincipalSearcher searcher;

            var parsedName = new FqdnNameParser(userName);

            user = new UserPrincipal(pc);
            user.SamAccountName = parsedName.SamAccountName;
            searcher            = new PrincipalSearcher(user);
            user = searcher.FindOne() as UserPrincipal;

            return(user);
        }
        /// <summary>
        /// Search a user hierarchicaly in the AD structure
        /// </summary>
        /// <param name="userName">The user name or identifier</param>
        /// <param name="ac">The principal context global configuration</param>
        /// <param name="pc">The principal context this user was found in</param>
        /// <returns></returns>
        private static UserPrincipal SearchUser(string userName, AccountManagementPrincipalContext ac, out PrincipalContext pc)
        {
            UserPrincipal user = null;

            var name = new FqdnNameParser(userName);

            if (name.ContextType == ContextType.Machine)
            {
                pc = BuildPrincipal(null);

                user = FindUser(userName, pc);

                if (user != null)
                {
                    return(user);
                }

                pc.Dispose();

                return(null);
            }

            // Probar sin OU
            pc = BuildPrincipal(ac);

            user = FindUser(userName, pc);

            if (user != null)
            {
                return(user);
            }

            pc.Dispose();

            // Probar con OU
            pc = BuildPrincipalWithoutOu(ac);

            user = FindUser(userName, pc);

            if (user != null)
            {
                return(user);
            }

            pc.Dispose();

            return(null);
        }
        /// <summary>
        /// Get a group in a principal context
        /// </summary>
        /// <param name="groupName">The group name or identifier</param>
        /// <param name="pc">The principal context</param>
        /// <returns></returns>
        private static GroupPrincipal FindGroup(string groupName, PrincipalContext pc)
        {
            GroupPrincipal    group;
            PrincipalSearcher searcher;

            var parsedName = new FqdnNameParser(groupName);

            // Search by SamGroupName
            if (!string.IsNullOrWhiteSpace(parsedName.SamAccountName))
            {
                group = new GroupPrincipal(pc);
                group.SamAccountName = parsedName.SamAccountName;
                searcher             = new PrincipalSearcher(group);
                group = searcher.FindOne() as GroupPrincipal;

                if (group != null)
                {
                    return(group);
                }
            }

            // Search by name (original approach, backwards compatiblity)
            if (!string.IsNullOrWhiteSpace(parsedName.SamAccountName))
            {
                group      = new GroupPrincipal(pc);
                group.Name = parsedName.SamAccountName;
                searcher   = new PrincipalSearcher(group);
                group      = searcher.FindOne() as GroupPrincipal;

                if (group != null)
                {
                    return(group);
                }
            }

            if (parsedName.Sid != null)
            {
                group = GroupPrincipal.FindByIdentity(pc, IdentityType.Sid, parsedName.Sid.ToString());

                if (group != null)
                {
                    return(group);
                }
            }

            return(null);
        }
        /// <summary>
        /// Search for a group in diferent principal context in the following order: local, domain without OU, domain with OU
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="ac"></param>
        /// <param name="pc"></param>
        /// <returns></returns>
        private static GroupPrincipal SearchGroup(string groupName, AccountManagementPrincipalContext ac, out PrincipalContext pc)
        {
            var name = new FqdnNameParser(groupName);

            GroupPrincipal group = null;

            if (name.ContextType == ContextType.Machine)
            {
                pc = BuildPrincipal(null);

                group = FindGroup(groupName, pc);

                if (group != null)
                {
                    return(group);
                }

                pc.Dispose();

                return(null);
            }

            // Look in provided OU
            pc = BuildPrincipal(ac);

            group = FindGroup(groupName, pc);

            if (group != null)
            {
                return(group);
            }

            pc.Dispose();

            // Look in domain
            pc = BuildPrincipalWithoutOu(ac);

            group = FindGroup(groupName, pc);

            if (group != null)
            {
                return(group);
            }

            pc.Dispose();
            return(null);
        }
        /// <summary>
        /// Create a user or return one if it does not exist.
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="password"></param>
        /// <param name="displayName"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        /// <returns></returns>
        public static UserPrincipal EnsureUserExists(
            string identity,
            string password,
            string displayName,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            var parsedUserName = new FqdnNameParser(identity);

            if (parsedUserName.UserPrincipalName.Length > 64)
            {
                throw new Exception($"Windows account userPrincipalName '{parsedUserName.UserPrincipalName}' cannot be longer than 64 characters.");
            }

            using (PrincipalContext pc = BuildPrincipal(acp))
            {
                UserPrincipal up = FindUser(identity, pc);

                string samAccountName = parsedUserName.SamAccountName;

                logger.LogInfo(false, $"Ensure windows account exists '{samAccountName}@{password}' with userPrincipal '{identity}'");

                if (up == null)
                {
                    up = new UserPrincipal(pc, samAccountName, password, true);
                }
                else
                {
                    logger.LogInfo(true, $"Found account IsAccountLockedOut={up.IsAccountLockedOut()}, SamAccountName={up.SamAccountName}");

                    // Make sure we have the latest password, just in case
                    // the pwd algorithm generation changes...
                    try
                    {
                        up.SetPassword(password);
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning(true, "Cannot update password for account: " + e.Message + e.InnerException?.Message);
                    }
                }

                up.UserCannotChangePassword = true;
                up.PasswordNeverExpires     = true;
                up.Enabled     = true;
                up.DisplayName = displayName;
                up.Description = parsedUserName.UserPrincipalName;

                // If we are in a domain, assign the user principal name
                if (pc.ContextType == ContextType.Domain)
                {
                    logger.LogInfo(true, "Setting UserPrincipalName to '{0}'", parsedUserName.UserPrincipalName);
                    up.UserPrincipalName = parsedUserName.UserPrincipalName + "@" + parsedUserName.DomainName.ToLower();
                }

                if (up.IsAccountLockedOut())
                {
                    try
                    {
                        up.UnlockAccount();
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning(true, "Cannot unlock account: " + e.Message + e.InnerException?.Message);
                    }
                }

                try
                {
                    up.Save();
                }
                catch (Exception e)
                {
                    logger.LogException(new Exception("Error while saving user", e), EventLogEntryType.Warning);

                    // Sometimes it crashes, but everything was OK (weird?)
                    // so we check again if the user has been created
                    Thread.Sleep(500);
                    up = FindUser(identity, pc);

                    if (up == null)
                    {
                        // Rethrow the original, whatever it was.
                        ExceptionDispatchInfo.Capture(e).Throw();
                    }
                }

                return(up);
            }
        }