public async Task FullUninstall()
            {
                var currentRelease = getReleases().MaxBy(x => x.Name.ToVersion()).FirstOrDefault();

                this.Log().Info("Starting full uninstall");
                if (currentRelease.Exists)
                {
                    var version = currentRelease.Name.ToVersion();

                    try {
                        var squirrelAwareApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName);

                        if (squirrelAwareApps.Count > 0)
                        {
                            await squirrelAwareApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, String.Format("--squirrel-uninstall {0}", version)), 1);
                        }
                        else
                        {
                            var allApps = currentRelease.EnumerateFiles()
                                          .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                          .ToList();

                            allApps.ForEach(x => RemoveShortcutsForExecutable(x.Name, ShortcutLocation.StartMenu | ShortcutLocation.Desktop));
                        }
                    } catch (Exception ex) {
                        this.Log().WarnException("Failed to run pre-uninstall hooks, uninstalling anyways", ex);
                    }
                }

                await this.ErrorIfThrows(() => Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory),
                                         "Failed to delete app directory: " + rootAppDirectory);
            }
            async Task invokePostInstall(SemanticVersion currentVersion, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
            {
                var targetDir = getDirectoryForRelease(currentVersion);
                var args      = isInitialInstall ?
                                String.Format("--squirrel-install {0}", currentVersion) :
                                String.Format("--squirrel-updated {0}", currentVersion);

                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(async exe => {
                        using (var cts = new CancellationTokenSource()) {
                            cts.CancelAfter(45 * 1000);
                            try {
                                await Utility.InvokeProcessAsync(exe, args, cts.Token);
                            } catch (Exception ex) {
                                this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
                            }
                        }
                    }, 1 /* at a time */);
                }

                // If this is the first run, we run the apps with first-run and
                // *don't* wait for them, since they're probably the main EXE
                if (squirrelApps.Count == 0)
                {
                    this.Log().Warn("No apps are marked as Squirrel-aware! Going to run them all");

                    squirrelApps = targetDir.EnumerateFiles()
                                   .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                   .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase))
                                   .Select(x => x.FullName)
                                   .ToList();

                    // Create shortcuts for apps automatically if they didn't
                    // create any Squirrel-aware apps
                    squirrelApps.ForEach(x => CreateShortcutsForExecutable(Path.GetFileName(x), ShortcutLocations.Defaults, isInitialInstall == false, null, null));
                }

                if (!isInitialInstall || silentInstall)
                {
                    return;
                }

                var firstRunParam = isInitialInstall ? "--squirrel-firstrun" : "";

                squirrelApps
                .Select(exe => new ProcessStartInfo(exe, firstRunParam)
                {
                    WorkingDirectory = Path.GetDirectoryName(exe)
                })
                .ForEach(info => {
                    var p = Process.Start(info);
                    this.Log().Info("ran {0}, pid {1}", info.FileName, p.Id);
                });
            }
            public async Task FullUninstall()
            {
                var currentRelease = getReleases().MaxBy(x => x.Name.ToVersion()).FirstOrDefault();

                this.Log().Info("Starting full uninstall");
                if (currentRelease.Exists)
                {
                    var version = currentRelease.Name.ToVersion();

                    try {
                        var squirrelAwareApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName);

                        if (isAppFolderDead(currentRelease.FullName))
                        {
                            throw new Exception("App folder is dead, but we're trying to uninstall it?");
                        }

                        if (squirrelAwareApps.Count > 0)
                        {
                            await squirrelAwareApps.ForEachAsync(async exe => {
                                using (var cts = new CancellationTokenSource()) {
                                    cts.CancelAfter(10 * 1000);

                                    try {
                                        await Utility.InvokeProcessAsync(exe, String.Format("--squirrel-uninstall {0}", version), cts.Token);
                                    } catch (Exception ex) {
                                        this.Log().ErrorException("Failed to run cleanup hook, continuing: " + exe, ex);
                                    }
                                }
                            }, 1 /*at a time*/);
                        }
                        else
                        {
                            var allApps = currentRelease.EnumerateFiles()
                                          .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                          .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase))
                                          .ToList();

                            allApps.ForEach(x => RemoveShortcutsForExecutable(x.Name, ShortcutLocation.StartMenu | ShortcutLocation.Desktop));
                        }
                    } catch (Exception ex) {
                        this.Log().WarnException("Failed to run pre-uninstall hooks, uninstalling anyways", ex);
                    }
                }

                fixPinnedExecutables(new Version(255, 255, 255, 255));

                await this.ErrorIfThrows(() => Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory),
                                         "Failed to delete app directory: " + rootAppDirectory);

                // NB: We drop this file here so that --checkInstall will ignore
                // this folder - if we don't do this, users who "accidentally" run as
                // administrator will find the app reinstalling itself on every
                // reboot
                File.WriteAllText(Path.Combine(rootAppDirectory, ".dead"), " ");
            }
            async Task invokePostInstall(Version currentVersion, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
            {
                var targetDir = getDirectoryForRelease(currentVersion);
                var args      = isInitialInstall ?
                                String.Format("--squirrel-install {0}", currentVersion) :
                                String.Format("--squirrel-updated {0}", currentVersion);

                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */);
                }

                // If this is the first run, we run the apps with first-run and
                // *don't* wait for them, since they're probably the main EXE
                if (squirrelApps.Count == 0)
                {
                    this.Log().Warn("No apps are marked as Squirrel-aware! Going to run them all");

                    squirrelApps = targetDir.EnumerateFiles()
                                   .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                   .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase))
                                   .Select(x => x.FullName)
                                   .ToList();

                    // Create shortcuts for apps automatically if they didn't
                    // create any Squirrel-aware apps
                    squirrelApps.ForEach(x => CreateShortcutsForExecutable(Path.GetFileName(x), ShortcutLocation.Desktop | ShortcutLocation.StartMenu, isInitialInstall == false));
                }

                if (!isInitialInstall || silentInstall)
                {
                    return;
                }

                var firstRunParam = isInitialInstall ? "--squirrel-firstrun" : "";

                squirrelApps
                .Select(exe => new ProcessStartInfo(exe, firstRunParam)
                {
                    WorkingDirectory = Path.GetDirectoryName(exe)
                })
                .ForEach(info => Process.Start(info));
            }
            public async Task FullUninstall()
            {
                var currentRelease = getReleases().MaxBy(x => x.Name.ToVersion()).FirstOrDefault();

                this.Log().Info("Starting full uninstall");
                if (currentRelease.Exists)
                {
                    var version = currentRelease.Name.ToVersion();

                    try {
                        var squirrelAwareApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName);

                        if (isAppFolderDead(currentRelease.FullName))
                        {
                            throw new Exception("App folder is dead, but we're trying to uninstall it?");
                        }

                        if (squirrelAwareApps.Count > 0)
                        {
                            await squirrelAwareApps.ForEachAsync(async exe => {
                                var cts = new CancellationTokenSource();
                                cts.CancelAfter(10 * 1000);

                                try {
                                    await Utility.InvokeProcessAsync(exe, String.Format("--squirrel-uninstall {0}", version), cts.Token);
                                } catch (Exception ex) {
                                    this.Log().ErrorException("Failed to run cleanup hook, continuing: " + exe, ex);
                                }
                            }, 1 /*at a time*/);
                        }
                        else
                        {
                            var allApps = currentRelease.EnumerateFiles()
                                          .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                          .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase))
                                          .ToList();

                            allApps.ForEach(x => RemoveShortcutsForExecutable(x.Name, ShortcutLocation.StartMenu | ShortcutLocation.Desktop));
                        }
                    } catch (Exception ex) {
                        this.Log().WarnException("Failed to run pre-uninstall hooks, uninstalling anyways", ex);
                    }
                }

                await this.ErrorIfThrows(() => Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory),
                                         "Failed to delete app directory: " + rootAppDirectory);
            }
예제 #6
0
            async Task invokePostInstall(SemanticVersion currentVersion, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
            {
                var targetDir = getDirectoryForRelease(currentVersion);
                var args      = isInitialInstall ?
                                String.Format("--squirrel-install {0}", currentVersion) :
                                String.Format("--squirrel-updated {0}", currentVersion);

                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(async exe => {
                        using (var cts = new CancellationTokenSource()) {
                            cts.CancelAfter(15 * 1000);

                            try {
                                await Utility.InvokeProcessAsync(exe, args, cts.Token);
                            } catch (Exception ex) {
                                this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
                            }
                        }
                    }, 1 /* at a time */);
                }

                squirrelApps.ForEach(x => CreateShortcutsForExecutable(Path.GetFileName(x), ShortcutLocation.StartMenu, isInitialInstall == false, null, null));

                if (!isInitialInstall || silentInstall)
                {
                    return;
                }

                squirrelApps
                .Select(exe => new ProcessStartInfo(exe)
                {
                    WorkingDirectory = Path.GetDirectoryName(exe), Arguments = "install-to-path"
                })
                .ForEach(info => Process.Start(info).WaitForExit());

                var fuseExe  = squirrelApps.First();
                var fuseShim = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(fuseExe)), "Bin", "Fuse.exe");

                Process.Start(fuseShim);
            }
예제 #7
0
            public async Task FullUninstall()
            {
                var currentRelease = getReleases().MaxBy(x => x.Name.ToVersion()).FirstOrDefault();

                this.Log().Info("Starting full uninstall");
                if (currentRelease.Exists)
                {
                    var version = currentRelease.Name.ToVersion();

                    try {
                        await SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName)
                        .ForEachAsync(exe => Utility.InvokeProcessAsync(exe, String.Format("--squirrel-uninstall {0}", version)), 1);
                    } catch (Exception ex) {
                        this.Log().WarnException("Failed to run pre-uninstall hooks, uninstalling anyways", ex);
                    }
                }

                await this.ErrorIfThrows(() => Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory),
                                         "Failed to delete app directory: " + rootAppDirectory);
            }
            async Task invokePostInstall(SemanticVersion currentVersion, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
            {
                var targetDir = getDirectoryForRelease(currentVersion);
                var args      = isInitialInstall ?
                                String.Format("--squirrel-install {0}", currentVersion) :
                                String.Format("--squirrel-updated {0}", currentVersion);

                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(async exe => {
                        using (var cts = new CancellationTokenSource()) {
                            cts.CancelAfter(15 * 1000);

                            try {
                                await Utility.InvokeProcessAsync(exe, args, cts.Token);
                            } catch (Exception ex) {
                                this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
                            }
                        }
                    }, 1 /* at a time */);
                }

                if (!isInitialInstall || silentInstall)
                {
                    return;
                }

                var firstRunParam = isInitialInstall ? "--squirrel-firstrun" : "";

                squirrelApps
                .Select(exe => new ProcessStartInfo(exe, firstRunParam)
                {
                    WorkingDirectory = Path.GetDirectoryName(exe)
                })
                .ForEach(info => Process.Start(info));
            }
예제 #9
0
            async Task invokePostInstall(Version currentVersion, bool isInitialInstall, bool firstRunOnly)
            {
                var targetDir = getDirectoryForRelease(currentVersion);
                var args      = isInitialInstall ?
                                String.Format("--squirrel-install {0}", currentVersion) :
                                String.Format("--squirrel-updated {0}", currentVersion);

                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */);
                }

                if (!isInitialInstall)
                {
                    return;
                }

                // If this is the first run, we run the apps with first-run and
                // *don't* wait for them, since they're probably the main EXE
                if (squirrelApps.Count == 0)
                {
                    this.Log().Warn("No apps are marked as Squirrel-aware! Going to run them all");

                    squirrelApps = targetDir.EnumerateFiles()
                                   .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                   .Select(x => x.FullName)
                                   .ToList();
                }

                squirrelApps.ForEach(exe => Process.Start(exe, "--squirrel-firstrun"));
            }
            // NB: Once we uninstall the old version of the app, we try to schedule
            // it to be deleted at next reboot. Unfortunately, depending on whether
            // the user has admin permissions, this can fail. So as a failsafe,
            // before we try to apply any update, we assume previous versions in the
            // directory are "dead" (i.e. already uninstalled, but not deleted), and
            // we blow them away. This is to make sure that we don't attempt to run
            // an uninstaller on an already-uninstalled version.
            async Task cleanDeadVersions(SemanticVersion originalVersion, SemanticVersion currentVersion, bool forceUninstall = false)
            {
                if (currentVersion == null)
                {
                    return;
                }

                var di = new DirectoryInfo(rootAppDirectory);

                if (!di.Exists)
                {
                    return;
                }

                this.Log().Info("cleanDeadVersions: for version {0}", currentVersion);

                string originalVersionFolder = null;

                if (originalVersion != null)
                {
                    originalVersionFolder = getDirectoryForRelease(originalVersion).Name;
                    this.Log().Info("cleanDeadVersions: exclude folder {0}", originalVersionFolder);
                }

                string currentVersionFolder = null;

                if (currentVersion != null)
                {
                    currentVersionFolder = getDirectoryForRelease(currentVersion).Name;
                    this.Log().Info("cleanDeadVersions: exclude folder {0}", currentVersionFolder);
                }

                // NB: If we try to access a directory that has already been
                // scheduled for deletion by MoveFileEx it throws what seems like
                // NT's only error code, ERROR_ACCESS_DENIED. Squelch errors that
                // come from here.
                var toCleanup = di.GetDirectories()
                                .Where(x => x.Name.ToLowerInvariant().Contains("app-"))
                                .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder)
                                .Where(x => !isAppFolderDead(x.FullName));

                if (forceUninstall == false)
                {
                    await toCleanup.ForEachAsync(async x => {
                        var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(x.FullName);
                        var args         = String.Format("--squirrel-obsolete {0}", x.Name.Replace("app-", ""));

                        if (squirrelApps.Count > 0)
                        {
                            // For each app, run the install command in-order and wait
                            await squirrelApps.ForEachAsync(async exe => {
                                using (var cts = new CancellationTokenSource()) {
                                    cts.CancelAfter(10 * 1000);

                                    try {
                                        await Utility.InvokeProcessAsync(exe, args, cts.Token);
                                    } catch (Exception ex) {
                                        this.Log().ErrorException("Coudln't run Squirrel hook, continuing: " + exe, ex);
                                    }
                                }
                            }, 1 /* at a time */);
                        }
                    });
                }

                // Include dead folders in folders to :fire:
                toCleanup = di.GetDirectories()
                            .Where(x => x.Name.ToLowerInvariant().Contains("app-"))
                            .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder);

                // Get the current process list in an attempt to not burn
                // directories which have running processes
                var runningProcesses = UnsafeUtility.EnumerateProcesses();

                // Finally, clean up the app-X.Y.Z directories
                await toCleanup.ForEachAsync(async x => {
                    try {
                        if (runningProcesses.All(p => !p.Item1.StartsWith(x.FullName, StringComparison.OrdinalIgnoreCase)))
                        {
                            await Utility.DeleteDirectoryOrJustGiveUp(x.FullName);
                        }

                        if (Directory.Exists(x.FullName))
                        {
                            // NB: If we cannot clean up a directory, we need to make
                            // sure that anyone finding it later won't attempt to run
                            // Squirrel events on it. We'll mark it with a .dead file
                            markAppFolderAsDead(x.FullName);
                        }
                    } catch (UnauthorizedAccessException ex) {
                        this.Log().WarnException("Couldn't delete directory: " + x.FullName, ex);

                        // NB: Same deal as above
                        markAppFolderAsDead(x.FullName);
                    }
                });

                // Clean up the packages directory too
                var releasesFile = Utility.LocalReleaseFileForAppDir(rootAppDirectory);
                var entries      = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesFile, Encoding.UTF8));
                var pkgDir       = Utility.PackageDirectoryForAppDir(rootAppDirectory);
                var releaseEntry = default(ReleaseEntry);

                foreach (var entry in entries)
                {
                    if (entry.Version == currentVersion)
                    {
                        releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename));
                        continue;
                    }

                    File.Delete(Path.Combine(pkgDir, entry.Filename));
                }

                ReleaseEntry.WriteReleaseFile(new[] { releaseEntry }, releasesFile);
            }
예제 #11
0
            async Task invokePostInstall(SemanticVersion currentVersion, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
            {
                var targetDir = getDirectoryForRelease(currentVersion);

                var args = isInitialInstall ?
                           String.Format("--squirrel-install {0}", currentVersion) :
                           String.Format("--squirrel-updated {0}", currentVersion);

                var incomingArgs    = Environment.GetCommandLineArgs();
                var pathKeyArgIndex = Array.FindIndex(incomingArgs, arg => String.Equals(arg, "--installer-path"));

                if (pathKeyArgIndex != -1 && incomingArgs.Length > pathKeyArgIndex + 1)
                {
                    var installerPath = incomingArgs[pathKeyArgIndex + 1];
                    args += String.Format(" --squirrel-installer-path \"{0}\"", installerPath);
                }
                this.Log().Info("Incoming args to UpdateManager process: {0}", String.Join(",", incomingArgs));
                this.Log().Info("Running app with args: {0}", args);
                var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName);

                this.Log().Info("Squirrel Enabled Apps: [{0}]", String.Join(",", squirrelApps));

                // For each app, run the install command in-order and wait
                if (!firstRunOnly)
                {
                    await squirrelApps.ForEachAsync(async exe => {
                        using (var cts = new CancellationTokenSource()) {
                            cts.CancelAfter(15 * 1000);

                            try {
                                await Utility.InvokeProcessAsync(exe, args, cts.Token);
                            } catch (Exception ex) {
                                this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
                            }
                        }
                    }, 1 /* at a time */);
                }

                // If this is the first run, we run the apps with first-run and
                // *don't* wait for them, since they're probably the main EXE
                if (squirrelApps.Count == 0)
                {
                    this.Log().Warn("No apps are marked as Squirrel-aware! Going to run them all");

                    squirrelApps = targetDir.EnumerateFiles()
                                   .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                   .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase))
                                   .Select(x => x.FullName)
                                   .ToList();

                    // Create shortcuts for apps automatically if they didn't
                    // create any Squirrel-aware apps
                    squirrelApps.ForEach(x => CreateShortcutsForExecutable(Path.GetFileName(x), ShortcutLocation.Desktop | ShortcutLocation.StartMenu, isInitialInstall == false, null, null));
                }

                if (!isInitialInstall || silentInstall)
                {
                    return;
                }

                var firstRunParam = isInitialInstall ? "--squirrel-firstrun" : "";

                squirrelApps
                .Select(exe => new ProcessStartInfo(exe, firstRunParam)
                {
                    WorkingDirectory = Path.GetDirectoryName(exe)
                })
                .ForEach(info => Process.Start(info));
            }
            public async Task FullUninstall()
            {
                var currentRelease = getReleases().MaxBy(x => x.Name.ToSemanticVersion()).FirstOrDefault();

                this.Log().Info("Starting full uninstall");
                if (currentRelease.Exists)
                {
                    var version = currentRelease.Name.ToSemanticVersion();

                    try {
                        var squirrelAwareApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName);

                        if (isAppFolderDead(currentRelease.FullName))
                        {
                            throw new Exception("App folder is dead, but we're trying to uninstall it?");
                        }

                        var allApps = currentRelease.EnumerateFiles()
                                      .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                                      .Where(x => !x.Name.StartsWith("squirrel.", StringComparison.OrdinalIgnoreCase) && !x.Name.StartsWith("update.", StringComparison.OrdinalIgnoreCase))
                                      .ToList();

                        if (squirrelAwareApps.Count > 0)
                        {
                            await squirrelAwareApps.ForEachAsync(async exe => {
                                using (var cts = new CancellationTokenSource()) {
                                    cts.CancelAfter(10 * 1000);

                                    try {
                                        await Utility.InvokeProcessAsync(exe, String.Format("--squirrel-uninstall {0}", version), cts.Token);
                                    } catch (Exception ex) {
                                        this.Log().ErrorException("Failed to run cleanup hook, continuing: " + exe, ex);
                                    }
                                }
                            }, 1 /*at a time*/);
                        }
                        else
                        {
                            allApps.ForEach(x => RemoveShortcutsForExecutable(x.Name, ShortcutLocations.Defaults));
                        }

                        // NB: Some people attempt to uninstall apps while
                        // they're still running. I cannot even.
                        var toKill = allApps
                                     .SelectMany(x => Process.GetProcessesByName(x.Name.Replace(".exe", "")))
                                     .ToList();

                        if (toKill.Count > 0)
                        {
                            toKill.ForEach(x => x.Kill());
                            Thread.Sleep(750);
                        }
                    } catch (Exception ex) {
                        this.Log().WarnException("Failed to run pre-uninstall hooks, uninstalling anyways", ex);
                    }
                }

                fixPinnedExecutables(new SemanticVersion(255, 255, 255, 255));

                bool      didSucceedDeleting = false;
                const int retryAttempts      = 10;

                for (int i = 0; i < retryAttempts; ++i)
                {
                    try {
                        await Utility.DeleteDirectory(rootAppDirectory);

                        didSucceedDeleting = true;
                    } catch (Exception) {
                        Thread.Sleep(1000); // Give the OS a second to release handles and we'll try again
                    }
                }

                if (!didSucceedDeleting)
                {
                    await this.ErrorIfThrows(() => Utility.DeleteDirectoryOrJustGiveUp(rootAppDirectory),
                                             "Failed to delete app directory: " + rootAppDirectory);
                }

                // NB: We drop this file here so that --checkInstall will ignore
                // this folder - if we don't do this, users who "accidentally" run as
                // administrator will find the app reinstalling itself on every
                // reboot
                File.WriteAllText(Path.Combine(rootAppDirectory, ".dead"), " ");
            }
            // NB: Once we uninstall the old version of the app, we try to schedule
            // it to be deleted at next reboot. Unfortunately, depending on whether
            // the user has admin permissions, this can fail. So as a failsafe,
            // before we try to apply any update, we assume previous versions in the
            // directory are "dead" (i.e. already uninstalled, but not deleted), and
            // we blow them away. This is to make sure that we don't attempt to run
            // an uninstaller on an already-uninstalled version.
            async Task cleanDeadVersions(Version originalVersion, Version currentVersion, bool forceUninstall = false)
            {
                if (currentVersion == null)
                {
                    return;
                }

                var di = new DirectoryInfo(rootAppDirectory);

                if (!di.Exists)
                {
                    return;
                }

                this.Log().Info("cleanDeadVersions: for version {0}", currentVersion);

                string originalVersionFolder = null;

                if (originalVersion != null)
                {
                    originalVersionFolder = getDirectoryForRelease(originalVersion).Name;
                    this.Log().Info("cleanDeadVersions: exclude folder {0}", originalVersionFolder);
                }

                string currentVersionFolder = null;

                if (currentVersion != null)
                {
                    currentVersionFolder = getDirectoryForRelease(currentVersion).Name;
                    this.Log().Info("cleanDeadVersions: exclude folder {0}", currentVersionFolder);
                }

                // NB: If we try to access a directory that has already been
                // scheduled for deletion by MoveFileEx it throws what seems like
                // NT's only error code, ERROR_ACCESS_DENIED. Squelch errors that
                // come from here.
                var toCleanup = di.GetDirectories()
                                .Where(x => x.Name.ToLowerInvariant().Contains("app-"))
                                .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder);

                if (forceUninstall == false)
                {
                    await toCleanup.ForEachAsync(async x => {
                        var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(x.FullName);
                        var args         = String.Format("--squirrel-obsolete {0}", x.Name.Replace("app-", ""));

                        if (squirrelApps.Count > 0)
                        {
                            // For each app, run the install command in-order and wait
                            await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */);
                        }
                    });
                }

                // Finally, clean up the app-X.Y.Z directories
                await toCleanup.ForEachAsync(async x => {
                    try {
                        await Utility.DeleteDirectoryWithFallbackToNextReboot(x.FullName);
                    } catch (UnauthorizedAccessException ex) {
                        this.Log().WarnException("Couldn't delete directory: " + x.FullName, ex);
                    }
                });

                // Clean up the packages directory too
                var releasesFile = Utility.LocalReleaseFileForAppDir(rootAppDirectory);
                var entries      = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesFile, Encoding.UTF8));
                var pkgDir       = Utility.PackageDirectoryForAppDir(rootAppDirectory);
                var releaseEntry = default(ReleaseEntry);

                foreach (var entry in entries)
                {
                    if (entry.Version == currentVersion)
                    {
                        releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename));
                        continue;
                    }

                    File.Delete(Path.Combine(pkgDir, entry.Filename));
                }

                ReleaseEntry.WriteReleaseFile(new[] { releaseEntry }, releasesFile);
            }