Exemple #1
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);
        }
Exemple #2
0
 /// <summary>
 /// Find a site with a specific name in a resilient way
 /// </summary>
 /// <param name="manager"></param>
 /// <param name="siteName"></param>
 /// <param name="logger"></param>
 /// <returns></returns>
 public static List <Site> FindSiteWithName(ServerManager manager, string siteName, ILoggerInterface logger)
 {
     return(UtilsSystem.QueryEnumerable(
                manager.Sites,
                (s) => s.Name == siteName,
                (s) => s,
                (s) => s.Name,
                logger));
 }
Exemple #3
0
        /// <summary>
        /// Write contents of a web.config.
        ///
        /// Takes into consideration maximum web.config file size as defined at the OS level.
        ///
        /// Throws an exception if size is exceeded.
        /// </summary>
        /// <param name="webconfigfilepath"></param>
        /// <param name="webConfigContents"></param>
        /// <returns></returns>
        public static void WriteWebConfig(string webconfigfilepath, string webConfigContents)
        {
            long requiredSizeKb = UtilsSystem.GetStringSizeInDiskBytes(webConfigContents) / 1024;
            int  maxSizeKb      = UtilsIis.GetMaxWebConfigFileSizeInKb() - 1;

            if (requiredSizeKb >= maxSizeKb)
            {
                throw new Exception($"Required web.config size of {requiredSizeKb}Kb for CDN chef feature exceeds current limit of {maxSizeKb}Kb. Please, review this in 'HKLM\\SOFTWARE\\Microsoft\\InetStp\\Configuration\\MaxWebConfigFileSizeInKB'");
            }

            File.WriteAllText(webconfigfilepath, webConfigContents);
        }
Exemple #4
0
        /// <summary>
        /// Get an instance of FqdnNameParser
        /// </summary>
        /// <param name="name"></param>
        /// <param name="autoCalculateSamAccountName">Usernames used by CHEF al have an autocalculated sam account name</param>
        public FqdnNameParser(string name, bool autoCalculateSamAccountName = true)
        {
            // Backwards compatibility fix
            if (name.StartsWith("sid:"))
            {
                name = name.Replace("sid:", string.Empty).Trim();
            }

            if (name.Contains("@"))
            {
                this.DomainName = name.Split("@".ToCharArray()).Last();
                name            = name.Split("@".ToCharArray()).First();
            }

            if (name.Contains("\\"))
            {
                this.DomainName = name.Split("\\".ToCharArray()).First();
                name            = name.Split("\\".ToCharArray()).Last();
            }

            if (UtilsSystem.IsValidSid(name))
            {
                // Assume we are using the user principal name, we cannot determine the context here...
                this.Sid = new SecurityIdentifier(name);
            }
            else
            {
                this.UserPrincipalName = name;

                if (autoCalculateSamAccountName)
                {
                    this.SamAccountName = UtilsWindowsAccounts.SamAccountNameFromUserPrincipalName(this.UserPrincipalName);
                }
            }

            if (name.StartsWith("#"))
            {
                throw new Exception("Unsupported prefix # used in identity definition.");

                // Assume we are using the user principal name, we cannot determine the context here...
                // this.UserPrincipalName = name.Replace("#", string.Empty);
                // this.SamAccountName = name.Replace("#", string.Empty);
            }

            if (!string.IsNullOrWhiteSpace(this.DomainName))
            {
                this.ContextType = (Environment.MachineName == this.DomainName || this.DomainName.Equals("localhost", StringComparison.CurrentCultureIgnoreCase))
                    ? System.DirectoryServices.AccountManagement.ContextType.Machine
                    : System.DirectoryServices.AccountManagement.ContextType.Domain;
            }
        }
Exemple #5
0
        /// <summary>
        /// Delete a site from the server manager.
        ///
        /// The behaviour has been enhanced to prevent deleting a site from affecting bindings from
        /// other sites. See related article. Doing a simple sites.Remove() call can totally mess up bindings from
        /// other websites, which becomes even worse when using CCS (central certificate store).
        ///
        /// @see https://stackoverflow.com/questions/37792421/removing-secured-site-programmatically-spoils-other-bindings
        /// </summary>
        /// <param name="site"></param>
        /// <param name="manager"></param>
        /// <param name="criteria"></param>
        /// <param name="logger"></param>
        public static void RemoveSiteBindings(
            Site site,
            ServerManager manager,
            Func <Binding, bool> criteria,
            ILoggerInterface logger)
        {
            // Site bindings
            var siteBindingsToRemove = site.Bindings.Where(criteria).ToList();

            // Make sure that this is "resilient"
            var allBindings = UtilsSystem.QueryEnumerable(
                manager.Sites,
                (s) => true,
                (s) => s.Bindings,
                (s) => s.Name,
                logger);

            // We have to collect all existing bindings from other sites, including ourse
            var existingBindings = (from p in allBindings
                                    select p.Where((i) => i.Protocol == "https")).SelectMany((i) => i)
                                   .ToList();

            // Remove all bindings for our site, we only care about SSL bindings,the other onse
            // will be removed with the site itself
            foreach (var b in siteBindingsToRemove)
            {
                existingBindings.Remove(b);

                // The central certificate store
                bool bindingUsedInAnotherSite = (from p in existingBindings
                                                 where (p.Host == b.Host &&
                                                        p.BindingInformation == b.BindingInformation)
                                                 ||

                                                 // A combination of port and usage of CCS is a positive, even if in different IP addresses
                                                 (p.SslFlags.HasFlag(SslFlags.CentralCertStore) && b.SslFlags.HasFlag(SslFlags.CentralCertStore) &&
                                                  p.EndPoint.Port.ToString() == b.EndPoint.Port.ToString())
                                                 select 1).Any();

                site.Bindings.Remove(b, bindingUsedInAnotherSite);
            }
        }
Exemple #6
0
 /// <summary>
 /// There is a delay between a serverManager.CommitChanges() and the actual
 /// materialization of the configuration.
 ///
 /// This methods waits for a specific site to be available.
 /// </summary>
 public static void WaitForSiteToBeAvailable(string siteName, ILoggerInterface logger)
 {
     UtilsSystem.RetryWhile(
         () =>
     {
         using (ServerManager sm = new ServerManager())
         {
             // Site is ready when state is available
             var state = UtilsSystem.QueryEnumerable(
                 sm.Sites,
                 (s) => s.Name == siteName,
                 (s) => s,
                 (s) => s.Name,
                 logger).Single().State;
         }
     },
         (e) => true,
         3000,
         logger);
 }
Exemple #7
0
        /// <summary>
        /// Busca la carpeta de user settings para application pools
        /// que están configurados como "ApplicationPoolIdentity".
        /// </summary>
        /// <param name="pool"></param>
        /// <returns></returns>
        public static string FindStorageFolderForAppPoolWithDefaultIdentity(ApplicationPool pool)
        {
            // Peligroso... este método solo para las identidades de defecto.
            if (pool.ProcessModel.IdentityType != ProcessModelIdentityType.ApplicationPoolIdentity)
            {
                return(null);
            }

            // If the user profile is not being loaded, then no need
            // to handle this.
            if (pool.ProcessModel.LoadUserProfile == false)
            {
                return(null);
            }

            var suffix      = "\\" + Environment.UserName;
            var pathWithEnv = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%");

            if (!pathWithEnv.EndsWith(suffix))
            {
                return(null);
            }

            var subpath  = pathWithEnv.Substring(0, pathWithEnv.Length - suffix.Length);
            var userpath = UtilsSystem.CombinePaths(subpath, pool.Name + ".IIS APPPOOL");

            if (Directory.Exists(userpath))
            {
                return(userpath);
            }

            userpath = UtilsSystem.CombinePaths(subpath, pool.Name);
            if (Directory.Exists(userpath))
            {
                return(userpath);
            }

            return(null);
        }
        /// <summary>
        /// Stop a site
        /// </summary>
        /// <param name="p"></param>
        /// <param name="maxWait"></param>
        /// <returns></returns>
        private bool StopSite(Site p, int maxWait = WaitMaxForProcessMs)
        {
            var state = p.State;

            if (state == ObjectState.Stopped)
            {
                return(true);
            }

            if (state != ObjectState.Started)
            {
                return(false);
            }

            UtilsSystem.RetryWhile(() => p.Stop(), (e) => true, 2000, this.Logger);

            Stopwatch sw = Stopwatch.StartNew();

            sw.Start();

            while (true)
            {
                if (sw.ElapsedMilliseconds > maxWait)
                {
                    break;
                }

                if (p.State == ObjectState.Stopped)
                {
                    return(true);
                }

                Thread.Sleep(WaitPauseMs);
            }

            return(false);
        }
Exemple #9
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="type"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string FindResourcePhysicalPath(Type type, string path)
        {
            string codeBasePath = UtilsSystem.GetCodeBaseDir();

            var finalparts = NormalizeResourcePath(type, path);

            // There is a problem with the root path of the assembly,
            // so start removing leading namespaces until we find a physical match.
            for (int x = 0; x < 5; x++)
            {
                List <string> pathParts = new List <string>();
                pathParts.Add(codeBasePath);
                pathParts.AddRange(finalparts.Skip(x).Take(finalparts.Count - x));

                string pathAsFile = CombinePaths(pathParts.ToArray());

                if (File.Exists(pathAsFile) || Directory.Exists(pathAsFile))
                {
                    return(pathAsFile);
                }
            }

            return(null);
        }
Exemple #10
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);
        }
Exemple #11
0
        /// <summary>
        /// IIS is very bad at detecting and handling changes in certificates stored in the
        /// central certificate store, use this method to ensure that a hostname bound
        /// to a SSL termination is properly updated throughout IIS
        ///
        /// https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-85/certificate-rebind-in-iis85
        /// https://delpierosysadmin.wordpress.com/2015/02/23/iis-8-5-enable-automatic-rebind-of-renewed-certificate-via-command-line/
        /// </summary>
        public static void EnsureCertificateInCentralCertificateStoreIsRebound(string hostname, ILoggerInterface logger)
        {
            Dictionary <string, List <Binding> > temporaryBindings = new Dictionary <string, List <Binding> >();

            using (var sm = new ServerManager())
            {
                // Al sites that have an SSL termination bound to this hostname
                var sites = UtilsSystem.QueryEnumerable(
                    sm.Sites,
                    (s) => s.Bindings.Any(i => i.Protocol == "https" && hostname.Equals(i.Host, StringComparison.CurrentCultureIgnoreCase)),
                    (s) => s,
                    (s) => s.Name,
                    logger).ToList();

                // Remove temporarily
                foreach (var site in sites)
                {
                    foreach (var binding in site.Bindings.Where((i) => i.Protocol == "https" && hostname.Equals(i.Host, StringComparison.CurrentCultureIgnoreCase)).ToList())
                    {
                        if (!temporaryBindings.ContainsKey(site.Name))
                        {
                            temporaryBindings[site.Name] = new List <Binding>();
                        }

                        logger.LogInfo(true, "Removed binding {0} from site {1}", binding.BindingInformation, site.Name);

                        temporaryBindings[site.Name].Add(binding);
                        site.Bindings.Remove(binding);
                    }
                }

                CommitChanges(sm);
            }

            // This wait here helps...
            Thread.Sleep(2000);

            // Now restore...
            using (var sm = new ServerManager())
            {
                foreach (var siteName in temporaryBindings.Keys)
                {
                    var site = FindSiteWithName(sm, siteName, logger).Single();

                    foreach (var binding in temporaryBindings[siteName])
                    {
                        var b = site.Bindings.Add(binding.BindingInformation, binding.Protocol);
                        b.SslFlags             = binding.SslFlags;
                        b.CertificateStoreName = binding.CertificateStoreName;
                        b.UseDsMapper          = binding.UseDsMapper;

                        logger.LogInfo(true, "Restored binding {0} to site {1}", binding.BindingInformation, site.Name);
                    }
                }

                CommitChanges(sm);
            }

            // This wait here helps also...
            Thread.Sleep(2000);
        }
 /// <summary>
 /// Remove a host mapping
 /// </summary>
 /// <param name="hostname">Use null to remove all host mappings for this application Id</param>
 /// <param name="owner"></param>
 public void RemoveHostsMapping(string owner, string hostname = null)
 {
     UtilsSystem.RetryWhile(() => this.DoRemoveHostsMapping(owner, hostname), (e) => e is IOException, 2000, this.Logger);
 }
 /// <summary>
 /// Añade un mapping al fichero hosts, o lo actualiza en función del hostname.
 /// Evita añadir duplicados o conflictivos.
 /// </summary>
 /// <param name="address"></param>
 /// <param name="hostname"></param>
 /// <param name="owner"></param>
 public void AddHostsMapping(string address, string hostname, string owner)
 {
     UtilsSystem.RetryWhile(() => this.DoAddHostsMapping(address, hostname, owner), (e) => e is IOException, 2000, this.Logger);
 }
        private static List <Handle> GetProcessesThatBlockPathHandle(string path, ILoggerInterface logger, bool logDetails = false)
        {
            if (!File.Exists(path) && !Directory.Exists(path))
            {
                return(new List <Handle>());
            }

            string key  = "SOFTWARE\\Sysinternals\\Handle";
            string name = "EulaAccepted";

            // This Utility has an EULA GUI on first run... try to avoid that
            // by manually setting the registry
            int?eulaaccepted64 = (int?)UtilsRegistry.GetRegistryKeyValue64(RegistryHive.CurrentUser, key, name, null);
            int?eulaaccepted32 = (int?)UtilsRegistry.GetRegistryKeyValue32(RegistryHive.CurrentUser, key, name, null);

            bool eulaaccepted = (eulaaccepted32 == 1 && eulaaccepted64 == 1);

            if (!eulaaccepted)
            {
                UtilsRegistry.SetRegistryValue(RegistryHive.CurrentUser, key, name, 1, RegistryValueKind.DWord);
            }

            // Normalize the path, to ensure that long path is not used, otherwise handle.exe won't work as expected
            string fileName = UtilsSystem.RemoveLongPathSupport(path);

            List <Handle> result     = new List <Handle>();
            string        outputTool = string.Empty;

            // Gather the handle.exe from the embeded resource and into a temp file
            var handleexe = UtilsSystem.GetTempPath("handle") + Guid.NewGuid().ToString().Replace("-", "_") + ".exe";

            UtilsSystem.EmbededResourceToFile(Assembly.GetExecutingAssembly(), "_Resources.Handle.exe", handleexe);

            try
            {
                using (Process tool = new Process())
                {
                    tool.StartInfo.FileName               = handleexe;
                    tool.StartInfo.Arguments              = fileName;
                    tool.StartInfo.UseShellExecute        = false;
                    tool.StartInfo.Verb                   = "runas";
                    tool.StartInfo.RedirectStandardOutput = true;
                    tool.Start();
                    outputTool = tool.StandardOutput.ReadToEnd();
                    tool.WaitForExit(1000);

                    if (!tool.HasExited)
                    {
                        tool.Kill();
                    }
                }
            }
            catch (Exception e)
            {
                logger.LogException(e, EventLogEntryType.Warning);
            }
            finally
            {
                UtilsSystem.DeleteFile(handleexe, logger, 5);
            }

            string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";

            foreach (Match match in Regex.Matches(outputTool, matchPattern))
            {
                if (int.TryParse(match.Value, out var pid))
                {
                    if (result.All(i => i.pid != pid))
                    {
                        result.Add(new Handle()
                        {
                            pid = pid
                        });
                    }
                }
            }

            if (result.Any() && logDetails)
            {
                logger?.LogInfo(true, outputTool);
            }

            return(result);
        }
        /// <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);
            }
        }
        /// <summary>
        /// Start a site
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private bool StartSite(Site p)
        {
            if (p.State == ObjectState.Started)
            {
                return(true);
            }

            if (p.State != ObjectState.Stopped)
            {
                return(false);
            }

            try
            {
                // Try a couple of times before actually handling an exception
                UtilsSystem.RetryWhile(() => p.Start(), (e) => true, 3000, this.Logger);
            }
            catch (Exception e)
            {
                // This usually happens when a port is in-use
                if (Convert.ToString(e.HResult) == "-2147024864")
                {
                    List <string> ports = new List <string>();

                    foreach (var binding in p.Bindings)
                    {
                        if (ports.Contains(binding.EndPoint.Port.ToString()))
                        {
                            continue;
                        }

                        ports.Add(binding.EndPoint.Port.ToString());
                    }

                    // Let's give a hint into what ports might be in use....
                    var bindings = (from binding in p.Bindings
                                    select binding.Host + "@" + Convert.ToString(binding.EndPoint));

                    // Try to figure out what process is using the port...
                    string process = null;

                    try
                    {
                        var processes = UtilsProcessPort.GetNetStatPorts();
                        process = string.Join(
                            ", " + Environment.NewLine,
                            processes.Where((i) => ports.Contains(i.port_number))
                            .Select((i) => $"{i.process_name}:{i.port_number}"));
                    }
                    catch
                    {
                        // ignored
                    }

                    throw new Exception(
                              "Cannot start website, a port or binding might be already in use by another application or website: " + Environment.NewLine
                              + string.Join(", " + Environment.NewLine, bindings) + Environment.NewLine
                              + "The following port usages have been detected:" + Environment.NewLine
                              + process, e);
                }
                else if (Convert.ToString(e.HResult) == "-2146233088")
                {
                    this.Logger.LogInfo(false, "Cannot start website: " +
                                        Environment.NewLine +
                                        e.Message +
                                        Environment.NewLine +
                                        e.InnerException?.Message);
                    return(true);
                }
                else
                {
                    throw;
                }
            }

            Stopwatch sw = Stopwatch.StartNew();

            sw.Start();

            while (true)
            {
                if (sw.ElapsedMilliseconds > WaitMaxForProcessMs)
                {
                    break;
                }

                if (p.State == ObjectState.Started)
                {
                    return(true);
                }

                Thread.Sleep(WaitPauseMs);
            }

            return(false);
        }