Exemple #1
0
        public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null)
        {
            sourceDirectory = sourceDirectory ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var releasesPath = Path.Combine(sourceDirectory, "RELEASES");

            this.Log().Info("Starting install, writing to {0}", sourceDirectory);

            if (!File.Exists(releasesPath))
            {
                this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath);
                var nupkgs = (new DirectoryInfo(sourceDirectory)).GetFiles()
                             .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
                             .Select(x => ReleaseEntry.GenerateFromFile(x.FullName));

                ReleaseEntry.WriteReleaseFile(nupkgs, releasesPath);
            }

            var ourAppName = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesPath, Encoding.UTF8))
                             .First().PackageName;

            using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) {
                this.Log().Info("About to install to: " + mgr.RootAppDirectory);
                if (Directory.Exists(mgr.RootAppDirectory))
                {
                    this.Log().Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory);

                    await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory),
                                             "Failed to remove existing directory on full install, is the app still running???");

                    this.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3),
                                       "Couldn't recreate app directory, perhaps Antivirus is blocking it");
                }

                Directory.CreateDirectory(mgr.RootAppDirectory);

                var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe");
                this.ErrorIfThrows(() => File.Copy(Assembly.GetExecutingAssembly().Location, updateTarget, true),
                                   "Failed to copy Update.exe to " + updateTarget);

                await mgr.FullInstall(silentInstall, progressSource.Raise);

                await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(),
                                         "Failed to create uninstaller registry entry");
            }
        }
Exemple #2
0
        public void ApplyReleasesWithOneReleaseFile()
        {
            string tempDir;

            using (Utility.WithTempDirectory(out tempDir)) {
                Directory.CreateDirectory(Path.Combine(tempDir, "theApp"));
                Directory.CreateDirectory(Path.Combine(tempDir, "theApp", "packages"));

                new[] {
                    "Shimmer.Core.1.0.0.0-full.nupkg",
                    "Shimmer.Core.1.1.0.0-full.nupkg",
                }.ForEach(x => File.Copy(IntegrationTestHelper.GetPath("fixtures", x), Path.Combine(tempDir, "theApp", "packages", x)));

                var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, tempDir, null, new FakeUrlDownloader());

                var baseEntry       = ReleaseEntry.GenerateFromFile(Path.Combine(tempDir, "theApp", "packages", "Shimmer.Core.1.0.0.0-full.nupkg"));
                var latestFullEntry = ReleaseEntry.GenerateFromFile(Path.Combine(tempDir, "theApp", "packages", "Shimmer.Core.1.1.0.0-full.nupkg"));

                var updateInfo = UpdateInfo.Create(baseEntry, new[] { latestFullEntry }, "dontcare", FrameworkVersion.Net40);
                updateInfo.ReleasesToApply.Contains(latestFullEntry).ShouldBeTrue();

                using (fixture.AcquireUpdateLock()) {
                    var progress = fixture.ApplyReleases(updateInfo).ToList().First();
                    this.Log().Info("Progress: [{0}]", String.Join(",", progress));
                    progress.Buffer(2, 1).All(x => x.Count != 2 || x[1] > x[0]).ShouldBeTrue();
                    progress.Last().ShouldEqual(100);
                }

                var filesToFind = new[] {
                    new { Name = "NLog.dll", Version = new Version("2.0.0.0") },
                    new { Name = "NSync.Core.dll", Version = new Version("1.1.0.0") },
                    new { Name = "Ionic.Zip.dll", Version = new Version("1.9.1.8") },
                };

                filesToFind.ForEach(x => {
                    var path = Path.Combine(tempDir, "theApp", "app-1.1.0.0", x.Name);
                    this.Log().Info("Looking for {0}", path);
                    File.Exists(path).ShouldBeTrue();

                    var vi      = FileVersionInfo.GetVersionInfo(path);
                    var verInfo = new Version(vi.FileVersion ?? "1.0.0.0");
                    x.Version.ShouldEqual(verInfo);
                });
            }
        }
Exemple #3
0
        async Task <string> createSetupEmbeddedZip(string fullPackage, string releasesDir, string backgroundGif, string signingOpts)
        {
            string tempPath;

            this.Log().Info("Building embedded zip file for Setup.exe");
            using (Utility.WithTempDirectory(out tempPath, null)) {
                this.ErrorIfThrows(() => {
                    File.Copy(Assembly.GetEntryAssembly().Location.Replace("-Mono.exe", ".exe"), Path.Combine(tempPath, "Update.exe"));
                    File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage)));
                }, "Failed to write package files to temp dir: " + tempPath);

                if (!String.IsNullOrWhiteSpace(backgroundGif))
                {
                    this.ErrorIfThrows(() => {
                        File.Copy(backgroundGif, Path.Combine(tempPath, "background.gif"));
                    }, "Failed to write animated GIF to temp dir: " + tempPath);
                }

                var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) };
                ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES"));

                var target = Path.GetTempFileName();
                File.Delete(target);

                // Sign Update.exe so that virus scanners don't think we're
                // pulling one over on them
                if (signingOpts != null)
                {
                    var di = new DirectoryInfo(tempPath);

                    var files = di.EnumerateFiles()
                                .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe"))
                                .Select(x => x.FullName);

                    await files.ForEachAsync(x => signPEFile(x, signingOpts));
                }

                this.ErrorIfThrows(() =>
                                   ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false),
                                   "Failed to create Zip file from directory: " + tempPath);

                return(target);
            }
        }
        public void WhenNoNewReleasesAreAvailableTheListIsEmpty()
        {
            string tempDir;

            using (Utility.WithTempDirectory(out tempDir)) {
                var appDir   = Directory.CreateDirectory(Path.Combine(tempDir, "theApp"));
                var packages = Path.Combine(appDir.FullName, "packages");
                Directory.CreateDirectory(packages);

                var package = "Squirrel.Core.1.0.0.0-full.nupkg";
                File.Copy(IntegrationTestHelper.GetPath("fixtures", package), Path.Combine(packages, package));

                var aGivenPackage = Path.Combine(packages, package);
                var baseEntry     = ReleaseEntry.GenerateFromFile(aGivenPackage);

                var updateInfo = UpdateInfo.Create(baseEntry, new[] { baseEntry }, "dontcare");

                Assert.Empty(updateInfo.ReleasesToApply);
            }
        }
Exemple #5
0
        public void DownloadReleasesFromFileDirectoryIntegrationTest()
        {
            string tempDir = null;

            var updateDir = new DirectoryInfo(IntegrationTestHelper.GetPath("..", "SampleUpdatingApp", "SampleReleasesFolder"));

            var entriesToDownload = updateDir.GetFiles("*.nupkg")
                                    .Select(x => ReleaseEntry.GenerateFromFile(x.FullName))
                                    .ToArray();

            entriesToDownload.Count().ShouldBeGreaterThan(0);

            using (Utility.WithTempDirectory(out tempDir)) {
                // NB: This is normally done by CheckForUpdates, but since
                // we're skipping that in the test we have to do it ourselves
                Directory.CreateDirectory(Path.Combine(tempDir, "SampleUpdatingApp", "packages"));

                var fixture = new UpdateManager(updateDir.FullName, "SampleUpdatingApp", FrameworkVersion.Net40, tempDir);
                using (fixture) {
                    var progress = new ReplaySubject <int>();

                    fixture.DownloadReleases(entriesToDownload, progress).First();
                    this.Log().Info("Progress: [{0}]", String.Join(",", progress));

                    progress.Buffer(2, 1).All(x => x.Count != 2 || x[1] > x[0]).First().ShouldBeTrue();
                    progress.Last().ShouldEqual(100);
                }

                entriesToDownload.ForEach(x => {
                    this.Log().Info("Looking for {0}", x.Filename);
                    var actualFile = Path.Combine(tempDir, "SampleUpdatingApp", "packages", x.Filename);
                    File.Exists(actualFile).ShouldBeTrue();

                    var actualEntry = ReleaseEntry.GenerateFromFile(actualFile);
                    actualEntry.SHA1.ShouldEqual(x.SHA1);
                    actualEntry.Version.ShouldEqual(x.Version);
                });
            }
        }
        public void ExecutablesPinnedToTaskbarShouldPointToNewVersion()
        {
            string tempDir;

            using (Utility.WithTempDirectory(out tempDir)) {
                string packagesDir = Path.Combine(tempDir, "theApp", "packages");
                Directory.CreateDirectory(packagesDir);

                new[] {
                    "SampleUpdatingApp.1.0.0.0.nupkg",
                    "SampleUpdatingApp.1.1.0.0.nupkg",
                }.ForEach(x => File.Copy(IntegrationTestHelper.GetPath("fixtures", x), Path.Combine(packagesDir, x)));

                var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, tempDir, null, new FakeUrlDownloader());

                var baseEntry       = ReleaseEntry.GenerateFromFile(Path.Combine(packagesDir, "SampleUpdatingApp.1.0.0.0.nupkg"));
                var latestFullEntry = ReleaseEntry.GenerateFromFile(Path.Combine(packagesDir, "SampleUpdatingApp.1.1.0.0.nupkg"));

                var updateInfo = UpdateInfo.Create(null, new[] { baseEntry }, packagesDir, FrameworkVersion.Net40);
                using (fixture) {
                    fixture.ApplyReleases(updateInfo).ToList().First();
                }

                var oldExecutable = Path.Combine(tempDir, "theApp", "app-1.0.0.0", "SampleUpdatingApp.exe");
                File.Exists(oldExecutable).ShouldBeTrue();
                TaskbarHelper.PinToTaskbar(oldExecutable);

                updateInfo = UpdateInfo.Create(baseEntry, new[] { latestFullEntry }, packagesDir, FrameworkVersion.Net40);
                using (fixture) {
                    fixture.ApplyReleases(updateInfo).ToList().First();
                }

                var newExecutable = Path.Combine(tempDir, "theApp", "app-1.1.0.0", "SampleUpdatingApp.exe");
                File.Exists(newExecutable).ShouldBeTrue();
                TaskbarHelper.IsPinnedToTaskbar(newExecutable).ShouldBeTrue();

                Utility.Retry(() => TaskbarHelper.UnpinFromTaskbar(newExecutable));
            }
        }
        public void InstallRunsHooks()
        {
            string dir;
            string outDir;

            var package = "SampleUpdatingApp.1.2.0.0.nupkg";

            using (Utility.WithTempDirectory(out outDir))
                using (IntegrationTestHelper.WithFakeInstallDirectory(package, out dir)) {
                    var di = new DirectoryInfo(dir);

                    var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName);
                    var fixture        = new InstallManager(bundledRelease, outDir);
                    var pkg            = new ZipPackage(Path.Combine(dir, package));

                    fixture.ExecuteInstall(dir, pkg).Wait();

                    var generatedFile = Path.Combine(outDir, "SampleUpdatingApp", "app-1.2.0.0", "install");

                    Assert.True(File.Exists(generatedFile));
                }
        }
Exemple #8
0
        IObservable <ReleaseEntry> createFullPackagesFromDeltas(IEnumerable <ReleaseEntry> releasesToApply, ReleaseEntry currentVersion)
        {
            Contract.Requires(releasesToApply != null);

            // If there are no deltas in our list, we're already done
            if (!releasesToApply.Any() || releasesToApply.All(x => !x.IsDelta))
            {
                return(Observable.Return(releasesToApply.MaxBy(x => x.Version).First()));
            }

            if (!releasesToApply.All(x => x.IsDelta))
            {
                return(Observable.Throw <ReleaseEntry>(new Exception("Cannot apply combinations of delta and full packages")));
            }

            // Smash together our base full package and the nearest delta
            var ret = Observable.Start(() => {
                var basePkg  = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", currentVersion.Filename));
                var deltaPkg = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", releasesToApply.First().Filename));

                var deltaBuilder = new DeltaPackageBuilder();

                return(deltaBuilder.ApplyDeltaPackage(basePkg, deltaPkg,
                                                      Regex.Replace(deltaPkg.InputPackageFile, @"-delta.nupkg$", ".nupkg", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)));
            }, RxApp.TaskpoolScheduler);

            if (releasesToApply.Count() == 1)
            {
                return(ret.Select(x => ReleaseEntry.GenerateFromFile(x.InputPackageFile)));
            }

            return(ret.SelectMany(x => {
                var fi = fileSystem.GetFileInfo(x.InputPackageFile);
                var entry = ReleaseEntry.GenerateFromFile(fi.OpenRead(), fi.Name);

                // Recursively combine the rest of them
                return createFullPackagesFromDeltas(releasesToApply.Skip(1), entry);
            }));
        }
Exemple #9
0
        public void UninstallRemovesEverything()
        {
            string dir;
            string appDir;

            using (IntegrationTestHelper.WithFakeInstallDirectory(out dir))
                using (IntegrationTestHelper.WithFakeAlreadyInstalledApp(out appDir)) {
                    var di       = new DirectoryInfo(dir);
                    var progress = new Subject <int>();

                    var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName);
                    var fixture        = new InstallManager(bundledRelease, appDir);

                    var progressValues = new List <int>();
                    progress.Subscribe(progressValues.Add);

                    fixture.ExecuteUninstall().First();

                    di = new DirectoryInfo(appDir);
                    di.GetDirectories().Any().ShouldBeFalse();
                    di.GetFiles().Any().ShouldBeFalse();
                }
        }
Exemple #10
0
        public void ChecksumShouldFailIfFilesAreBogus()
        {
            var filename      = "Squirrel.Core.1.0.0.0.nupkg";
            var nuGetPkg      = IntegrationTestHelper.GetPath("fixtures", filename);
            var fs            = new Mock <IFileSystemFactory>();
            var urlDownloader = new Mock <IUrlDownloader>();

            ReleaseEntry entry;

            using (var f = File.OpenRead(nuGetPkg)) {
                entry = ReleaseEntry.GenerateFromFile(f, filename);
            }

            var fileInfo = new Mock <FileInfoBase>();

            fileInfo.Setup(x => x.OpenRead()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("Lol broken")));
            fileInfo.Setup(x => x.Exists).Returns(true);
            fileInfo.Setup(x => x.Length).Returns(new FileInfo(nuGetPkg).Length);
            fileInfo.Setup(x => x.Delete()).Verifiable();

            fs.Setup(x => x.GetFileInfo(Path.Combine(".", "theApp", "packages", filename))).Returns(fileInfo.Object);

            var fixture = ExposedObject.From(
                new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, ".", fs.Object, urlDownloader.Object));

            bool shouldDie = true;

            try {
                fixture.checksumPackage(entry);
            } catch (Exception ex) {
                this.Log().InfoException("Checksum failure", ex);
                shouldDie = false;
            }

            shouldDie.ShouldBeFalse();
            fileInfo.Verify(x => x.Delete(), Times.Once());
        }
        string createSetupEmbeddedZip(string fullPackage, string releasesDir)
        {
            string tempPath;

            this.Log().Info("Building embedded zip file for Setup.exe");
            using (Utility.WithTempDirectory(out tempPath)) {
                this.ErrorIfThrows(() => {
                    File.Copy(Assembly.GetEntryAssembly().Location, Path.Combine(tempPath, "Update.exe"));
                    File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage)));
                }, "Failed to write package files to temp dir: " + tempPath);

                var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) };
                ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES"));

                var target = Path.GetTempFileName();
                File.Delete(target);

                this.ErrorIfThrows(() =>
                                   ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false),
                                   "Failed to create Zip file from directory: " + tempPath);

                return(target);
            }
        }
Exemple #12
0
        public void InstallWithContentInPackageDropsInSameFolder()
        {
            string dir;
            string outDir;

            var package = "ProjectWithContent.1.0.0.0-beta-full.nupkg";

            using (Utility.WithTempDirectory(out outDir))
                using (IntegrationTestHelper.WithFakeInstallDirectory(package, out dir))
                {
                    try
                    {
                        var di = new DirectoryInfo(dir);

                        var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName);
                        var fixture        = new InstallManager(bundledRelease, outDir);
                        var pkg            = new ZipPackage(Path.Combine(dir, package));

                        fixture.ExecuteInstall(dir, pkg).Wait();

                        var filesToLookFor = new[] {
                            "ProjectWithContent\\app-1.0.0.0\\project-with-content.exe",
                            "ProjectWithContent\\app-1.0.0.0\\some-words.txt",
                            "ProjectWithContent\\app-1.0.0.0\\dir\\item-in-subdirectory.txt",
                            "ProjectWithContent\\packages\\RELEASES",
                            "ProjectWithContent\\packages\\ProjectWithContent.1.0.0.0-beta-full.nupkg",
                        };

                        filesToLookFor.ForEach(f => Assert.True(File.Exists(Path.Combine(outDir, f)), "Could not find file: " + f));
                    }
                    finally
                    {
                        Directory.Delete(dir, true);
                    }
                }
        }
Exemple #13
0
            public async Task <UpdateInfo> CheckForUpdate(
                string localReleaseFile,
                string updateUrlOrPath,
                bool ignoreDeltaUpdates       = false,
                Action <int> progress         = null,
                IFileDownloader urlDownloader = null)
            {
                progress = progress ?? (_ => { });

                var localReleases = Enumerable.Empty <ReleaseEntry>();
                var stagingId     = GetOrCreateStagedUserId();

                var shouldInitialize = false;

                try
                {
                    localReleases = Utility.LoadLocalReleases(localReleaseFile);
                }
                catch (Exception ex)
                {
                    // Something has gone pear-shaped, let's start from scratch
                    Log.Warn("Failed to load local releases, starting from scratch", ex);
                    shouldInitialize = true;
                }

                if (shouldInitialize)
                {
                    await InitializeClientAppDirectory();
                }

                string releaseFile;

                var latestLocalRelease = localReleases.Count() > 0
                    ? localReleases.MaxBy(x => x.Version).First()
                    : default;

                // Fetch the remote RELEASES file, whether it's a local dir or an
                // HTTP URL
                if (Utility.IsHttpUrl(updateUrlOrPath))
                {
                    if (updateUrlOrPath.EndsWith("/"))
                    {
                        updateUrlOrPath = updateUrlOrPath.Substring(0, updateUrlOrPath.Length - 1);
                    }

                    Log.InfoFormat("Downloading RELEASES file from {0}", updateUrlOrPath);

                    var retries = 3;

retry:

                    try
                    {
                        var uri = Utility.AppendPathToUri(new Uri(updateUrlOrPath), "RELEASES");

                        if (latestLocalRelease != null)
                        {
                            uri = Utility.AddQueryParamsToUri(
                                uri,
                                new Dictionary <string, string>
                            {
                                { "id", latestLocalRelease.PackageName },
                                { "localVersion", latestLocalRelease.Version.ToString() },
                                {
                                    "arch", Environment.Is64BitOperatingSystem
                                            ? "amd64"
                                            : "x86"
                                }
                            });
                        }

                        var data = await urlDownloader.DownloadUrl(uri.ToString());

                        releaseFile = Encoding.UTF8.GetString(data);
                    }
                    catch (WebException ex)
                    {
                        Log.Info("Download resulted in WebException (returning blank release list)", ex);

                        if (retries <= 0)
                        {
                            throw;
                        }

                        retries--;
                        goto retry;
                    }

                    progress(33);
                }
                else
                {
                    Log.InfoFormat("Reading RELEASES file from {0}", updateUrlOrPath);

                    if (!Directory.Exists(updateUrlOrPath))
                    {
                        var message = $"The directory {updateUrlOrPath} does not exist, something is probably broken with your application";

                        throw new Exception(message);
                    }

                    var fi = new FileInfo(Path.Combine(updateUrlOrPath, "RELEASES"));
                    if (!fi.Exists)
                    {
                        var message = $"The file {fi.FullName} does not exist, something is probably broken with your application";

                        Log.WarnFormat(message);

                        var packages = new DirectoryInfo(updateUrlOrPath).GetFiles("*.nupkg");
                        if (packages.Length == 0)
                        {
                            throw new Exception(message);
                        }

                        // NB: Create a new RELEASES file since we've got a directory of packages
                        ReleaseEntry.WriteReleaseFile(
                            packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)),
                            fi.FullName);
                    }

                    releaseFile = File.ReadAllText(fi.FullName, Encoding.UTF8);
                    progress(33);
                }

                var ret            = default(UpdateInfo);
                var remoteReleases = ReleaseEntry.ParseReleaseFileAndApplyStaging(releaseFile, stagingId);

                progress(66);

                if (!remoteReleases.Any())
                {
                    throw new Exception("Remote release File is empty or corrupted");
                }

                ret = DetermineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates);

                progress(100);
                return(ret);
            }
Exemple #14
0
        async Task <int> main(string[] args)
        {
            using (var logger = new SetupLogLogger(false)
            {
                Level = Splat.LogLevel.Info
            }) {
                Splat.Locator.CurrentMutable.Register(() => logger, typeof(Splat.ILogger));

                var releaseDir = default(string);
                var repoUrl    = default(string);
                var token      = default(string);

                opts = new OptionSet()
                {
                    "Usage: SyncGitHubReleases.exe command [OPTS]",
                    "Builds a Releases directory from releases on GitHub",
                    "",
                    "Options:",
                    { "h|?|help", "Display Help and exit", _ => {} },
                    { "r=|releaseDir=", "Path to a release directory to download to", v => releaseDir = v },
                    { "u=|repoUrl=", "The URL to the repository root page", v => repoUrl = v },
                    { "t=|token=", "The OAuth token to use as login credentials", v => token = v },
                };

                opts.Parse(args);

                if (token == null || repoUrl == null || repoUrl.StartsWith("http", true, CultureInfo.InvariantCulture) == false)
                {
                    ShowHelp();
                    return(-1);
                }

                var releaseDirectoryInfo = new DirectoryInfo(releaseDir ?? Path.Combine(".", "Releases"));
                if (!releaseDirectoryInfo.Exists)
                {
                    releaseDirectoryInfo.Create();
                }

                var repoUri   = new Uri(repoUrl);
                var userAgent = new ProductHeaderValue("SyncGitHubReleases", Assembly.GetExecutingAssembly().GetName().Version.ToString());
                var client    = new GitHubClient(userAgent, repoUri)
                {
                    Credentials = new Credentials(token)
                };

                var nwo      = nwoFromRepoUrl(repoUrl);
                var releases = await client.Release.GetAll(nwo.Item1, nwo.Item2);

                await releases.ForEachAsync(async release => {
                    // NB: Why do I have to double-fetch the release assets? It's already in GetAll
                    var assets = await client.Release.GetAssets(nwo.Item1, nwo.Item2, release.Id);

                    await assets
                    .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
                    .Where(x => {
                        var fi = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name));
                        return(!(fi.Exists && fi.Length == x.Size));
                    })
                    .ForEachAsync(async x => {
                        var target = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name));
                        if (target.Exists)
                        {
                            target.Delete();
                        }

                        var hc = new HttpClient();
                        var rq = new HttpRequestMessage(HttpMethod.Get, x.Url);
                        rq.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream"));
                        rq.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(userAgent.Name, userAgent.Version));
                        rq.Headers.Add("Authorization", "Bearer " + token);

                        var resp = await hc.SendAsync(rq);
                        resp.EnsureSuccessStatusCode();

                        using (var from = await resp.Content.ReadAsStreamAsync())
                            using (var to = File.OpenWrite(target.FullName)) {
                                await from.CopyToAsync(to);
                            }
                    });
                });

                var entries = releaseDirectoryInfo.GetFiles("*.nupkg")
                              .AsParallel()
                              .Select(x => ReleaseEntry.GenerateFromFile(x.FullName));

                ReleaseEntry.WriteReleaseFile(entries, Path.Combine(releaseDirectoryInfo.FullName, "RELEASES"));
            }

            return(0);
        }
Exemple #15
0
        public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null)
        {
            if (baseUrl != null)
            {
                if (!Utility.IsHttpUrl(baseUrl))
                {
                    throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl));
                }

                if (!baseUrl.EndsWith("/"))
                {
                    baseUrl += "/";
                }
            }

            targetDir       = targetDir ?? ".\\Releases";
            packagesDir     = packagesDir ?? ".";
            bootstrapperExe = bootstrapperExe ?? ".\\Setup.exe";

            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
            }

            if (!File.Exists(bootstrapperExe))
            {
                bootstrapperExe = Path.Combine(
                    Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
                    "Setup.exe");
            }

            this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe);

            var di = new DirectoryInfo(targetDir);

            File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true);

            var allNuGetFiles = di.EnumerateFiles()
                                .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase));

            var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full"));
            var processed = new List <string>();

            var releaseFilePath  = Path.Combine(di.FullName, "RELEASES");
            var previousReleases = Enumerable.Empty <ReleaseEntry>();

            if (File.Exists(releaseFilePath))
            {
                previousReleases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8));
            }

            foreach (var file in toProcess)
            {
                this.Log().Info("Creating release package: " + file.FullName);

                var rp = new ReleasePackage(file.FullName);
                rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir, contentsPostProcessHook: pkgPath => {
                    if (signingOpts == null)
                    {
                        return;
                    }

                    new DirectoryInfo(pkgPath).GetAllFilesRecursively()
                    .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe"))
                    .ForEachAsync(x => signPEFile(x.FullName, signingOpts))
                    .Wait();
                });

                processed.Add(rp.ReleasePackageFile);

                var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir);
                if (prev != null)
                {
                    var deltaBuilder = new DeltaPackageBuilder();

                    var dp = deltaBuilder.CreateDeltaPackage(prev, rp,
                                                             Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta")));
                    processed.Insert(0, dp.InputPackageFile);
                }
            }

            foreach (var file in toProcess)
            {
                File.Delete(file.FullName);
            }

            var releaseEntries = previousReleases.Concat(processed.Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl)));

            ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath);

            var targetSetupExe    = Path.Combine(di.FullName, "Setup.exe");
            var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First();

            File.Copy(bootstrapperExe, targetSetupExe, true);
            var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, backgroundGif, signingOpts).Result;

            try {
                var zip = File.ReadAllBytes(zipPath);

                IntPtr handle = NativeMethods.BeginUpdateResource(targetSetupExe, false);
                if (handle == IntPtr.Zero)
                {
                    throw new Win32Exception();
                }

                if (!NativeMethods.UpdateResource(handle, "DATA", new IntPtr(131), 0x0409, zip, zip.Length))
                {
                    throw new Win32Exception();
                }

                if (!NativeMethods.EndUpdateResource(handle, false))
                {
                    throw new Win32Exception();
                }
            } catch (Exception ex) {
                this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex);
            } finally {
                File.Delete(zipPath);
            }

            Utility.Retry(() =>
                          setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait());

            if (signingOpts != null)
            {
                signPEFile(targetSetupExe, signingOpts).Wait();
            }
        }
Exemple #16
0
        public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null, bool generateMsi = true)
        {
            if (baseUrl != null)
            {
                if (!Utility.IsHttpUrl(baseUrl))
                {
                    throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl));
                }

                if (!baseUrl.EndsWith("/"))
                {
                    baseUrl += "/";
                }
            }

            targetDir       = targetDir ?? Path.Combine(".", "Releases");
            packagesDir     = packagesDir ?? ".";
            bootstrapperExe = bootstrapperExe ?? Path.Combine(".", "Setup.exe");

            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
            }

            if (!File.Exists(bootstrapperExe))
            {
                bootstrapperExe = Path.Combine(
                    Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
                    "Setup.exe");
            }

            this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe);

            var di = new DirectoryInfo(targetDir);

            File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true);

            var allNuGetFiles = di.EnumerateFiles()
                                .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase));

            var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full"));
            var processed = new List <string>();

            var releaseFilePath  = Path.Combine(di.FullName, "RELEASES");
            var previousReleases = new List <ReleaseEntry>();

            if (File.Exists(releaseFilePath))
            {
                previousReleases.AddRange(ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8)));
            }

            foreach (var file in toProcess)
            {
                this.Log().Info("Creating release package: " + file.FullName);

                var rp = new ReleasePackage(file.FullName);
                rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir, contentsPostProcessHook: pkgPath => {
                    new DirectoryInfo(pkgPath).GetAllFilesRecursively()
                    .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe"))
                    .Where(x => !x.Name.ToLowerInvariant().Contains("squirrel.exe"))
                    .Where(x => Utility.ExecutableUsesWin32Subsystem(x.FullName))
                    .ForEachAsync(x => createExecutableStubForExe(x.FullName))
                    .Wait();

                    if (signingOpts == null)
                    {
                        return;
                    }

                    new DirectoryInfo(pkgPath).GetAllFilesRecursively()
                    .Where(x => Utility.FileIsLikelyPEImage(x.Name))
                    .ForEachAsync(async x => {
                        if (isPEFileSigned(x.FullName))
                        {
                            this.Log().Info("{0} is already signed, skipping", x.FullName);
                            return;
                        }

                        this.Log().Info("About to sign {0}", x.FullName);
                        await signPEFile(x.FullName, signingOpts);
                    })
                    .Wait();
                });

                processed.Add(rp.ReleasePackageFile);

                var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir);
                if (prev != null)
                {
                    var deltaBuilder = new DeltaPackageBuilder(null);

                    var dp = deltaBuilder.CreateDeltaPackage(prev, rp,
                                                             Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta")));
                    processed.Insert(0, dp.InputPackageFile);
                }
            }

            foreach (var file in toProcess)
            {
                File.Delete(file.FullName);
            }

            var newReleaseEntries = processed
                                    .Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl))
                                    .ToList();
            var distinctPreviousReleases = previousReleases
                                           .Where(x => !newReleaseEntries.Select(e => e.Version).Contains(x.Version));
            var releaseEntries = distinctPreviousReleases.Concat(newReleaseEntries).ToList();

            ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath);

            var targetSetupExe    = Path.Combine(di.FullName, "Setup.exe");
            var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First();

            File.Copy(bootstrapperExe, targetSetupExe, true);
            var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, backgroundGif, signingOpts).Result;

            var writeZipToSetup = findExecutable("WriteZipToSetup.exe");

            try {
                var result = Utility.InvokeProcessAsync(writeZipToSetup, String.Format("\"{0}\" \"{1}\"", targetSetupExe, zipPath), CancellationToken.None).Result;
                if (result.Item1 != 0)
                {
                    throw new Exception("Failed to write Zip to Setup.exe!\n\n" + result.Item2);
                }
            } catch (Exception ex) {
                this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex);
            } finally {
                File.Delete(zipPath);
            }

            Utility.Retry(() =>
                          setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait());

            if (signingOpts != null)
            {
                signPEFile(targetSetupExe, signingOpts).Wait();
            }

            if (generateMsi)
            {
                createMsiPackage(targetSetupExe, new ZipPackage(package)).Wait();

                if (signingOpts != null)
                {
                    signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait();
                }
            }
        }
        private void CreateDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary <string, string> baseFileListing)
        {
            // NB: There are three cases here that we'll handle:
            //
            // 1. Exists only in new => leave it alone, we'll use it directly.
            // 2. Exists in both old and new => write a dummy file so we know
            //    to keep it.
            // 3. Exists in old but changed in new => create a delta file
            //
            // The fourth case of "Exists only in old => delete it in new"
            // is handled when we apply the delta package
            var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, "");

            if (!baseFileListing.ContainsKey(relativePath))
            {
                Log.InfoFormat("{0} not found in base package, marking as new", relativePath);
                return;
            }

            var oldData = File.ReadAllBytes(baseFileListing[relativePath]);
            var newData = File.ReadAllBytes(targetFile.FullName);

            if (BytesAreIdentical(oldData, newData))
            {
                Log.InfoFormat("{0} hasn't changed, writing dummy file", relativePath);

                File.Create(targetFile.FullName + ".diff").Dispose();
                File.Create(targetFile.FullName + ".shasum").Dispose();
                targetFile.Delete();
                return;
            }

            Log.InfoFormat("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName);
            var msDelta = new MsDeltaCompression();

            if (targetFile.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) ||
                targetFile.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
                targetFile.Extension.Equals(".node", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff");
                    goto exit;
                }
                catch (Exception)
                {
                    Log.WarnFormat("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name);
                }
            }

            try
            {
                using (var of = File.Create(targetFile.FullName + ".bsdiff"))
                {
                    BinaryPatchUtility.Create(oldData, newData, of);

                    // NB: Create a dummy corrupt .diff file so that older
                    // versions which don't understand bsdiff will fail out
                    // until they get upgraded, instead of seeing the missing
                    // file and just removing it.
                    File.WriteAllText(targetFile.FullName + ".diff", "1");
                }
            }
            catch (Exception ex)
            {
                Log.Warn($"We really couldn't create a delta for {targetFile.Name}", ex);

                Utility.DeleteFileHarder(targetFile.FullName + ".bsdiff", true);
                Utility.DeleteFileHarder(targetFile.FullName + ".diff", true);
                return;
            }

exit:

            var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum");

            File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8);
            targetFile.Delete();
        }
Exemple #18
0
        IObservable <UpdateInfo> checkForUpdate(bool ignoreDeltaUpdates = false, IObserver <int> progress = null)
        {
            var localReleases = Enumerable.Empty <ReleaseEntry>();

            progress = progress ?? new Subject <int>();

            try {
                var file = fileSystem.GetFileInfo(LocalReleaseFile).OpenRead();

                // NB: sr disposes file
                using (var sr = new StreamReader(file, Encoding.UTF8)) {
                    localReleases = ReleaseEntry.ParseReleaseFile(sr.ReadToEnd());
                }
            } catch (Exception ex) {
                // Something has gone wrong, we'll start from scratch.
                log.WarnException("Failed to load local release list", ex);
                initializeClientAppDirectory();
            }

            IObservable <string> releaseFile;

            // Fetch the remote RELEASES file, whether it's a local dir or an
            // HTTP URL
            try {
                if (isHttpUrl(updateUrlOrPath))
                {
                    log.Info("Downloading RELEASES file from {0}", updateUrlOrPath);
                    releaseFile = urlDownloader.DownloadUrl(String.Format("{0}/{1}", updateUrlOrPath, "RELEASES"), progress)
                                  .Catch <string, TimeoutException>(ex => {
                        log.Info("Download timed out (returning blank release list)");
                        return(Observable.Return(String.Empty));
                    })
                                  .Catch <string, WebException>(ex => {
                        log.InfoException("Download resulted in WebException (returning blank release list)", ex);
                        return(Observable.Return(String.Empty));
                    });
                }
                else
                {
                    log.Info("Reading RELEASES file from {0}", updateUrlOrPath);

                    if (!fileSystem.GetDirectoryInfo(updateUrlOrPath).Exists)
                    {
                        var message =
                            String.Format(
                                "The directory {0} does not exist, something is probably broken with your application", updateUrlOrPath);
                        var ex = new SquirrelConfigurationException(message);
                        return(Observable.Throw <UpdateInfo>(ex));
                    }

                    var fi = fileSystem.GetFileInfo(Path.Combine(updateUrlOrPath, "RELEASES"));
                    if (!fi.Exists)
                    {
                        var message = String.Format(
                            "The file {0} does not exist, something is probably broken with your application", fi.FullName);

                        log.Warn(message);

                        var packages = fileSystem.GetDirectoryInfo(updateUrlOrPath).GetFiles("*.nupkg");
                        if (packages.Length == 0)
                        {
                            var ex = new SquirrelConfigurationException(message);
                            return(Observable.Throw <UpdateInfo>(ex));
                        }

                        // NB: Create a new RELEASES file since we've got a directory of packages
                        ReleaseEntry.WriteReleaseFile(
                            packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName);
                    }

                    using (var sr = new StreamReader(fi.OpenRead(), Encoding.UTF8)) {
                        var text = sr.ReadToEnd();
                        releaseFile = Observable.Return(text);
                    }

                    progress.OnNext(100);
                    progress.OnCompleted();
                }
            } catch (Exception ex) {
                progress.OnCompleted();
                return(Observable.Throw <UpdateInfo>(ex));
            }

            // Return null if no updates found
            var ret = releaseFile
                      .Select(ReleaseEntry.ParseReleaseFile)
                      .SelectMany(releases =>
                                  releases.Any() ? determineUpdateInfo(localReleases, releases, ignoreDeltaUpdates)
                        : Observable.Return <UpdateInfo>(null))
                      .PublishLast();

            ret.Connect();
            return(ret);
        }
Exemple #19
0
        public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null)
        {
            sourceDirectory = sourceDirectory ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var releasesPath = Path.Combine(sourceDirectory, "RELEASES");

            this.Log().Info("Starting install, writing to {0}", sourceDirectory);

            if (!File.Exists(releasesPath))
            {
                this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath);
                var nupkgs = (new DirectoryInfo(sourceDirectory)).GetFiles()
                             .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
                             .Select(x => ReleaseEntry.GenerateFromFile(x.FullName));

                ReleaseEntry.WriteReleaseFile(nupkgs, releasesPath);
            }

            var ourAppName = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesPath, Encoding.UTF8))
                             .First().PackageName;

            this.Log().Warn("Preparing to install");

            string tempSettingsFile = null;
            string destSettingsFile = null;

            using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) {
                this.Log().Info("About to install to: " + mgr.RootAppDirectory);
                if (Directory.Exists(mgr.RootAppDirectory))
                {
                    this.Log().Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory);

                    var settingsName = "UiPath.settings";
                    var settingsFile = Path.Combine(mgr.RootAppDirectory, settingsName);
                    tempSettingsFile = File.Exists(settingsFile) ? Path.GetTempPath() + Path.GetRandomFileName() : null;

                    if (tempSettingsFile != null)
                    {
                        this.Log().Warn("Backup uipath.settings");
                        destSettingsFile = settingsFile;
                        File.Copy(settingsFile, tempSettingsFile);
                    }

                    bool success    = false;
                    int  maxRetries = 3;
                    do
                    {
                        var killed = mgr.KillAllExecutablesBelongingToPackage();
                        await Task.Delay(500);

                        this.Log().Warn($"Deleted {killed} processes");

                        try
                        {
                            await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory),
                                                     "Failed to remove existing directory on full install, is the app still running???");

                            success = true;
                        }
                        catch (Exception ex)
                        {
                            this.Log().Warn($"Failed to delete whole folder. Reason {ex.ToString()}");
                            this.Log().Warn($"Attempts left {maxRetries}");
                            if (maxRetries == 0)
                            {
                                throw;
                            }
                        }
                    }while (!success || maxRetries-- > 0);
                    this.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3),
                                       "Couldn't recreate app directory, perhaps Antivirus is blocking it");
                }

                Directory.CreateDirectory(mgr.RootAppDirectory);

                var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe");
                this.ErrorIfThrows(() => File.Copy(Assembly.GetExecutingAssembly().Location, updateTarget, true),
                                   "Failed to copy Update.exe to " + updateTarget);

                await mgr.FullInstall(silentInstall, progressSource.Raise);

                if (destSettingsFile != null && tempSettingsFile != null)
                {
                    File.Copy(tempSettingsFile, destSettingsFile);
                }

                await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(),
                                         "Failed to create uninstaller registry entry");
            }
        }
            // 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.
            private async Task CleanDeadVersions(SemanticVersion originalVersion, SemanticVersion currentVersion, bool forceUninstall = false)
            {
                if (currentVersion == null)
                {
                    return;
                }

                var di = new DirectoryInfo(rootAppDirectory);

                if (!di.Exists)
                {
                    return;
                }

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

                string originalVersionFolder = null;

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

                string currentVersionFolder = null;

                if (currentVersion != null)
                {
                    currentVersionFolder = GetDirectoryForRelease(currentVersion).Name;
                    Log.InfoFormat("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         = $"--squirrel-obsolete {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)
                                    {
                                        Log.Error("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 == 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)
                    {
                        Log.Warn("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 Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null)
        {
            targetDir       = targetDir ?? ".\\Releases";
            packagesDir     = packagesDir ?? ".";
            bootstrapperExe = bootstrapperExe ?? ".\\Setup.exe";

            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
            }

            if (!File.Exists(bootstrapperExe))
            {
                bootstrapperExe = Path.Combine(
                    Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
                    "Setup.exe");
            }

            this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe);

            var di = new DirectoryInfo(targetDir);

            File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true);

            var allNuGetFiles = di.EnumerateFiles()
                                .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase));

            var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full"));

            var releaseFilePath  = Path.Combine(di.FullName, "RELEASES");
            var previousReleases = Enumerable.Empty <ReleaseEntry>();

            if (File.Exists(releaseFilePath))
            {
                previousReleases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8));
            }

            foreach (var file in toProcess)
            {
                this.Log().Info("Creating release package: " + file.FullName);

                var rp = new ReleasePackage(file.FullName);
                rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir);

                var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir);
                if (prev != null)
                {
                    var deltaBuilder = new DeltaPackageBuilder();

                    deltaBuilder.CreateDeltaPackage(prev, rp,
                                                    Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta")));
                }
            }

            foreach (var file in toProcess)
            {
                File.Delete(file.FullName);
            }

            var releaseEntries = allNuGetFiles.Select(x => ReleaseEntry.GenerateFromFile(x.FullName));

            ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath);

            var targetSetupExe    = Path.Combine(di.FullName, "Setup.exe");
            var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First();

            File.Copy(bootstrapperExe, targetSetupExe, true);
            var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName);

            try {
                var zip = File.ReadAllBytes(zipPath);

                IntPtr handle = NativeMethods.BeginUpdateResource(targetSetupExe, false);
                if (handle == IntPtr.Zero)
                {
                    throw new Win32Exception();
                }

                if (!NativeMethods.UpdateResource(handle, "DATA", new IntPtr(131), 0x0409, zip, zip.Length))
                {
                    throw new Win32Exception();
                }

                if (!NativeMethods.EndUpdateResource(handle, false))
                {
                    throw new Win32Exception();
                }
            } catch (Exception ex) {
                this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex);
            } finally {
                File.Delete(zipPath);
            }
        }
Exemple #22
0
        public static async Task SyncFromGitHub(string repoUrl, string token, DirectoryInfo releaseDirectoryInfo)
        {
            var repoUri   = new Uri(repoUrl);
            var userAgent = new ProductHeaderValue("SyncReleases", Assembly.GetExecutingAssembly().GetName().Version.ToString());

            var client = new GitHubClient(userAgent, repoUri);

            if (token != null)
            {
                client.Credentials = new Credentials(token);
            }

            var nwo      = nwoFromRepoUrl(repoUrl);
            var releases = (await client.Release.GetAll(nwo.Item1, nwo.Item2))
                           .OrderByDescending(x => x.PublishedAt)
                           .Take(5);

            await releases.ForEachAsync(async release => {
                // NB: Why do I have to double-fetch the release assets? It's already in GetAll
                var assets = await client.Release.GetAssets(nwo.Item1, nwo.Item2, release.Id);

                await assets
                .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase))
                .Where(x => {
                    var fi = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name));
                    return(!(fi.Exists && fi.Length == x.Size));
                })
                .ForEachAsync(async x => {
                    var target = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name));
                    if (target.Exists)
                    {
                        target.Delete();
                    }

                    await retryAsync(3, async() => {
                        var hc = new HttpClient();
                        var rq = new HttpRequestMessage(HttpMethod.Get, x.Url);
                        rq.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream"));
                        rq.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(userAgent.Name, userAgent.Version));
                        if (token != null)
                        {
                            rq.Headers.Add("Authorization", "Bearer " + token);
                        }

                        var resp = await hc.SendAsync(rq);
                        resp.EnsureSuccessStatusCode();

                        using (var from = await resp.Content.ReadAsStreamAsync())
                            using (var to = File.OpenWrite(target.FullName)) {
                                await from.CopyToAsync(to);
                            }
                    });
                });
            });

            var entries = releaseDirectoryInfo.GetFiles("*.nupkg")
                          .AsParallel()
                          .Select(x => ReleaseEntry.GenerateFromFile(x.FullName));

            ReleaseEntry.WriteReleaseFile(entries, Path.Combine(releaseDirectoryInfo.FullName, "RELEASES"));
        }
        static int Main(string[] args)
        {
            var command  = "";
            var target   = "";
            var appName  = default(string);
            var showHelp = false;

            var opts = new OptionSet()
            {
                { "c|command=", "One of 'check' or 'update' to return the latest update information, or to perform the update", v => command = v },
                { "t|update-target=", "The URL or directory to download updates from", v => target = v },
                { "n|app-name=", "(Optional) The name of the application, only necessary if updater.exe isn't in the install directory", v => appName = v },
                { "h|help", "Show this message and exit", v => showHelp = v != null },
            };

            opts.Parse(args);

            if (!new[] { "check", "update", "install", }.Any(x => command.ToLowerInvariant() == x))
            {
                Console.Error.WriteLine("Command must be either 'check' or 'update'");
                showHelp = true;
            }

            if (!Directory.Exists(target) && !File.Exists(target) && !isAUrl(target))
            {
                Console.Error.WriteLine("Target must be either a directory or a URL to check for updates");
                showHelp = true;
            }

            if (showHelp)
            {
                Console.WriteLine("\nSquirrel.Updater.exe - Check for updates or update an application");
                Console.WriteLine(@"Usage: Squirrel.Updater.exe [options]");

                Console.WriteLine("Options:");
                foreach (var v in opts)
                {
                    if (v.GetNames().Length != 2)
                    {
                        Console.WriteLine("  --{0} - {1}", v.GetNames()[0], v.Description);
                    }
                    else
                    {
                        Console.WriteLine("  -{0}/--{1} - {2}", v.GetNames()[0], v.GetNames()[1], v.Description);
                    }
                }

                return(0);
            }

            appName = appName ?? determineAppName();
            using (var mgr = new UpdateManager(target, appName, FrameworkVersion.Net40)) {
                if (command.ToLowerInvariant() == "check")
                {
                    var updateInfo = default(UpdateInfo);
                    try {
                        updateInfo = mgr.CheckForUpdate().First();

                        if (updateInfo.ReleasesToApply.Count > 0)
                        {
                            mgr.DownloadReleases(updateInfo.ReleasesToApply).First();
                        }
                    } catch (Exception ex) {
                        writeJsonForException(ex, "Failed to check for updates");
                        return(-1);
                    }

                    var releaseNotes = new Dictionary <ReleaseEntry, string>();

                    try {
                        releaseNotes = (updateInfo.ReleasesToApply.Count > 0) ? updateInfo.FetchReleaseNotes() : releaseNotes;
                    } catch (Exception ex) {
                        // TODO: Find a way to log this
                    }

                    Console.WriteLine(JsonConvert.SerializeObject(new {
                        UpdateInfo   = updateInfo,
                        ReleaseNotes = releaseNotes,
                    }));

                    return(0);
                }

                if (command.ToLowerInvariant() == "update")
                {
                    var result = default(ReleaseEntry);
                    try {
                        result = mgr.UpdateAppAsync().Result;
                    } catch (Exception ex) {
                        writeJsonForException(ex, "Failed to update application");
                        return(-1);
                    }

                    Console.WriteLine(JsonConvert.SerializeObject(result));
                    return(0);
                }

                if (command.ToLowerInvariant() == "install")
                {
                    var targetRelease = ReleaseEntry.GenerateFromFile(target);
                    mgr.ApplyReleases(UpdateInfo.Create(null, new[] { targetRelease }, Path.GetDirectoryName(target), FrameworkVersion.Net40)).First();
                    return(0);
                }
            }

            throw new Exception("How even did we get here?");
        }