Пример #1
0
        /// <summary>
        /// Delete a directory
        /// </summary>
        /// <param name="strDir">Directory to delete</param>
        /// <param name="logger"></param>
        /// <param name="closeProcesses">Force a process close if it cannot be deleted (i.e. in use)</param>
        /// <param name="waitTimeIfInUse">If in-use, time to wait (in seconds) before either failing or closing all processes if forceCloseProcesses is true.</param>
        private static void DoDeleteDirectory(
            string strDir,
            ILoggerInterface logger,
            List <string> closeProcesses = null,
            int waitTimeIfInUse          = 10)
        {
            if (string.IsNullOrWhiteSpace(strDir))
            {
                logger.LogWarning(true, "Empty directory name provided DoDeleteDirectory, skipping.");
                return;
            }

            ValidateDirectoryDepthDeletion(strDir);
            strDir = EnsureLongPathSupportIfAvailable(strDir);

            if (!Directory.Exists(strDir))
            {
                return;
            }

            logger.LogInfo(true, "Removing directory {0} with close processes {1}", strDir, closeProcesses == null ? string.Empty : string.Join(", ", closeProcesses));

            if (closeProcesses?.Any() == true)
            {
                var processes = UtilsProcess.GetPathProcessesInfo(strDir, logger, true);

                foreach (var p in processes.AsIterable())
                {
                    logger.LogWarning(
                        false,
                        "The following process might be blocking files in the directory: {0}",
                        p.CommandLine);
                }

                if (processes.Any())
                {
                    UtilsProcess.ClosePathProcesses(strDir, closeProcesses, logger);
                }
            }

            RetryWhile(
                () =>
            {
                DeleteDirectoryAndRemovePermissionsIfNeeded(strDir);
            },
                ExceptionIsAccessDeniedOrFileInUse,
                waitTimeIfInUse * 1000,
                logger);

            if (Directory.Exists(strDir))
            {
                throw new Exception($"Could not completely delete directory '{strDir}', see log for details.");
            }
        }
Пример #2
0
        /// <summary>
        /// Central store path for certificates. Returns exception if not configured or cannot be returned.
        /// </summary>
        public static string CentralStorePath(ILoggerInterface logger)
        {
            if (!CentralStoreEnabled())
            {
                throw new Exception(
                          "IIS Central store path not enabled or installed. Please check https://blogs.msdn.microsoft.com/kaushal/2012/10/11/central-certificate-store-ccs-with-iis-8-windows-server-2012/");
            }

            string certStoreLocation = Convert.ToString(UtilsRegistry.GetRegistryKeyValue64(
                                                            RegistryHive.LocalMachine,
                                                            "SOFTWARE\\Microsoft\\IIS\\CentralCertProvider",
                                                            "CertStoreLocation",
                                                            string.Empty));

            if (string.IsNullOrWhiteSpace(certStoreLocation))
            {
                throw new Exception("IIS Central store location not configured");
            }

            var resolvedCertStoreLocation = certStoreLocation;

            if (UtilsJunction.IsJunctionOrSymlink(certStoreLocation))
            {
                resolvedCertStoreLocation = UtilsJunction.ResolvePath(resolvedCertStoreLocation);
            }

            if (UtilsSystem.IsNetworkPath(resolvedCertStoreLocation))
            {
                logger.LogWarning(true, "Central Certificate Store Path is located on a network share [{0}]. This has proven to be unstable as CCS will cache corrupted certificates when it is unable to read from the network share.", certStoreLocation);
            }

            return(certStoreLocation);
        }
Пример #3
0
        /// <summary>
        /// Get the executable path of a process from it's process id
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        public static ProcessInfo GetProcessInfo(int processId, ILoggerInterface logger)
        {
            ProcessInfo result = new ProcessInfo();

            result.ProcessId = processId;

            try
            {
                string query = "SELECT ExecutablePath, Name, CommandLine FROM Win32_Process WHERE ProcessId = " + processId;

                using (ManagementObjectSearcher mos = new ManagementObjectSearcher(query))
                {
                    using (ManagementObjectCollection moc = mos.Get())
                    {
                        result.MainModulePath = (from mo in moc.Cast <ManagementObject>() select mo["ExecutablePath"]).First().ToString();
                        result.ProcessName    = (from mo in moc.Cast <ManagementObject>() select mo["Name"]).First().ToString();
                        result.CommandLine    = (from mo in moc.Cast <ManagementObject>() select mo["CommandLine"]).First().ToString();
                    }
                }
            }
            catch (Exception e)
            {
                logger.LogWarning(true, e.Message);
            }

            return(result);
        }
        /// <summary>
        /// Ensure user is in group
        /// </summary>
        /// <param name="userPrincipalName"></param>
        /// <param name="groupname"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        public static void EnsureUserInGroup(
            string userPrincipalName,
            string groupname,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            logger.LogInfo(true, $"Ensure user '{userPrincipalName}' in group '{groupname}'");

            UserPrincipal user = SearchUser(userPrincipalName, acp, out var userContext);

            if (user == null)
            {
                userContext?.Dispose();
                throw new Exception($"User '{userPrincipalName}' not found.");
            }

            GroupPrincipal group = SearchGroup(groupname, acp, out var groupContext);

            if (group == null)
            {
                userContext?.Dispose();
                groupContext?.Dispose();
                throw new Exception($"Group '{groupname}' not found.");
            }

            logger.LogWarning(false, $"Found group '{group.Name}' '{group.Sid}' in context '{groupContext.ConnectedServer}' and '{groupContext.ContextType}'");

            foreach (Principal member in group.GetMembers(true))
            {
                if (member.SamAccountName == user.SamAccountName)
                {
                    logger.LogInfo(true, $"User already in group '{groupname}'");
                    return;
                }
            }

            group.Members.Add(user);
            group.Save();

            userContext.Dispose();
            groupContext.Dispose();

            logger.LogInfo(true, $"Added user '{userPrincipalName}' to group '{groupname}'");
        }
Пример #5
0
        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="source"></param>
        /// <param name="condition"></param>
        /// <param name="selector"></param>
        /// <param name="name"></param>
        /// <param name="logger"></param>
        /// <returns></returns>
        public static List <T2> QueryEnumerable <T, T2>(IEnumerable <T> source, Func <T, bool> condition, Func <T, T2> selector, Func <T, string> name, ILoggerInterface logger)
        {
            var results = new List <T2>();

            foreach (var s in source)
            {
                bool isMatch = false;

                try
                {
                    isMatch = condition(s);
                }
                catch (Exception e)
                {
                    string displayName = null;

                    try
                    {
                        displayName = name(s);
                    }
                    catch
                    {
                        // ignored
                    }

                    logger.LogWarning(true, "Error while inspecting condition on object {0}: {1}", displayName, e.Message);
                }

                if (!isMatch)
                {
                    continue;
                }

                results.Add(selector(s));
            }

            return(results);
        }
Пример #6
0
        /// <summary>
        /// Creates a link (symlink or junction)
        /// </summary>
        /// <param name="mountPath">Path where the symlink or junction will be created.</param>
        /// <param name="mountDestination">Path the JUNCTION points to.</param>
        /// <param name="logger"></param>
        /// <param name="persistOnDeploy">If true, any files in the repo are synced to the content folder.</param>
        /// <param name="overWrite">If a junction or link already exists, overwrite it</param>
        /// <param name="linkType">Use this to force usage of symlinks. Otherwise junction/symlink is chosen by the method internally.</param>
        /// <returns></returns>
        public static void EnsureLink(
            string mountPath,
            string mountDestination,
            ILoggerInterface logger,
            bool persistOnDeploy,
            bool overWrite           = false,
            LinkTypeRequest linkType = LinkTypeRequest.Auto)
        {
            var linkmanager = ReparsePointFactory.Create();

            // On a local folder based deployment we might be redeploying on top of same application...
            if (Directory.Exists(mountPath))
            {
                logger.LogWarning(true, "Mount destination already exists: {0}", mountPath);

                if (linkmanager.GetLinkType(mountPath) == LinkType.Junction ||
                    linkmanager.GetLinkType(mountPath) == LinkType.Symbolic)
                {
                    logger.LogInfo(true, "Mount destination is junction, grabbing attributes.");
                    var atts = linkmanager.GetLink(mountPath);
                    logger.LogInfo(true, "Mount destination attributes: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(atts));

                    var currentTarget  = new DirectoryInfo(atts.Target);
                    var requiredTarget = new DirectoryInfo(mountDestination);

                    if (currentTarget.FullName == requiredTarget.FullName || overWrite)
                    {
                        // Remove it, it will be recreated anyways.
                        Directory.Delete(mountPath);
                    }
                    else
                    {
                        // Something already exists. And it is NOT a junction equivalent to what
                        // we are asking for.
                        throw new Exception($"Could not mount junction because a directory or junction already exists at the junction source path: {mountPath}");
                    }
                }
                else
                {
                    bool existingDirectoryHasFiles = Directory.EnumerateFiles(mountPath, "*", SearchOption.AllDirectories).Any();

                    // If the mountpath exists, but has nothing in it, delete it to make this process more error-proof.
                    if (Directory.Exists(mountPath))
                    {
                        if (persistOnDeploy)
                        {
                            // Copy any files, and then delete
                            UtilsSystem.CopyFilesRecursivelyFast(mountPath, mountDestination, true, null, logger);
                            Directory.Delete(mountPath, true);
                        }
                        else
                        {
                            if (!existingDirectoryHasFiles)
                            {
                                // Delete so we can junction
                                Directory.Delete(mountPath, true);
                            }
                        }
                    }
                }
            }

            // Create junction will fail if the physicial folder exists at the junction target
            // so the previous logic takes care of that...7
            bool useSymlinkInsteadOfJunction;

            switch (linkType)
            {
            case LinkTypeRequest.Auto:
                useSymlinkInsteadOfJunction = mountDestination.StartsWith("\\");
                break;

            case LinkTypeRequest.Junction:
                useSymlinkInsteadOfJunction = false;
                break;

            case LinkTypeRequest.Symlink:
                useSymlinkInsteadOfJunction = true;
                break;

            default:
                throw new NotSupportedException();
            }

            logger.LogInfo(true, $"Creating {(useSymlinkInsteadOfJunction ? "symlink" : "junction")} '{mountPath}' => '{mountDestination}'");

            // For remote drives, junctions will not work
            // https://helpcenter.netwrix.com/Configure_IT_Infrastructure/File_Servers/Enable_Symlink.html
            linkmanager.CreateLink(mountPath, mountDestination, useSymlinkInsteadOfJunction ? LinkType.Symbolic : LinkType.Junction);
        }
Пример #7
0
        /// <summary>
        /// Find a certificate in IIS central certificate store
        /// </summary>
        /// <param name="hostName"></param>
        /// <param name="logger"></param>
        /// <param name="certificatePath"></param>
        /// <returns></returns>
        public static X509Certificate2 FindCertificateInCentralCertificateStore(
            string hostName,
            ILoggerInterface logger,
            out string certificatePath)
        {
            string   centralStorePath = UtilsIis.CentralStorePath(logger);
            FileInfo certificateFile  = null;

            // Look for a certificate file that includes wildcard matching logic
            // https://serverfault.com/questions/901494/iis-wildcard-https-binding-with-centralized-certificate-store
            var hostNameParts = hostName.Split(".".ToCharArray()).Reverse().ToList();

            foreach (var f in new DirectoryInfo(centralStorePath).EnumerateFiles())
            {
                // Check if this certificate file is valid for the hostname...
                var certNameParts = Path.GetFileNameWithoutExtension(f.FullName).Split(".".ToCharArray()).Reverse()
                                    .ToList();

                // This won't allow for nested subdomain with wildcards, but it's a good starting point
                // i.e. a hostname such as "a.mytest.mydomain.com" won't be matched to a certifica
                // such as "_.mydomain.com"
                // but "mytest.mydomain.com" will match to "_.mydomain.com".
                if (certNameParts.Count != hostNameParts.Count)
                {
                    continue;
                }

                bool isMatch = true;

                for (int x = 0; x < hostNameParts.Count; x++)
                {
                    if (hostNameParts[x] == "*" || certNameParts[x] == "_")
                    {
                        continue;
                    }

                    if (hostNameParts[x] != certNameParts[x])
                    {
                        isMatch = false;
                        break;
                    }
                }

                if (isMatch)
                {
                    certificateFile = f;
                    break;
                }
            }

            certificatePath = certificateFile?.FullName;

            // This is null on purpose.
            string certificatePassword = null;

            X509Certificate2Collection collection = new X509Certificate2Collection();

            if (certificateFile != null)
            {
                logger.LogInfo(true, "Found potential certificate matching file at {0}", certificateFile.FullName);

                try
                {
                    // Usamos ephemeral keyset para que no almacene claves en la máquina todo el tiempo...
                    collection.Import(certificateFile.FullName, certificatePassword, X509KeyStorageFlags.EphemeralKeySet);
                    var originalCert = collection[0];

                    logger.LogInfo(true, "Certificate IssuerName '{0}'", originalCert.IssuerName.Name);
                    logger.LogInfo(true, "Certificate FriendlyName '{0}'", originalCert.FriendlyName);
                    logger.LogInfo(true, "Certificate SubjectName '{0}'", originalCert.SubjectName.Name);
                    logger.LogInfo(true, "Certificate NotBefore '{0}'", originalCert.NotBefore.ToString("HH:mm:ss yyyy/MM/dd"));

                    return(originalCert);
                }
                catch (Exception e)
                {
                    logger.LogWarning(false, $"Error importing certificate: '{certificateFile.FullName}'." + e.Message);
                }
            }

            return(null);
        }
Пример #8
0
        public static X509Certificate2 CreateSelfSignedCertificateOldImplementationNetFramework(
            string subjectName,
            string friendlyName,
            ILoggerInterface logger,
            int expirationDays = 90)
        {
            // create DN for subject
            var dn = new CX500DistinguishedName();

            dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);

            // create DN for the issuer
            var issuer = new CX500DistinguishedName();

            issuer.Encode("CN=ChefCertificate", X500NameFlags.XCN_CERT_NAME_STR_NONE);

            // create a new private key for the certificate
            CX509PrivateKey privateKey = new CX509PrivateKey();

            privateKey.ProviderName   = "Microsoft Base Cryptographic Provider v1.0";
            privateKey.MachineContext = true;
            privateKey.Length         = 2048;
            privateKey.KeySpec        = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
            privateKey.ExportPolicy   = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
            privateKey.Create();

            // Use the stronger SHA512 hashing algorithm
            var hashobj = new CObjectId();

            hashobj.InitializeFromAlgorithmName(
                ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
                ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
                AlgorithmFlags.AlgorithmFlagsNone,
                "SHA512");

            // add extended key usage if you want - look at MSDN for a list of possible OIDs
            var oid = new CObjectId();

            oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
            var oidlist = new CObjectIds();

            oidlist.Add(oid);
            var eku = new CX509ExtensionEnhancedKeyUsage();

            eku.InitializeEncode(oidlist);

            // Create the self signing request
            var cert = new CX509CertificateRequestCertificate();

            cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, string.Empty);
            cert.Subject   = dn;
            cert.Issuer    = issuer;
            cert.NotBefore = DateTime.Now;

            // this cert expires immediately. Change to whatever makes sense for you
            cert.NotAfter = DateTime.Now.AddDays(expirationDays);
            cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
            cert.HashAlgorithm = hashobj;                 // Specify the hashing algorithm
            cert.Encode();                                // encode the certificate

            // Do the final enrollment process
            var enroll = new CX509Enrollment();

            enroll.InitializeFromRequest(cert);            // load the certificate
            enroll.CertificateFriendlyName = friendlyName; // Optional: add a friendly name
            string csr = enroll.CreateRequest();           // Output the request in base64

            // and install it back as the response
            enroll.InstallResponse(
                InstallResponseRestrictionFlags.AllowUntrustedCertificate,
                csr,
                EncodingType.XCN_CRYPT_STRING_BASE64,
                string.Empty); // no password

            // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
            var base64Encoded = enroll.CreatePFX(
                string.Empty, // no password, this is for internal consumption
                PFXExportOptions.PFXExportChainWithRoot);

            // Delete the key
            var storePath = UtilsCertificate.FindKeyStoragePath(privateKey.UniqueContainerName);

            if (string.IsNullOrWhiteSpace(storePath))
            {
                logger.LogWarning(false, $"Unable to determine private key store path for key with UCN '{privateKey.UniqueContainerName}', keys will build up in disk storage.");
            }
            else
            {
                File.Delete(storePath);
            }

            // instantiate the target class with the PKCS#12 data (and the empty password)
            var crt = new X509Certificate2(
                Convert.FromBase64String(base64Encoded),
                string.Empty,

                // mark the private key as exportable (this is usually what you want to do)
                X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable);

            // Remove the locally signed certificate from the local store, as we are only interested in
            // the exported PFX file
            UtilsCertificate.RemoveCertificateFromLocalStoreByThumbprint(crt.Thumbprint);

            return(crt);
        }
Пример #9
0
        /// <summary>
        /// Closes all the handles that block any files in the specified path
        /// </summary>
        /// <param name="path"></param>
        /// <param name="allowedProcesses">List of whitelisted processes</param>
        /// <param name="logger"></param>
        public static void ClosePathProcesses(
            string path,
            List <string> allowedProcesses,
            ILoggerInterface logger)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                return;
            }

            // Make sure the path exists
            if (!File.Exists(path) && !Directory.Exists(path))
            {
                return;
            }

            // Load list of processes that block directory
            var processes = GetPathProcessesInfo(path, logger);

            // Filter the whitelisted
            string regex = string.Join("|", allowedProcesses);
            var    processesThatWillBeClosed = processes.Where((i) => i.MainModulePath != null && Regex.IsMatch(i.MainModulePath, regex)).ToList();

            if (!processesThatWillBeClosed.Any())
            {
                return;
            }

            // Message of processes that will not be closed
            var processesThatWillNotBeClosed = processes.Except(processesThatWillBeClosed).ToList();

            if (processesThatWillNotBeClosed.Any())
            {
                logger.LogWarning(true, "The following processes are not whitelisted and will not be closed {0}", string.Join(", ", processesThatWillNotBeClosed.Select((i) => i.ProcessName)));
            }

            // Grab the actual process instances
            var processesInstances = GetProcessInstance(processesThatWillBeClosed);

            // First kill al the processes.
            foreach (var p in processesInstances)
            {
                try
                {
                    logger.LogInfo(true, "Killing process: {0}", p.ProcessName);

                    if (!p.HasExited)
                    {
                        p.Kill();
                        p.WaitForExit(3000);
                    }
                }
                catch (Exception e)
                {
                    logger.LogException(e, EventLogEntryType.Warning);
                }
            }

            // Even though the processes have exited, handles take a while to be released
            Thread.Sleep(500);

            foreach (var p in processesInstances)
            {
                bool hasClosed = UtilsSystem.WaitWhile(() => !p.HasExited, 15000, $"Waiting for process {p.ProcessName} to close.", logger);
                logger.LogInfo(true, "Process {0} has closed: {1}", p.ProcessName, hasClosed);
            }
        }
Пример #10
0
        /// <summary>
        /// Copy files recursively.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="overwrite"></param>
        /// <param name="ignoreOnDeployPattern"></param>
        /// <param name="logger"></param>
        private static void DoCopyFilesRecursivelyFast(
            DirectoryInfo source,
            DirectoryInfo target,
            bool overwrite,
            string ignoreOnDeployPattern,
            ILoggerInterface logger)
        {
            var             files = source.EnumerateFiles("*", SearchOption.AllDirectories);
            ParallelOptions pop   = new ParallelOptions();

            // The bottle neck here is disk rather than CPU... but number of CPU's is a good measure
            // of how powerful the target machine might be...
            pop.MaxDegreeOfParallelism = (int)Math.Ceiling(Environment.ProcessorCount * 1.5);
            logger.LogInfo(true, "Copying files from {1} with {0} threads.", pop.MaxDegreeOfParallelism, source.FullName);

            var ignoreOnDeployRegex = string.IsNullOrWhiteSpace(ignoreOnDeployPattern)
                ? null
                : new Regex(ignoreOnDeployPattern);

            int fileCount = 0;
            int dirCount  = 0;

            Stopwatch sw = new Stopwatch();

            sw.Start();

            Parallel.ForEach(files, pop, (i) =>
            {
                try
                {
                    var dir = i.Directory.FullName;

                    var relativeDir =
                        dir.Substring(source.FullName.Length, dir.Length - source.FullName.Length)
                        .TrimStart("\\".ToCharArray());

                    var relativeFile = Path.Combine(relativeDir, i.Name);

                    var destDir  = Path.Combine(target.FullName, relativeDir);
                    var destFile = new FileInfo(Path.Combine(destDir, i.Name));

                    if (ignoreOnDeployRegex?.IsMatch(relativeFile) == true)
                    {
                        return;
                    }

                    if (!Directory.Exists(destDir))
                    {
                        Directory.CreateDirectory(destDir);
                        Interlocked.Add(ref dirCount, 1);
                    }

                    if ((!destFile.Exists) || (destFile.Exists && overwrite))
                    {
                        i.CopyTo(destFile.FullName, true);
                        Interlocked.Add(ref fileCount, 1);
                    }
                }
                catch (Exception e)
                {
                    // Reserved names cannot be copied...
                    if (InvalidWindowsFileNames.Contains(i.Name.ToLower()))
                    {
                        logger.LogWarning(true, "Skipped invalid file: " + i.FullName);
                    }
                    else
                    {
                        throw new Exception("Error copying file: " + i.FullName, e);
                    }
                }

                if (sw.ElapsedMilliseconds > 2000)
                {
                    lock (string.Intern("utils-system-filecopy-fast"))
                    {
                        if (sw.ElapsedMilliseconds > 2000)
                        {
                            try
                            {
                                int leftPos = Console.CursorLeft - 80;
                                Console.SetCursorPosition(leftPos >= 0 ? leftPos : 0, Console.CursorTop);
                                Console.Write($"Copied {fileCount} files.".PadRight(80, " ".ToCharArray().First()));
                            }
                            catch (IOException)
                            {
                                // ignored, the console might no always be available (i.e. in a service)
                                // Exception Type: 'System.IO.IOException
                                // ExceptionMessage: The handle is invalid.
                            }

                            sw.Restart();
                        }
                    }
                }
            });

            logger.LogInfo(true, "Copied {0} files and {1} directories.", fileCount, dirCount);
        }
        /// <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);
            }
        }