// 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); }