コード例 #1
0
        /// <summary>
        /// Retry an action while the exception meets the condition during the maximum wait specified
        /// </summary>
        /// <param name="task"></param>
        /// <param name="condition"></param>
        /// <param name="maxWait">Max milliseconds for the operation to complete</param>
        /// <param name="logger"></param>
        /// <param name="minRetries"></param>
        public static void RetryWhile(
            Action task,
            Func <Exception, bool> condition,
            int maxWait,
            ILoggerInterface logger,
            int minRetries = 2)
        {
            Stopwatch sw = Stopwatch.StartNew();

            sw.Start();
            int sleep     = 250;
            int sleepStep = 400;
            int failCount = 0;

            while (true)
            {
                try
                {
                    task();

                    if (failCount > 0)
                    {
                        logger?.LogInfo(true, "Operation completed.");
                    }

                    return;
                }
                catch (Exception e)
                {
                    // If the looping condition is not met, throw the exception.
                    if (!condition(e))
                    {
                        throw;
                    }

                    // If we have reached the maximum wait limit plus we have failed at least once, abort.
                    if (sw.ElapsedMilliseconds > maxWait && failCount >= minRetries)
                    {
                        throw new Exception($"Transient error did not go away after waiting for {maxWait}ms and failing {failCount} times...", e);
                    }

                    failCount++;

                    string errorMessage = e.Message;

                    if (e is AggregateException aggregateException)
                    {
                        errorMessage += "(" + string.Join(
                            ", ",
                            aggregateException.InnerExceptions.Select((i) => i.Message)) + ")";
                    }

                    logger?.LogInfo(true, "Found transient error: {0}", errorMessage);
                    logger?.LogInfo(true, "Retrying operation...");
                    Thread.Sleep(sleep);
                    sleep = sleep + sleepStep;
                }
            }
        }
コード例 #2
0
        /// <summary>
        /// Moves a directory (MOVE) if in same drive, or copies and deletes if between drives
        /// as MOVE operation is not supported in such scenario. Supports long path names.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="logger"></param>
        /// <param name="ignoreOnDeployPattern"></param>
        public static void MoveDirectory(string source, string destination, ILoggerInterface logger, string ignoreOnDeployPattern = null)
        {
            try
            {
                source      = EnsureLongPathSupportIfAvailable(source);
                destination = EnsureLongPathSupportIfAvailable(destination);

                RetryWhile(
                    () => { Directory.Move(source, destination); },

                    // Retry while access to the path is denied, in move operations
                    // this might happen due to files being scanned by an antivirus
                    // or other transient locks
                    (e) => Convert.ToString((uint)e.HResult) == "2147942405",
                    10000,
                    logger);
            }
            catch (IOException e)
            {
                if (e.HResult != -2146232800)
                {
                    throw;
                }

                logger.LogInfo(
                    true,
                    $"Move operation cannot complete because source '{source}' and destination '{destination}' are on same drive, falling back to copy.");

                CopyFilesRecursivelyFast(source, destination, false, ignoreOnDeployPattern, logger);
                Directory.Delete(source, true);
            }
        }
コード例 #3
0
        /// <summary>
        /// Get an instance of Application
        /// </summary>
        /// <param name="parentLogger">Logger implementation</param>
        public Application(ILoggerInterface parentLogger)
        {
            NewRelic.Api.Agent.NewRelic.SetApplicationName("IisChef");

            NewRelicAgentExtensions.AddCustomParameter("server", Environment.MachineName);
            NewRelicAgentExtensions.AddCustomParameter("user", Environment.UserName);

            BindingRedirectHandler.DoBindingRedirects(AppDomain.CurrentDomain);

            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol  = SecurityProtocolType.Tls
                                                    | SecurityProtocolType.Tls11
                                                    | SecurityProtocolType.Tls12
                                                    | SecurityProtocolType.Ssl3;

            // Check current account
            var identity  = WindowsIdentity.GetCurrent();
            var principal = new WindowsPrincipal(identity);

            parentLogger.LogInfo(false, $"Chef app started with identity '{identity.Name}'");

            if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
            {
                parentLogger.LogError("Not running under full admin privileges.");

                if (Debugger.IsAttached)
                {
                    throw new Exception("You must run the deployer with full privileges.");
                }
            }

            // Use the parent logger, at least until we can build a file based one...
            this.Logger = parentLogger;
        }
コード例 #4
0
        /// <summary>
        /// Get a setting
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="name"></param>
        /// <param name="defaultValue"></param>
        /// <param name="logger"></param>
        /// <param name="isEnum"></param>
        /// <returns></returns>
        public TType GetSettingPersistent <TType>(string name, TType defaultValue, ILoggerInterface logger, bool isEnum = false)
        {
            this.privateDataPersistent = this.privateDataPersistent ?? new Dictionary <string, object>();

            TType result = defaultValue;

            if (!this.privateDataPersistent.ContainsKey(name))
            {
                return(defaultValue);
            }

            try
            {
                if (isEnum)
                {
                    result = (TType)Enum.Parse(typeof(TType), Convert.ToString(this.privateDataPersistent[name]));
                }
                else
                {
                    result = (TType)this.privateDataPersistent[name];
                }
            }
            catch (Exception e)
            {
                logger.LogInfo(false, "source value: '" + Convert.ToString(this.privateDataPersistent[name]) + "'");
                logger.LogException(e);
            }

            return(result);
        }
コード例 #5
0
        /// <summary>
        /// Add permissions to a directory if missing
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="directory"></param>
        /// <param name="logger"></param>
        public static void RemoveAccessRulesForIdentity(
            IdentityReference identity,
            string directory,
            ILoggerInterface logger)
        {
            var directoryInfo = new DirectoryInfo(directory);

            // Get a DirectorySecurity object that represents the current security settings.
            DirectorySecurity dSecurity = directoryInfo.GetAccessControl();

            bool removed = false;

            var rules = dSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));

            foreach (AuthorizationRule r in rules)
            {
                if (r.IdentityReference == identity)
                {
                    var currentRule = (FileSystemAccessRule)r;
                    dSecurity.RemoveAccessRule(currentRule);
                    removed = true;
                }
            }

            if (removed)
            {
                directoryInfo.SetAccessControl(dSecurity);
            }
            else
            {
                logger.LogInfo(true, "Could not find any rule to remove for identity {0}", identity.Value);
            }
        }
コード例 #6
0
        /// <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}'");
        }
コード例 #7
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.");
            }
        }
コード例 #8
0
        /// <summary>
        /// Ensure user not in group
        /// </summary>
        /// <param name="userPrincipalName"></param>
        /// <param name="groupname"></param>
        /// <param name="logger"></param>
        /// <param name="acp"></param>
        public static void EnsureUserNotInGroup(
            string userPrincipalName,
            string groupname,
            ILoggerInterface logger,
            AccountManagementPrincipalContext acp)
        {
            logger.LogInfo(true, $"Ensure user '{userPrincipalName}' NOT in group {groupname}");

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

            if (up == 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.");
            }

            foreach (Principal member in group.GetMembers(true))
            {
                if (member.SamAccountName == up.SamAccountName)
                {
                    group.Members.Remove(member);
                    group.Save();
                    logger.LogInfo(true, $"Removed user '{userPrincipalName}' from group '{groupname}'");
                    return;
                }
            }

            logger.LogInfo(true, $"User '{userPrincipalName}' not found in group '{groupname}'");
        }
コード例 #9
0
        /// <summary>
        /// Wait for an operation to complete
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="maxWaitMilliseconds"></param>
        /// <param name="waitMessage"></param>
        /// <param name="logger"></param>
        /// <returns></returns>
        public static bool WaitWhile(
            Func <bool> condition,
            int maxWaitMilliseconds,
            string waitMessage,
            ILoggerInterface logger)
        {
            int sleep     = 500;
            int sleepStep = 1000;

            Stopwatch sw;

            sw = Stopwatch.StartNew();
            sw.Start();

            while (condition() && sw.ElapsedMilliseconds < maxWaitMilliseconds)
            {
                logger.LogInfo(true, waitMessage);
                Thread.Sleep(sleep);
                sleep += sleepStep;
            }

            return(!condition());
        }
コード例 #10
0
        /// <summary>Adds a privilege to an account. Only works for local accounts.</summary>
        /// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
        /// <param name="privilegeName">Name of the privilege (ms-help://MS.VSCC/MS.MSDNVS.1031/security/accctrl_96lv.htm")</param>
        /// <param name="logger"></param>
        /// <returns>The windows error code returned by LsaAddAccountRights</returns>
        public static long SetRight(string accountName, string privilegeName, ILoggerInterface logger)
        {
            ValidatePrivilege(privilegeName);

            logger.LogInfo(true, $"SetRight '{privilegeName}' for account '{accountName}'");

            long winErrorCode = 0; // contains the last error
            var  rights       = new List <string>();

            // pointer an size for the SID
            IntPtr sid     = IntPtr.Zero;
            int    sidSize = 0;

            // StringBuilder and size for the domain name
            StringBuilder domainName = new StringBuilder();
            int           nameSize   = 0;

            // account-type variable for lookup
            int accountType = 0;

            // get required buffer size
            Advapi32Extern.LookupAccountName(string.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);

            // allocate buffers
            domainName = new StringBuilder(nameSize);
            sid        = Marshal.AllocHGlobal(sidSize);

            // lookup the SID for the account
            bool result = Advapi32Extern.LookupAccountName(string.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);

            // say what you're doing
            logger.LogInfo(true, "LookupAccountName result = " + result);
            logger.LogInfo(true, $"IsValidSid: {Advapi32Extern.IsValidSid(sid)}");
            logger.LogInfo(true, $"LookupAccountName succedded: [domainName='{domainName}']");

            if (!result)
            {
                winErrorCode = Advapi32Extern.GetLastError();
                logger.LogInfo(false, "LookupAccountName failed: " + winErrorCode);
                return(winErrorCode);
            }

            // initialize an empty unicode-string
            Advapi32Extern.LSA_UNICODE_STRING systemName = new Advapi32Extern.LSA_UNICODE_STRING();

            // initialize a pointer for the policy handle
            IntPtr policyHandle = IntPtr.Zero;

            // these attributes are not used, but LsaOpenPolicy wants them to exists
            Advapi32Extern.LSA_OBJECT_ATTRIBUTES objectAttributes = new Advapi32Extern.LSA_OBJECT_ATTRIBUTES();
            objectAttributes.Length                   = 0;
            objectAttributes.RootDirectory            = IntPtr.Zero;
            objectAttributes.Attributes               = 0;
            objectAttributes.SecurityDescriptor       = IntPtr.Zero;
            objectAttributes.SecurityQualityOfService = IntPtr.Zero;

            // get a policy handle
            logger.LogInfo(true, "OpenPolicy started");
            int resultPolicy = Advapi32Extern.LsaOpenPolicy(ref systemName, ref objectAttributes, Advapi32Extern.Access, out policyHandle);

            winErrorCode = Advapi32Extern.LsaNtStatusToWinError(resultPolicy);

            if (winErrorCode != 0)
            {
                logger.LogInfo(false, "OpenPolicy failed: " + winErrorCode);
                return(winErrorCode);
            }

            IntPtr userRightsPtr = IntPtr.Zero;
            int    countOfRights = 0;

            logger.LogInfo(true, "LsaEnumerateAccountRights started");
            int resultEnumerate = Advapi32Extern.LsaEnumerateAccountRights(policyHandle, sid, out userRightsPtr, out countOfRights);

            winErrorCode = Advapi32Extern.LsaNtStatusToWinError(resultEnumerate);
            if (winErrorCode != 0 && winErrorCode != 2)
            {
                logger.LogInfo(false, "LsaEnumerateAccountRights failed: " + winErrorCode);
                return(winErrorCode);
            }

            // Code 2 means no privileges
            if (winErrorCode == 0)
            {
                long ptr = userRightsPtr.ToInt64();
                Advapi32Extern.LSA_UNICODE_STRING userRight;

                for (int i = 0; i < countOfRights; i++)
                {
                    userRight = (Advapi32Extern.LSA_UNICODE_STRING)Marshal.PtrToStructure(userRightsPtr, typeof(Advapi32Extern.LSA_UNICODE_STRING));
                    string userRightStr = Marshal.PtrToStringAuto(userRight.Buffer);
                    rights.Add(userRightStr);
                    ptr += Marshal.SizeOf(userRight);
                }

                if (rights.Contains(privilegeName))
                {
                    logger.LogInfo(false, $"Account already has right '{privilegeName}'");
                    return(winErrorCode);
                }
            }

            // Now that we have the SID an the policy,
            // we can add rights to the account.

            // initialize an unicode-string for the privilege name
            Advapi32Extern.LSA_UNICODE_STRING[] userRights = new Advapi32Extern.LSA_UNICODE_STRING[1];
            userRights[0]               = new Advapi32Extern.LSA_UNICODE_STRING();
            userRights[0].Buffer        = Marshal.StringToHGlobalUni(privilegeName);
            userRights[0].Length        = (ushort)(privilegeName.Length * UnicodeEncoding.CharSize);
            userRights[0].MaximumLength = (ushort)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);

            // add the right to the account
            logger.LogInfo(true, "LsaAddAccountRights started");
            int res = Advapi32Extern.LsaAddAccountRights(policyHandle, sid, userRights, 1);

            winErrorCode = Advapi32Extern.LsaNtStatusToWinError(res);

            if (winErrorCode != 0)
            {
                logger.LogInfo(false, "LsaAddAccountRights failed: " + winErrorCode);
                return(winErrorCode);
            }

            rights.Add(privilegeName);

            Advapi32Extern.LsaClose(policyHandle);

            Advapi32Extern.FreeSid(sid);

            return(winErrorCode);
        }
コード例 #11
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);
        }
コード例 #12
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);
        }
コード例 #13
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);
        }
コード例 #14
0
        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);
        }
コード例 #15
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);
            }
        }
コード例 #16
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);
        }
コード例 #17
0
ファイル: Client.cs プロジェクト: david-garcia-garcia/iischef
        /// <summary>
        /// Downloads (And extracts) single artifacts from jobs.
        /// </summary>
        /// <param name="applicationId"></param>
        /// <param name="build"></param>
        /// <param name="artifactRegex"></param>
        /// <param name="destinationPath"></param>
        /// <param name="logger"></param>
        public void DownloadSingleArtifactFromBuild(
            string applicationId,
            Build build,
            string artifactRegex,
            string destinationPath,
            ILoggerInterface logger)
        {
            UtilsSystem.EnsureDirectoryExists(destinationPath, true);

            // Use the first job in the build...
            var job      = build.jobs.First();
            var artifact = this.FindDefaultArtifactForBuild(job, build, artifactRegex);

            var filename  = artifact.fileName;
            var extension = Path.GetExtension(filename);

            string downloadTemporaryDir =
                UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.TempDir, "_appveyor", "dld", applicationId), true);

            int artifactRetentionNum     = 5;
            int artifactAgeHoursForStale = 24;

            // Do not touch the latest artifactRetentionNum artifacts or artifacts that are not older than artifactAgeHoursForStale hours
            var staleFiles = Directory.EnumerateFiles(downloadTemporaryDir)
                             .Select((i) => new FileInfo(i))
                             .Where((i) => i.Extension.Equals(".zip", StringComparison.CurrentCultureIgnoreCase))
                             .OrderByDescending((i) => i.CreationTimeUtc)
                             .Skip(artifactRetentionNum)
                             .Where((i) => (DateTime.UtcNow - i.LastWriteTime).TotalHours > artifactAgeHoursForStale)
                             .ToList();

            foreach (var f in staleFiles)
            {
                // Make this fail proof, it's just a cleanup.
                try
                {
                    this.Logger.LogInfo(true, "Removing stale artifact cache file {0}", f.FullName);
                    f.Delete();
                }
                catch
                {
                    // ignored
                }
            }

            // Use a short hash as the temporary file name, because long paths can have issues...
            var tmpFile = UtilsSystem.CombinePaths(downloadTemporaryDir, UtilsEncryption.GetShortHash(JsonConvert.SerializeObject(build) + filename) + extension);

            if (Path.GetExtension(tmpFile)?.ToLower() != ".zip")
            {
                throw new NotImplementedException("AppVeyor artifacts should only be Zip Files.");
            }

            if (!File.Exists(tmpFile))
            {
                // Use an intermediate .tmp file just in case the files does not finish to download,
                // if it exists, clear it.
                string tmpFileDownload = tmpFile + ".tmp";
                if (File.Exists(tmpFileDownload))
                {
                    UtilsSystem.RetryWhile(() => File.Delete(tmpFileDownload), (e) => true, 4000, this.Logger);
                }

                var url = $"/api/buildjobs/{job.jobId}/artifacts/{filename}";
                logger.LogInfo(true, "Downloading artifact from: '{0}' to '{1}'", url, tmpFileDownload);
                this.ExecuteApiCallToFile(url, tmpFileDownload);

                // Rename to the final cached artifact file
                logger.LogInfo(true, "Download succesful, moving to '{0}'", tmpFile);
                UtilsSystem.RetryWhile(() => File.Move(tmpFileDownload, tmpFile), (e) => true, 4000, this.Logger);
            }
            else
            {
                logger.LogInfo(true, "Skipping artifact download, already in local cache: {0}", tmpFile);
            }

            logger.LogInfo(true, "Unzipping {1} file to '{0}'...", destinationPath, UtilsSystem.BytesToString(new FileInfo(tmpFile).Length));

            ZipFile.ExtractToDirectory(tmpFile, destinationPath);

            logger.LogInfo(true, "Unzipping finished.");
        }
コード例 #18
0
        /// <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);
            }
        }