// 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 currentVersion, SemanticVersion newVersion, bool forceUninstall = false)
            {
                if (newVersion == null)
                {
                    return;
                }

                var di = new DirectoryInfo(rootAppDirectory);

                if (!di.Exists)
                {
                    return;
                }

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

                string currentVersionFolder = null;

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

                string newVersionFolder = null;

                if (newVersion != null)
                {
                    newVersionFolder = getDirectoryForRelease(newVersion).Name;
                    this.Log().Info("cleanDeadVersions: exclude new version folder {0}", newVersionFolder);
                }

                // 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 != newVersionFolder && x.Name != currentVersionFolder)
                                .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 != newVersionFolder && x.Name != currentVersionFolder);

                // 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 == null || !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 == newVersion)
                    {
                        releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename));
                        continue;
                    }

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

                ReleaseEntry.WriteReleaseFile(new[] { releaseEntry }, releasesFile);
            }
            public int KillAllProcessesBelongingToPackage()
            {
                var ourExe     = Assembly.GetEntryAssembly();
                var ourExePath = ourExe != null ? ourExe.Location : null;

                List <Tuple <string, int> > running = null;

                running = UnsafeUtility.EnumerateProcesses()
                          .Where(x =>
                {
                    // Processes we can't query will have an empty process name, we can't kill them
                    // anyways
                    if (String.IsNullOrWhiteSpace(x.Item1))
                    {
                        return(false);
                    }

                    // Files that aren't in our root app directory are untouched
                    if (!x.Item1.StartsWith(rootAppDirectory, StringComparison.OrdinalIgnoreCase))
                    {
                        return(false);
                    }

                    // Never kill our own EXE
                    if (ourExePath != null && x.Item1.Equals(ourExePath, StringComparison.OrdinalIgnoreCase))
                    {
                        return(false);
                    }

                    var name = Path.GetFileName(x.Item1).ToLowerInvariant();
                    if (name == "squirrel.exe" || name == "update.exe")
                    {
                        return(false);
                    }

                    return(true);
                }).ToList();

                running.Sort((Tuple <string, int> p1, Tuple <string, int> p2) =>
                {
                    if (p1.Item1 == default)
                    {
                        return(p2.Item1 == default ? -1 : 0);
                    }
                    if (p2.Item1 == default)
                    {
                        return(1);
                    }

                    var p1Parent = UnsafeUtility.GetParentProcessId(p1.Item2);
                    var p2Parent = UnsafeUtility.GetParentProcessId(p2.Item2);

                    if (p1Parent == -1 && p2Parent == -1)
                    {
                        return(0);
                    }

                    // no parent is higher.
                    if (p1Parent == -1)
                    {
                        return(1);
                    }
                    if (p2Parent == -1)
                    {
                        return(-1);
                    }

                    // direct relationships
                    if (p1.Item2 == p2Parent)
                    {
                        return(1);
                    }

                    if (p2.Item2 == p1Parent)
                    {
                        return(-1);
                    }

                    // don't care
                    return(0);
                });

                running.ForEach(x =>
                {
                    try
                    {
                        this.WarnIfThrows(() => Process.GetProcessById(x.Item2).Kill());
                    }
                    catch { }
                });

                return(running.Count);
            }