Exemplo n.º 1
0
        public void undeploy(bool isUninstall = false)
        {
            // Not that we want to accidentally delete contents and files
            // for an application...
            if (!isUninstall)
            {
                return;
            }

            var diskSettings = this.DeployerSettings.castTo <DiskServiceSettings>();
            var mounts       = this.Deployment.GetSettingCollection <DiskStore>($"service.{diskSettings.id}");

            foreach (var m in mounts.Values)
            {
                // Most of the time this directory will not exist, as the base storage deployer will already have
                // deleted the application folder. But for "local" installed applications, this removes
                // the symlinks.
                if (!string.IsNullOrWhiteSpace(m.junctionRealPath) && Directory.Exists(m.junctionRealPath))
                {
                    UtilsJunction.RemoveJunction(m.junctionRealPath);
                }

                if (Directory.Exists(m.path))
                {
                    UtilsSystem.DeleteDirectory(m.path, this.Logger);
                }
            }

            var baseStoragePath = this.GetStoragePath(diskSettings);

            if (!string.IsNullOrWhiteSpace(baseStoragePath) && Directory.Exists(baseStoragePath))
            {
                UtilsSystem.DeleteDirectory(baseStoragePath, this.Logger);
            }
        }
        public override void beforeDone()
        {
            // We also have a canonical access to the deployed app through a symlink
            string basePath      = this.Deployment.GetSetting("appstorage.base", (string)null, this.Logger);
            string canonicalPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, "_" + this.Deployment.installedApplicationSettings.GetId());

            UtilsJunction.EnsureLink(canonicalPath, basePath, this.Logger, false, true);
            this.Deployment.SetSetting("appstorage.canonical", canonicalPath);
        }
        public void TestSymlinksAreNotRemoved()
        {
            var logger = new TestLogsLogger(this, nameof(this.TestSymlinksAreNotRemoved));

            string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid());

            var pathWebsite            = Path.Combine(testPath, "website");
            var pathContentsPersistent = Path.Combine(testPath, "content_store_persistent");

            Directory.CreateDirectory(pathWebsite);
            Directory.CreateDirectory(pathContentsPersistent);
            Directory.CreateDirectory(Path.Combine(pathContentsPersistent, "empty_directory"));

            string linkedDir  = Path.Combine(pathWebsite, "contents");
            string linkedDir2 = Path.Combine(pathWebsite, "contents2", "contents");

            UtilsSystem.EnsureDirectoryExists(linkedDir);
            UtilsSystem.EnsureDirectoryExists(linkedDir2);

            UtilsJunction.EnsureLink(linkedDir, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);
            UtilsJunction.EnsureLink(linkedDir2, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);

            Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir));
            Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir2));

            string fileInContentsPeristent = Path.Combine(pathContentsPersistent, "test.txt");
            string fileInSymlinkDir        = Path.Combine(linkedDir, "test.txt");

            Assert.Equal(fileInContentsPeristent, UtilsJunction.ResolvePath(fileInSymlinkDir));

            string fileInContentsPeristent2 = Path.Combine(pathContentsPersistent, "test2.txt");
            string fileInSymlinkDir2        = Path.Combine(linkedDir2, "test2.txt");

            Assert.Equal(fileInContentsPeristent2, UtilsJunction.ResolvePath(fileInSymlinkDir2));

            File.WriteAllText(fileInSymlinkDir, "testfile");
            File.WriteAllText(fileInSymlinkDir2, "testfile");

            Assert.True(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}");
            Assert.True(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}");
            Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}");
            Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}");

            // If we delete the directory containing the symlink, the file still exists
            UtilsSystem.DeleteDirectory(pathWebsite, logger);
            Assert.False(Directory.Exists(pathWebsite), "Directory exists " + pathWebsite);

            Assert.False(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}");
            Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}");
            Assert.False(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}");
            Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}");

            Directory.Delete(testPath, true);
        }
        public void TestResolveJunctionPath()
        {
            var logger = new TestLogsLogger(this, nameof(this.TestResolveJunctionPath));

            string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid());

            // Probar resolución de nivel 1

            string test1OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test1"), true);
            string test1LinkPath     = Path.Combine(testPath, "test1_link");
            string test1JunctionPath = Path.Combine(testPath, "test1_junction");

            UtilsJunction.EnsureLink(test1LinkPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);
            UtilsJunction.EnsureLink(test1JunctionPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);

            Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1LinkPath));
            Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1JunctionPath));

            // Probar resolución de subdirectorio existente y no existente

            string test2OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test2"), true);
            string test2LinkPath     = Path.Combine(testPath, "test2_link");
            string test2JunctionPath = Path.Combine(testPath, "test2_junction");

            UtilsJunction.EnsureLink(test2LinkPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);
            UtilsJunction.EnsureLink(test2JunctionPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);

            string test2LinkSubDir     = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "sub1", "sub2"), true);
            string test2JunctionSubDir = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2JunctionPath, "sub3", "sub4"), true);

            Assert.Equal(Path.Combine(test2OriginalPath, "sub1", "sub2"), UtilsJunction.ResolvePath(test2LinkSubDir));
            Assert.Equal(Path.Combine(test2OriginalPath, "sub3", "sub4"), UtilsJunction.ResolvePath(test2JunctionSubDir));

            // Ahora subdirectorios que no existen
            Assert.Equal(Path.Combine(test2OriginalPath, "sub4", "sub5"), UtilsJunction.ResolvePath(Path.Combine(test2LinkPath, "sub4", "sub5")));
            Assert.Equal(Path.Combine(test2OriginalPath, "sub6", "sub7"), UtilsJunction.ResolvePath(Path.Combine(test2JunctionPath, "sub6", "sub7")));

            // Ahora una cadena de enlaces dentro de otro enlace...
            string test3LinkSubDir = Path.Combine(test2LinkPath, "sub8");

            UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "test3"), true);
            UtilsJunction.EnsureLink(test3LinkSubDir, Path.Combine(test2LinkPath, "test3"), logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);

            Assert.Equal(Path.Combine(test2OriginalPath, "test3"), UtilsJunction.ResolvePath(test3LinkSubDir));

            UtilsSystem.DeleteDirectory(testPath, logger, 2);

            // Non existent and malformed network uri get reconstructed as-is
            string testNetworkUri = "\\\\147.83.73.25\\a\\b\\c\\\\d";

            Assert.Equal(testNetworkUri, UtilsJunction.ResolvePath(testNetworkUri));
        }
Exemplo n.º 5
0
        public void deploy()
        {
            var diskSettings = this.DeployerSettings.castTo <DiskServiceSettings>();

            var baseStoragePath = this.GetStoragePath(diskSettings);

            if (diskSettings.mounts == null || !diskSettings.mounts.Any())
            {
                throw new Exception("You must specify at least a mount for a disk service.");
            }

            // Each one of these is to be mounted as a symlink/junction
            foreach (var mount in diskSettings.mounts)
            {
                if (string.IsNullOrWhiteSpace(diskSettings.id))
                {
                    throw new Exception("Disk settings must have an id");
                }

                if (string.IsNullOrWhiteSpace(mount.Value.id))
                {
                    throw new Exception("All mounts in disk configuration must have an id");
                }

                // Expand the local path..
                var mountDestination = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(baseStoragePath, mount.Value.path), true);
                this.Logger.LogInfo(true, "Mounting disk '{0}' at {1}", mount.Value.id, mountDestination);

                var settingkey = $"services.{diskSettings.id}.mount.{mount.Value.id}.path";

                // We might sometimes need to force a specific path in an environment...
                if (this.Deployment.installedApplicationSettings.GetRuntimeSettingsOverrides().ContainsKey(settingkey))
                {
                    string newMountDestination = this.Deployment.installedApplicationSettings.GetRuntimeSettingsOverrides()[settingkey];
                    if (Directory.Exists(newMountDestination))
                    {
                        this.Logger.LogInfo(false, "Default mount for '{0}' overriden with '{1}' from a default value of '{2}'.", settingkey, newMountDestination, mountDestination);
                        mountDestination = newMountDestination;
                    }
                    else
                    {
                        this.Logger.LogInfo(false, "Tried to override mount path ({0}) with a non-existent directory: '{1}'", settingkey, newMountDestination);
                    }
                }

                // Ensure proper permissions
                this.Logger.LogInfo(true, "Ensure mount has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), mountDestination, FileSystemRights.Modify, this.GlobalSettings.directoryPrincipal);

                string mountPath = null;

                if (!string.IsNullOrWhiteSpace(mount.Value.mountpath))
                {
                    mountPath = UtilsSystem.CombinePaths(this.Deployment.appPath, mount.Value.mountpath);
                    UtilsJunction.EnsureLink(mountPath, mountDestination, this.Logger, mount.Value.persist_on_deploy);
                }

                // Wether we requested or not a mountpath, make a link in the runtime folder to all disk stores
                var localMountPath = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "disk", mount.Value.id);
                this.Logger.LogInfo(true, "Linking disk at local path {0}", localMountPath);
                UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.Deployment.runtimePath, "disk"), true);
                UtilsJunction.EnsureLink(localMountPath, mountDestination, this.Logger, mount.Value.persist_on_deploy);

                // Make only the local mount path visible to the application
                this.Deployment.SetRuntimeSetting(settingkey, localMountPath);

                this.Deployment.SetSettingCollection($"service.{diskSettings.id}", settingkey, new DiskStore()
                {
                    path             = localMountPath,
                    junction         = mountPath,
                    originalPath     = mountDestination,
                    junctionRealPath = UtilsJunction.ResolvePath(mountPath)
                });
            }
        }
        /// <inheritdoc cref="DeployerBase"/>
        public void deploy()
        {
            var settings = this.GetSettings();

            this.Deployment.windowsUsername = "******" + this.Deployment.installedApplicationSettings.GetId();

            if (this.Deployment.GetPreviousDeployment() != null && this.Deployment.GetPreviousDeployment().windowsUsername != this.Deployment.windowsUsername)
            {
                this.Logger.LogWarning(
                    false,
                    "Windows account username has changed from '{0}' to '{1}', removal of account and granted permissions must be performed manually.",
                    this.Deployment.GetPreviousDeployment()?.windowsUsername,
                    this.Deployment.windowsUsername);
            }

            UtilsWindowsAccounts.EnsureUserExists(this.Deployment.WindowsUsernameFqdn(), this.Deployment.GetWindowsPassword(), this.Deployment.installedApplicationSettings.GetId(), this.Logger, this.GlobalSettings.directoryPrincipal);

            // Legacy behaviour, if no userGroups defined, create a chef_users groups and add the users
            // to it
            if (!(this.GlobalSettings.userGroups ?? new List <string>()).Any())
            {
                UtilsWindowsAccounts.EnsureGroupExists(LEGACY_CHEF_USERS_GROUPNAME, this.GlobalSettings.directoryPrincipal);
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), LEGACY_CHEF_USERS_GROUPNAME, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to the user groups
            foreach (var groupIdentifier in this.GlobalSettings.userGroups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to any user groups defined at the application level
            foreach (var groupIdentifier in settings.user_groups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add any privileges if requested
            foreach (var privilegeName in settings.privileges ?? new List <string>())
            {
                UtilsWindowsAccounts.SetRight(this.Deployment.WindowsUsernameFqdn(), privilegeName, this.Logger);
            }

            // Getting security right at the OS level here is a little bit picky...
            // in order to have REALPATH to work in PHP we need to be able to read all directories
            // in a path i.e. D:\webs\chef\appnumber1\
            // What we will do is disconnect the USERS account here...
            string basePath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, this.Deployment.getShortId());

            UtilsSystem.EnsureDirectoryExists(basePath, true);

            UtilsWindowsAccounts.DisablePermissionInheritance(basePath);
            UtilsWindowsAccounts.RemoveAccessRulesForIdentity(new SecurityIdentifier(UtilsWindowsAccounts.WELL_KNOWN_SID_USERS), basePath, this.Logger);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), basePath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);

            // Store this in the application storage location.
            this.Deployment.runtimePath = UtilsSystem.CombinePaths(basePath, "runtime");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.runtimePath, true);

            this.Deployment.runtimePathWritable = UtilsSystem.CombinePaths(basePath, "runtime_writable");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.runtimePathWritable, true);

            // Due to compatibility reasons with environments such as PHP (that do not play well with network file URIs such as shared folders)
            // by default these two directories are symlinked to a local path if they are network paths.

            // Temp dir
            string localTempPath  = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "temp");
            string remoteTempPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultTempStorage().path, this.Deployment.installedApplicationSettings.GetId());

            UtilsSystem.EnsureDirectoryExists(remoteTempPath, true);
            UtilsJunction.EnsureLink(localTempPath, remoteTempPath, this.Logger, false);
            this.Deployment.tempPath = localTempPath;

            // Temp dir sys
            this.Deployment.tempPathSys = UtilsSystem.CombinePaths(this.Deployment.runtimePathWritable, "_tmp");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.tempPathSys, true);

            // Log dir
            string localLogPath  = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "log");
            string remoteLogPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultLogStorage().path, this.Deployment.installedApplicationSettings.GetId());

            UtilsSystem.EnsureDirectoryExists(remoteLogPath, true);
            UtilsJunction.EnsureLink(localLogPath, remoteLogPath, this.Logger, false);
            this.Deployment.logPath = localLogPath;

            this.Deployment.SetSetting("appstorage.base", basePath);
            this.Deployment.SetSetting("appstorage.temp", this.Deployment.tempPath);
            this.Deployment.SetSetting("appstorage.log", this.Deployment.logPath);

            this.Deployment.SetSetting("appstorage.remote_temp", remoteTempPath);
            this.Deployment.SetSetting("appstorage.remote_log", remoteLogPath);

            // We use this flag to detect transient storage
            // that must be removed when the deployer is "undeployed".
            AppBaseStorageType appBaseStorageType = AppBaseStorageType.Original;

            // TODO: Make this configurable through the chef.yml settings file.
            string ignoreOnDeployPattern = "^\\.git\\\\|^chef\\\\|^\\.vs\\\\";

            switch (this.Deployment.installedApplicationSettings.GetApplicationMountStrategy())
            {
            case ApplicationMountStrategy.Copy:
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");

                // TODO: We should consider the ability to symlink the code here, or to point/mount directly
                // to the original source path. This would probably require delegating this step to the artifact downloader
                // (artifact.getDownloader()) or having the downloader tell us how to deal with this (symlinks, direct, whatever)
                this.Logger.LogInfo(true, "Copying artifact files...");
                UtilsSystem.CopyFilesRecursivelyFast(this.Deployment.artifact.localPath, this.Deployment.appPath, false, ignoreOnDeployPattern, this.Logger);
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUserPrincipalName(), this.Deployment.appPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.artifact.DeleteIfRemote(this.Logger);
                appBaseStorageType = AppBaseStorageType.Transient;
                break;

            case ApplicationMountStrategy.Move:
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");

                // TODO: We should consider the ability to symlink the code here, or to point/mount directly
                // to the original source path. This would probably require delegating this step to the artifact downloader
                // (artifact.getDownloader()) or having the downloader tell us how to deal with this (symlinks, direct, whatever)
                this.Logger.LogInfo(true, "Moving artifact files...");
                UtilsSystem.MoveDirectory(this.Deployment.artifact.localPath, this.Deployment.appPath, this.Logger, ignoreOnDeployPattern);

                // We had issues in appveyor where _webs location is in C drive and thus not giving
                // permissions here would make tests fail.
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.appPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.artifact.DeleteIfRemote(this.Logger);
                appBaseStorageType = AppBaseStorageType.Transient;
                break;

            case ApplicationMountStrategy.Link:
                this.Logger.LogInfo(true, "Linking artifact files...");
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");
                UtilsJunction.EnsureLink(this.Deployment.appPath, this.Deployment.artifact.localPath, this.Logger, false);
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.artifact.localPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                appBaseStorageType = AppBaseStorageType.Symlink;
                break;

            case ApplicationMountStrategy.Original:
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.artifact.localPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.appPath = UtilsSystem.CombinePaths(this.Deployment.artifact.localPath);
                appBaseStorageType      = AppBaseStorageType.Original;
                break;

            default:
                throw new NotImplementedException("The requested mount strategy for the application is not available: " + this.Deployment.installedApplicationSettings.GetApplicationMountStrategy());
            }

            this.Deployment.SetRuntimeSetting("deployment.appPath", this.Deployment.appPath);
            this.Deployment.SetRuntimeSetting("deployment.logPath", this.Deployment.logPath);
            this.Deployment.SetRuntimeSetting("deployment.tempPath", this.Deployment.tempPath);

            this.Deployment.SetSetting("appstorage.appBaseStorageType", appBaseStorageType);

            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), remoteTempPath, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), remoteLogPath, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.runtimePath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.runtimePathWritable, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);

            this.DeployFonts(settings);
        }
        public void undeploy(bool isUninstall = false)
        {
            if (this.Deployment == null)
            {
                return;
            }

            var    strategy = this.Deployment.installedApplicationSettings.GetApplicationMountStrategy();
            string basePath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, this.Deployment.getShortId());

            switch (strategy)
            {
            case ApplicationMountStrategy.Copy:
            case ApplicationMountStrategy.Move:
                UtilsSystem.DeleteDirectoryAndCloseProcesses(basePath, this.Logger, UtilsSystem.DefaultProcessWhitelist, 100);
                break;

            case ApplicationMountStrategy.Link:
                UtilsJunction.RemoveJunction(this.Deployment.appPath);
                UtilsSystem.DeleteDirectoryAndCloseProcesses(basePath, this.Logger, UtilsSystem.DefaultProcessWhitelist, 100);
                break;

            case ApplicationMountStrategy.Original:
                // Do nothing!
                break;

            default:
                throw new Exception("Option not supported.");
            }

            if (!isUninstall)
            {
                return;
            }

            var canonicalPath = this.Deployment.GetSetting <string>("appstorage.canonical", null, this.Logger);

            if (Directory.Exists(canonicalPath) && UtilsJunction.IsJunctionOrSymlink(canonicalPath))
            {
                Directory.Delete(canonicalPath);
            }

            // Usually the IIS site has been closed a few fractions of a second
            // before this is called, so the folders have probably not yet
            // been released, waitPauseMs at least 10 seconds.
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.temp", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.log", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.remote_temp", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.remote_log", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);

            var settings = this.GetSettings();

            var groups = this.GlobalSettings.userGroups ?? new List <string>();

            // add legacy group chef_users
            groups.Add(LEGACY_CHEF_USERS_GROUPNAME);

            // Remove user from all groups before deleting
            foreach (var groupIdentifier in groups)
            {
                UtilsWindowsAccounts.EnsureUserNotInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to any user groups defined at the application level
            foreach (var groupIdentifier in settings.user_groups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserNotInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            UtilsWindowsAccounts.DeleteUser(this.Deployment.WindowsUsernameFqdn(), this.GlobalSettings.directoryPrincipal);
        }