public void RemoveShortcutsForExecutable(string exeName, ShortcutLocation locations)
            {
                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var fileVerInfo = FileVersionInfo.GetVersionInfo(
                    Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName));

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file = linkTargetForVersionInfo(f, zf, fileVerInfo);

                    this.Log().Info("Removing shortcut for {0} => {1}", exeName, file);

                    this.ErrorIfThrows(() => {
                        if (File.Exists(file))
                        {
                            File.Delete(file);
                        }
                    }, "Couldn't delete shortcut: " + file);
                }

                fixPinnedExecutables(zf.Version);
            }
            public void CreateShortcutsForExecutable(string exeName, ShortcutLocation locations, bool updateOnly)
            {
                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var exePath     = Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName);
                var fileVerInfo = FileVersionInfo.GetVersionInfo(exePath);

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file       = linkTargetForVersionInfo(f, zf, fileVerInfo);
                    var fileExists = File.Exists(file);

                    // NB: If we've already installed the app, but the shortcut
                    // is no longer there, we have to assume that the user didn't
                    // want it there and explicitly deleted it, so we shouldn't
                    // annoy them by recreating it.
                    if (!fileExists && updateOnly)
                    {
                        this.Log().Warn("Wanted to update shortcut {0} but it appears user deleted it", file);
                        continue;
                    }

                    this.Log().Info("Creating shortcut for {0} => {1}", exeName, file);

                    this.ErrorIfThrows(() => {
                        if (fileExists)
                        {
                            File.Delete(file);
                        }

                        var sl = new ShellLink {
                            Target           = exePath,
                            IconPath         = exePath,
                            IconIndex        = 0,
                            WorkingDirectory = Path.GetDirectoryName(exePath),
                            Description      = zf.Description,
                        };

                        this.Log().Info("About to save shortcut: {0}", file);
                        if (ModeDetector.InUnitTestRunner() == false)
                        {
                            sl.Save(file);
                        }
                    }, "Can't write shortcut: " + file);
                }
            }
            public Dictionary <ShortcutLocation, ShellLink> GetShortcutsForExecutable(string exeName, ShortcutLocation locations, string programArguments)
            {
                this.Log().Info("About to create shortcuts for {0}, rootAppDir {1}", exeName, rootAppDirectory);

                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var exePath     = Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName);
                var fileVerInfo = FileVersionInfo.GetVersionInfo(exePath);

                var ret = new Dictionary <ShortcutLocation, ShellLink>();

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file                 = linkTargetForVersionInfo(f, zf, fileVerInfo);
                    var appUserModelId       = String.Format("com.squirrel.{0}.{1}", zf.Id.Replace(" ", ""), exeName.Replace(".exe", "").Replace(" ", ""));
                    var toastActivatorCLSDID = Utility.CreateGuidFromHash(appUserModelId).ToString();

                    this.Log().Info("Creating shortcut for {0} => {1}", exeName, file);
                    this.Log().Info("appUserModelId: {0} | toastActivatorCLSID: {1}", appUserModelId, toastActivatorCLSDID);

                    var target = Path.Combine(rootAppDirectory, exeName);
                    var sl     = new ShellLink
                    {
                        Target           = target,
                        IconPath         = target,
                        IconIndex        = 0,
                        WorkingDirectory = Path.GetDirectoryName(exePath),
                        Description      = zf.Description,
                    };

                    if (!String.IsNullOrWhiteSpace(programArguments))
                    {
                        sl.Arguments += String.Format(" -a \"{0}\"", programArguments);
                    }

                    sl.SetAppUserModelId(appUserModelId);
                    sl.SetToastActivatorCLSID(toastActivatorCLSDID);

                    ret.Add(f, sl);
                }

                return(ret);
            }
Exemplo n.º 4
0
            UpdateInfo determineUpdateInfo(UpdaterIntention intention, IEnumerable <ReleaseEntry> localReleases, IEnumerable <ReleaseEntry> remoteReleases, bool ignoreDeltaUpdates)
            {
                var packageDirectory = Utility.PackageDirectoryForAppDir(rootAppDirectory);

                localReleases = localReleases ?? Enumerable.Empty <ReleaseEntry>();

                if (remoteReleases == null)
                {
                    this.Log().Warn("Release information couldn't be determined due to remote corrupt RELEASES file");
                    throw new Exception("Corrupt remote RELEASES file");
                }

                var latestFullRelease = Utility.FindCurrentVersion(remoteReleases);
                var currentRelease    = Utility.FindCurrentVersion(localReleases);

                if (latestFullRelease == currentRelease)
                {
                    this.Log().Info("No updates, remote and local are the same");

                    var info = UpdateInfo.Create(currentRelease, new[] { latestFullRelease }, packageDirectory);
                    return(info);
                }

                if (ignoreDeltaUpdates)
                {
                    remoteReleases = remoteReleases.Where(x => !x.IsDelta);
                }

                if (!localReleases.Any())
                {
                    if (intention == UpdaterIntention.Install)
                    {
                        this.Log().Info("First run, starting from scratch");
                    }
                    else
                    {
                        this.Log().Warn("No local releases found, starting from scratch");
                    }

                    return(UpdateInfo.Create(null, new[] { latestFullRelease }, packageDirectory));
                }

                if (localReleases.Max(x => x.Version) > remoteReleases.Max(x => x.Version))
                {
                    this.Log().Warn("hwhat, local version is greater than remote version");
                    return(UpdateInfo.Create(Utility.FindCurrentVersion(localReleases), new[] { latestFullRelease }, packageDirectory));
                }

                return(UpdateInfo.Create(currentRelease, remoteReleases, packageDirectory));
            }
Exemplo n.º 5
0
            public Dictionary <ShortcutLocation, ShellLink> GetShortcutsForExecutable(string exeName, ShortcutLocation locations, string programArguments)
            {
                this.Log().Info("About to create shortcuts for {0}, rootAppDir {1}", exeName, rootAppDirectory);

                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);
                var updateExe   = Path.Combine(rootAppDirectory, "update.exe");

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var exePath     = Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName);
                var fileVerInfo = FileVersionInfo.GetVersionInfo(exePath);

                var ret = new Dictionary <ShortcutLocation, ShellLink>();

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file = linkTargetForVersionInfo(f, zf, fileVerInfo);

                    this.Log().Info("Creating shortcut for {0} => {1}", exeName, file);

                    ShellLink sl;
                    sl = new ShellLink {
                        Target           = updateExe,
                        IconPath         = exePath,
                        IconIndex        = 0,
                        WorkingDirectory = Path.GetDirectoryName(exePath),
                        Description      = zf.Description,
                        Arguments        = "--processStart " + exeName,
                    };

                    if (!String.IsNullOrWhiteSpace(programArguments))
                    {
                        sl.Arguments += String.Format(" -a \"{0}\"", programArguments);
                    }

                    sl.SetAppUserModelId(String.Format("com.squirrel.{0}.{1}", zf.Id, exeName.Replace(".exe", "")));
                    sl.SetAppUserModelRelaunchCommand(String.Format("{0} {1}", sl.Target, sl.Arguments));
                    ret.Add(f, sl);
                }

                return(ret);
            }
            UpdateInfo determineUpdateInfo(IEnumerable <ReleaseEntry> localReleases, IEnumerable <ReleaseEntry> remoteReleases, bool ignoreDeltaUpdates)
            {
                var packageDirectory = Utility.PackageDirectoryForAppDir(rootAppDirectory);

                localReleases = localReleases ?? Enumerable.Empty <ReleaseEntry>();

                if (remoteReleases == null || !remoteReleases.Any())
                {
                    this.Log().Warn("Release information couldn't be determined due to remote corrupt RELEASES file");
                    remoteReleases = localReleases;
                }

                var latestFullRelease = Utility.FindCurrentVersion(remoteReleases);
                var currentRelease    = Utility.FindCurrentVersion(localReleases);

                if (currentRelease != null && latestFullRelease.Version <= currentRelease.Version)
                {
                    this.Log().Info("No updates");
                    var info = UpdateInfo.Create(currentRelease, new[] { currentRelease }, packageDirectory);
                    return(info);
                }
                else
                {
                    var vold = currentRelease != null ? currentRelease.Version : null;
                    var vnew = latestFullRelease != null ? latestFullRelease.Version : null;
                    this.Log().Info("Remote version {0} differs from local {1}", vnew, vold);
                }

                if (ignoreDeltaUpdates)
                {
                    remoteReleases = remoteReleases.Where(x => !x.IsDelta);
                }

                if (!localReleases.Any())
                {
                    this.Log().Warn("First run or local directory is corrupt, starting from scratch");
                    return(UpdateInfo.Create(currentRelease, new[] { latestFullRelease }, packageDirectory));
                }

                if (localReleases.Max(x => x.Version) > remoteReleases.Max(x => x.Version))
                {
                    this.Log().Warn("hwhat, local version is greater than remote version");
                    return(UpdateInfo.Create(currentRelease, new[] { latestFullRelease }, packageDirectory));
                }

                return(UpdateInfo.Create(currentRelease, remoteReleases, packageDirectory));
            }
 internal async Task <List <ReleaseEntry> > updateLocalReleasesFile()
 {
     return(await Task.Run(() => ReleaseEntry.BuildReleasesFile(Utility.PackageDirectoryForAppDir(rootAppDirectory))));
 }
            // 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);
            }
            public void CreateShortcutsForExecutable(string exeName, ShortcutLocation locations, bool updateOnly, string programArguments, string icon)
            {
                this.Log().Info("About to create shortcuts for {0}, rootAppDir {1}", exeName, rootAppDirectory);

                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var exePath     = Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName);
                var fileVerInfo = FileVersionInfo.GetVersionInfo(exePath);

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file       = linkTargetForVersionInfo(f, zf, fileVerInfo);
                    var fileExists = File.Exists(file);

                    // NB: If we've already installed the app, but the shortcut
                    // is no longer there, we have to assume that the user didn't
                    // want it there and explicitly deleted it, so we shouldn't
                    // annoy them by recreating it.
                    if (!fileExists && updateOnly)
                    {
                        this.Log().Warn("Wanted to update shortcut {0} but it appears user deleted it", file);
                        continue;
                    }

                    this.Log().Info("Creating shortcut for {0} => {1}", exeName, file);

                    ShellLink sl;
                    this.ErrorIfThrows(() => Utility.Retry(() => {
                        File.Delete(file);

                        var target = Path.Combine(rootAppDirectory, exeName);
                        sl         = new ShellLink {
                            Target           = target,
                            IconPath         = icon ?? target,
                            IconIndex        = 0,
                            WorkingDirectory = Path.GetDirectoryName(exePath),
                            Description      = zf.Description,
                        };

                        if (!String.IsNullOrWhiteSpace(programArguments))
                        {
                            sl.Arguments += String.Format(" -a \"{0}\"", programArguments);
                        }

                        var appUserModelId      = String.Format("com.squirrel.{0}.{1}", zf.Id.Replace(" ", ""), exeName.Replace(".exe", "").Replace(" ", ""));
                        var toastActivatorCLSID = Utility.CreateGuidFromHash(appUserModelId).ToString();

                        sl.SetAppUserModelId(appUserModelId);
                        sl.SetToastActivatorCLSID(toastActivatorCLSID);

                        this.Log().Info("About to save shortcut: {0} (target {1}, workingDir {2}, args {3}, toastActivatorCSLID {4})", file, sl.Target, sl.WorkingDirectory, sl.Arguments, toastActivatorCLSID);
                        if (ModeDetector.InUnitTestRunner() == false)
                        {
                            sl.Save(file);
                        }
                    }, 4), "Can't write shortcut: " + file);
                }

                fixPinnedExecutables(zf.Version);
            }
Exemplo n.º 10
0
            public void CreateShortcutsForExecutable(string exeName, ShortcutLocation locations, bool updateOnly)
            {
                this.Log().Info("About to create shortcuts for {0}, rootAppDir {1}", exeName, rootAppDirectory);

                var releases    = Utility.LoadLocalReleases(Utility.LocalReleaseFileForAppDir(rootAppDirectory));
                var thisRelease = Utility.FindCurrentVersion(releases);
                var updateExe   = Path.Combine(rootAppDirectory, "update.exe");

                var zf = new ZipPackage(Path.Combine(
                                            Utility.PackageDirectoryForAppDir(rootAppDirectory),
                                            thisRelease.Filename));

                var exePath     = Path.Combine(Utility.AppDirForRelease(rootAppDirectory, thisRelease), exeName);
                var fileVerInfo = FileVersionInfo.GetVersionInfo(exePath);

                foreach (var f in (ShortcutLocation[])Enum.GetValues(typeof(ShortcutLocation)))
                {
                    if (!locations.HasFlag(f))
                    {
                        continue;
                    }

                    var file       = linkTargetForVersionInfo(f, zf, fileVerInfo);
                    var fileExists = File.Exists(file);

                    // NB: If we've already installed the app, but the shortcut
                    // is no longer there, we have to assume that the user didn't
                    // want it there and explicitly deleted it, so we shouldn't
                    // annoy them by recreating it.
                    if (!fileExists && updateOnly)
                    {
                        this.Log().Warn("Wanted to update shortcut {0} but it appears user deleted it", file);
                        continue;
                    }

                    this.Log().Info("Creating shortcut for {0} => {1}", exeName, file);

                    ShellLink sl;
                    this.ErrorIfThrows(() => {
                        if (fileExists)
                        {
                            try {
                                sl = new ShellLink();

                                sl.Open(file);
                                if (sl.Target == updateExe && sl.Description == zf.Description && sl.IconPath == exePath)
                                {
                                    return;
                                }

                                File.Delete(file);
                            } catch (Exception ex) {
                                this.Log().WarnException("Tried to compare shortcut and failed", ex);
                                File.Delete(file);
                            }
                        }

                        sl = new ShellLink {
                            Target           = updateExe,
                            IconPath         = exePath,
                            IconIndex        = 0,
                            WorkingDirectory = Path.GetDirectoryName(exePath),
                            Description      = zf.Description,
                            Arguments        = "--processStart " + exeName,
                        };

                        sl.SetAppUserModelId(String.Format("com.squirrel.{0}.{1}", zf.Id, exeName.Replace(".exe", "")));

                        this.Log().Info("About to save shortcut: {0} (target {1}, workingDir {2}, args {3})", file, sl.Target, sl.WorkingDirectory, sl.Arguments);
                        if (ModeDetector.InUnitTestRunner() == false)
                        {
                            sl.Save(file);
                        }
                    }, "Can't write shortcut: " + file);
                }
            }
            // 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);
            }