Remove a bunch of releasify code from Update.exe

This commit is contained in:
Caelan Sayler
2021-11-04 21:19:20 +00:00
parent f091d5145b
commit 2a21a0e52a
11 changed files with 83 additions and 578 deletions

View File

@@ -37,7 +37,7 @@ Copy-Item "$In\Win32\WriteZipToSetup.exe" -Destination "$Out"
Copy-Item "$In\Win32\WriteZipToSetup.pdb" -Destination "$Out"
Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$Out" -Recurse
Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse
# Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse
Copy-Item "$PSScriptRoot\vendor\NuGet.exe" -Destination "$Out"
Copy-Item "$PSScriptRoot\vendor\rcedit.exe" -Destination "$Out"
Copy-Item "$PSScriptRoot\vendor\signtool.exe" -Destination "$Out"

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

@@ -7,8 +7,7 @@
<Description>Squirrel</Description>
<Title>Squirrel</Title>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>9</LangVersion>
<AssemblyName>Squirrel.Lib</AssemblyName>
<AssemblyName>SquirrelLib</AssemblyName>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,22 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<LangVersion>9</LangVersion>
<AssemblyName>Squirrel</AssemblyName>
<ApplicationIcon>..\..\squirrel.ico</ApplicationIcon>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<!-- Publish -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<CompressionInSingleFile>true</CompressionInSingleFile>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<ApplicationIcon>squirrel.ico</ApplicationIcon>
<AssemblyName>Squirrel</AssemblyName>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<CompressionInSingleFile>true</CompressionInSingleFile>
</PropertyGroup>
<ItemGroup>
<Content Include="squirrel.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.2.0.0" name="Squirrel.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

View File

@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Squirrel.Update
{
public static class CopStache
{
public static string Render(string template, Dictionary<string, string> identifiers)
{
var buf = new StringBuilder();
foreach (var line in template.Split('\n')) {
identifiers["RandomGuid"] = (Guid.NewGuid()).ToString();
foreach (var key in identifiers.Keys) {
buf.Replace("{{" + key + "}}", SecurityElement.Escape(identifiers[key]));
}
buf.AppendLine(line);
}
return buf.ToString();
}
}
}

View File

@@ -17,21 +17,15 @@ using Squirrel.Lib;
namespace Squirrel.Update
{
enum UpdateAction
{
Unset = 0, Install, Uninstall, Download, Update, Releasify, Shortcut,
Deshortcut, ProcessStart, UpdateSelf, CheckForUpdate
}
class Program : IEnableLogger
{
static StartupOption opt;
static IFullLogger Log => SquirrelLocator.Current.GetService<ILogManager>().GetLogger(typeof(Program));
public static int Main(string[] args)
{
var pg = new Program();
try {
return pg.main(args);
return main(args);
} catch (Exception ex) {
// NB: Normally this is a terrible idea but we want to make
// sure Setup.exe above us gets the nonzero error code
@@ -40,7 +34,7 @@ namespace Squirrel.Update
}
}
int main(string[] args)
static int main(string[] args)
{
try {
opt = new StartupOption(args);
@@ -68,9 +62,9 @@ namespace Squirrel.Update
}
}
int executeCommandLine(string[] args)
static int executeCommandLine(string[] args)
{
this.Log().Info("Starting Squirrel Updater: " + String.Join(" ", args));
Log.Info("Starting Squirrel Updater: " + String.Join(" ", args));
if (args.Any(x => x.StartsWith("/squirrel", StringComparison.OrdinalIgnoreCase))) {
// NB: We're marked as Squirrel-aware, but we don't want to do
@@ -84,7 +78,6 @@ namespace Squirrel.Update
}
switch (opt.updateAction) {
#if !MONO
case UpdateAction.Install:
var progressSource = new ProgressSource();
Install(opt.silentInstall, progressSource, Path.GetFullPath(opt.target)).Wait();
@@ -105,7 +98,7 @@ namespace Squirrel.Update
UpdateSelf().Wait();
break;
case UpdateAction.Shortcut:
Shortcut(opt.target, opt.shortcutArgs, opt.processStartArgs, opt.setupIcon, opt.onlyUpdateShortcuts);
Shortcut(opt.target, opt.shortcutArgs, opt.processStartArgs, opt.icon, opt.onlyUpdateShortcuts);
break;
case UpdateAction.Deshortcut:
Deshortcut(opt.target, opt.shortcutArgs);
@@ -113,22 +106,18 @@ namespace Squirrel.Update
case UpdateAction.ProcessStart:
ProcessStart(opt.processStart, opt.processStartArgs, opt.shouldWait);
break;
#endif
case UpdateAction.Releasify:
Releasify(opt.target, opt.releaseDir, opt.bootstrapperExe, opt.backgroundGif, opt.signingParameters, opt.baseUrl, opt.setupIcon, !opt.noMsi, opt.packageAs64Bit, opt.frameworkVersion, !opt.noDelta);
break;
}
this.Log().Info("Finished Squirrel Updater");
Log.Info("Finished Squirrel Updater");
return 0;
}
public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null)
static async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null)
{
sourceDirectory = sourceDirectory ?? AssemblyRuntimeInfo.BaseDirectory;
var releasesPath = Path.Combine(sourceDirectory, "RELEASES");
this.Log().Info("Starting install, writing to {0}", sourceDirectory);
Log.Info("Starting install, writing to {0}", sourceDirectory);
if (!AssemblyRuntimeInfo.IsSingleFile) {
// when doing a standard build, our executable has multiple files/dependencies.
@@ -138,7 +127,7 @@ namespace Squirrel.Update
}
if (!File.Exists(releasesPath)) {
this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath);
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));
@@ -150,42 +139,42 @@ namespace Squirrel.Update
.First().PackageName;
using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) {
this.Log().Info("About to install to: " + mgr.RootAppDirectory);
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);
Log.Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory);
mgr.KillAllExecutablesBelongingToPackage();
await Task.Delay(500);
await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory),
await Log.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),
Log.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(AssemblyRuntimeInfo.EntryExePath, updateTarget, true),
Log.ErrorIfThrows(() => File.Copy(AssemblyRuntimeInfo.EntryExePath, updateTarget, true),
"Failed to copy Update.exe to " + updateTarget);
await mgr.FullInstall(silentInstall, progressSource.Raise);
await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(),
await Log.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(),
"Failed to create uninstaller registry entry");
}
}
public async Task Update(string updateUrl, string appName = null)
static async Task Update(string updateUrl, string appName = null)
{
appName = appName ?? getAppNameFromDirectory();
this.Log().Info("Starting update, downloading from " + updateUrl);
Log.Info("Starting update, downloading from " + updateUrl);
using (var mgr = new UpdateManager(updateUrl, appName)) {
bool ignoreDeltaUpdates = false;
this.Log().Info("About to update to: " + mgr.RootAppDirectory);
Log.Info("About to update to: " + mgr.RootAppDirectory);
retry:
try {
@@ -199,24 +188,24 @@ namespace Squirrel.Update
await mgr.ApplyReleases(updateInfo, x => Console.WriteLine(UpdateManager.CalculateProgress(x, 30, 100)));
} catch (Exception ex) {
if (ignoreDeltaUpdates) {
this.Log().ErrorException("Really couldn't apply updates!", ex);
Log.ErrorException("Really couldn't apply updates!", ex);
throw;
}
this.Log().WarnException("Failed to apply updates, falling back to full updates", ex);
Log.WarnException("Failed to apply updates, falling back to full updates", ex);
ignoreDeltaUpdates = true;
goto retry;
}
var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe");
await this.ErrorIfThrows(() =>
await Log.ErrorIfThrows(() =>
mgr.CreateUninstallerRegistryEntry(),
"Failed to create uninstaller registry entry");
}
}
public async Task UpdateSelf()
static async Task UpdateSelf()
{
waitForParentToExit();
var src = AssemblyRuntimeInfo.EntryExePath;
@@ -229,11 +218,11 @@ namespace Squirrel.Update
});
}
public async Task<string> Download(string updateUrl, string appName = null)
static async Task<string> Download(string updateUrl, string appName = null)
{
appName = appName ?? getAppNameFromDirectory();
this.Log().Info("Fetching update information, downloading from " + updateUrl);
Log.Info("Fetching update information, downloading from " + updateUrl);
using (var mgr = new UpdateManager(updateUrl, appName)) {
var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, progress: x => Console.WriteLine(x / 3));
await mgr.DownloadReleases(updateInfo.ReleasesToApply, x => Console.WriteLine(33 + x / 3));
@@ -253,11 +242,11 @@ namespace Squirrel.Update
}
}
public async Task<string> CheckForUpdate(string updateUrl, string appName = null)
static async Task<string> CheckForUpdate(string updateUrl, string appName = null)
{
appName = appName ?? getAppNameFromDirectory();
this.Log().Info("Fetching update information, downloading from " + updateUrl);
Log.Info("Fetching update information, downloading from " + updateUrl);
using (var mgr = new UpdateManager(updateUrl, appName)) {
var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, progress: x => Console.WriteLine(x));
var releaseNotes = updateInfo.FetchReleaseNotes();
@@ -275,9 +264,9 @@ namespace Squirrel.Update
}
}
public async Task Uninstall(string appName = null)
static async Task Uninstall(string appName = null)
{
this.Log().Info("Starting uninstall for app: " + appName);
Log.Info("Starting uninstall for app: " + appName);
appName = appName ?? getAppNameFromDirectory();
using (var mgr = new UpdateManager("", appName)) {
@@ -294,172 +283,7 @@ namespace Squirrel.Update
}
}
public void Releasify(string package, string targetDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null, bool generateMsi = true, bool packageAs64Bit = false, string frameworkVersion = null, bool generateDeltas = true)
{
ensureConsole();
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");
bootstrapperExe = bootstrapperExe ?? Path.Combine(".", "Setup.exe");
if (!Directory.Exists(targetDir)) {
Directory.CreateDirectory(targetDir);
}
if (!File.Exists(bootstrapperExe)) {
bootstrapperExe = Path.Combine(
AssemblyRuntimeInfo.BaseDirectory,
"Setup.exe");
}
if (!File.Exists(bootstrapperExe)) {
this.Log().Error("Could not find Bootstrapper EXE. Please place it in the directory next to me, or use the bootstrapperExe command line option.");
throw new ArgumentException();
}
this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe);
// validate that the provided "frameworkVersion" is supported by Setup.exe
if (!String.IsNullOrWhiteSpace(frameworkVersion)) {
var chkFrameworkResult = Utility.InvokeProcessAsync(bootstrapperExe, "--checkFramework " + frameworkVersion, CancellationToken.None).Result;
if (chkFrameworkResult.Item1 != 0) {
this.Log().Error($"Unsupported FrameworkVersion: '{frameworkVersion}'. {chkFrameworkResult.Item2}");
throw new ArgumentException();
}
}
// copy input package to target output directory
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), contentsPostProcessHook: pkgPath => {
// create sub executable for all exe's in this package (except Squirrel!)
new DirectoryInfo(pkgPath).GetAllFilesRecursively()
.Where(x => x.Name.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
.Where(x => !x.Name.Contains("squirrel.exe", StringComparison.InvariantCultureIgnoreCase))
.Where(x => Utility.IsFileTopLevelInPackage(x.FullName, pkgPath))
.Where(x => Utility.ExecutableUsesWin32Subsystem(x.FullName))
.ForEachAsync(x => createExecutableStubForExe(x.FullName))
.Wait();
// copy myself into the package so Squirrel can also be updated
// how we find the lib dir is a huge hack here, but 'ReleasePackage' verifies there can only be one of these so it should be fine.
var re = new Regex(@"lib[\\\/][^\\\/]*[\\\/]?", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
var libDir = Directory
.EnumerateDirectories(pkgPath, "*", SearchOption.AllDirectories)
.Where(d => re.IsMatch(d))
.OrderBy(d => d.Length)
.FirstOrDefault();
File.Copy(getUpdateExePath(), Path.Combine(libDir, "Squirrel.exe"));
// sign all exe's in this package
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);
}, 1)
.Wait();
});
processed.Add(rp.ReleasePackageFile);
var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir);
if (prev != null && generateDeltas) {
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, signingOpts, setupIcon).Result;
var writeZipToSetup = Utility.FindHelperExecutable("WriteZipToSetup.exe");
try {
string arguments = $"\"{targetSetupExe}\" \"{zipPath}\"";
if (!String.IsNullOrWhiteSpace(frameworkVersion)) {
arguments += $" --set-required-framework \"{frameworkVersion}\"";
}
if (!String.IsNullOrWhiteSpace(backgroundGif)) {
arguments += $" --set-splash \"{Path.GetFullPath(backgroundGif)}\"";
}
var result = Utility.InvokeProcessAsync(writeZipToSetup, arguments, 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);
throw;
} 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), packageAs64Bit).Wait();
if (signingOpts != null) {
signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait();
}
}
}
public void Shortcut(string exeName, string shortcutArgs, string processStartArgs, string icon, bool onlyUpdate)
static void Shortcut(string exeName, string shortcutArgs, string processStartArgs, string icon, bool onlyUpdate)
{
if (String.IsNullOrWhiteSpace(exeName)) {
ShowHelp();
@@ -475,7 +299,7 @@ namespace Squirrel.Update
}
}
public void Deshortcut(string exeName, string shortcutArgs)
static void Deshortcut(string exeName, string shortcutArgs)
{
if (String.IsNullOrWhiteSpace(exeName)) {
ShowHelp();
@@ -491,7 +315,7 @@ namespace Squirrel.Update
}
}
public void ProcessStart(string exeName, string arguments, bool shouldWait)
static void ProcessStart(string exeName, string arguments, bool shouldWait)
{
if (String.IsNullOrWhiteSpace(exeName)) {
ShowHelp();
@@ -519,7 +343,7 @@ namespace Squirrel.Update
// Check for the EXE name they want
var targetExe = new FileInfo(Path.Combine(latestAppDir, exeName.Replace("%20", " ")));
this.Log().Info("Want to launch '{0}'", targetExe);
Log.Info("Want to launch '{0}'", targetExe);
// Check for path canonicalization attacks
if (!targetExe.FullName.StartsWith(latestAppDir, StringComparison.Ordinal)) {
@@ -527,27 +351,27 @@ namespace Squirrel.Update
}
if (!targetExe.Exists) {
this.Log().Error("File {0} doesn't exist in current release", targetExe);
Log.Error("File {0} doesn't exist in current release", targetExe);
throw new ArgumentException();
}
if (shouldWait) waitForParentToExit();
try {
this.Log().Info("About to launch: '{0}': {1}", targetExe.FullName, arguments ?? "");
Log.Info("About to launch: '{0}': {1}", targetExe.FullName, arguments ?? "");
Process.Start(new ProcessStartInfo(targetExe.FullName, arguments ?? "") { WorkingDirectory = Path.GetDirectoryName(targetExe.FullName) });
} catch (Exception ex) {
this.Log().ErrorException("Failed to start process", ex);
Log.ErrorException("Failed to start process", ex);
}
}
public void ShowHelp()
static void ShowHelp()
{
ensureConsole();
opt.WriteOptionDescriptions();
}
void waitForParentToExit()
static void waitForParentToExit()
{
// Grab a handle the parent process
var parentPid = NativeMethods.GetParentProcessId();
@@ -557,242 +381,16 @@ namespace Squirrel.Update
try {
handle = NativeMethods.OpenProcess(ProcessAccess.Synchronize, false, parentPid);
if (handle != IntPtr.Zero) {
this.Log().Info("About to wait for parent PID {0}", parentPid);
Log.Info("About to wait for parent PID {0}", parentPid);
NativeMethods.WaitForSingleObject(handle, 0xFFFFFFFF /*INFINITE*/);
} else {
this.Log().Info("Parent PID {0} no longer valid - ignoring", parentPid);
Log.Info("Parent PID {0} no longer valid - ignoring", parentPid);
}
} finally {
if (handle != IntPtr.Zero) NativeMethods.CloseHandle(handle);
}
}
string getUpdateExePath()
{
//if (selfContained) {
// var selfPath = Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "UpdateSelfContained.exe");
// if (!File.Exists(selfPath)) {
// this.Log().Error("Could not find UpdateSelfContained.exe in base directory. Am I published?");
// }
// return selfPath;
//}
return AssemblyRuntimeInfo.EntryExePath;
}
async Task<string> createSetupEmbeddedZip(string fullPackage, string releasesDir, string signingOpts, string setupIcon)
{
string tempPath;
this.Log().Info("Building embedded zip file for Setup.exe");
using (Utility.WithTempDirectory(out tempPath, null)) {
this.ErrorIfThrows(() => {
File.Copy(getUpdateExePath(), 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(setupIcon)) {
this.ErrorIfThrows(() => {
File.Copy(setupIcon, Path.Combine(tempPath, "setupIcon.ico"));
}, "Failed to write icon 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.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
.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;
}
}
static async Task signPEFile(string exePath, string signingOpts)
{
// Try to find SignTool.exe
var exe = @".\signtool.exe";
if (!File.Exists(exe)) {
exe = Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "signtool.exe");
// Run down PATH and hope for the best
if (!File.Exists(exe)) exe = "signtool.exe";
}
var processResult = await Utility.InvokeProcessAsync(exe,
String.Format("sign {0} \"{1}\"", signingOpts, exePath), CancellationToken.None);
if (processResult.Item1 != 0) {
var optsWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(signingOpts, "/p ********");
var msg = String.Format("Failed to sign, command invoked was: '{0} sign {1} {2}'",
exe, optsWithPasswordHidden, exePath);
throw new Exception(msg);
} else {
Console.WriteLine(processResult.Item2);
}
}
bool isPEFileSigned(string path)
{
#if MONO
return Path.GetExtension(path).Equals(".exe", StringComparison.OrdinalIgnoreCase);
#else
try {
return AuthenticodeTools.IsTrusted(path);
} catch (Exception ex) {
this.Log().ErrorException("Failed to determine signing status for " + path, ex);
return false;
}
#endif
}
async Task createExecutableStubForExe(string fullName)
{
var exe = Utility.FindHelperExecutable(@"StubExecutable.exe");
var target = Path.Combine(
Path.GetDirectoryName(fullName),
Path.GetFileNameWithoutExtension(fullName) + "_ExecutionStub.exe");
await Utility.CopyToAsync(exe, target);
await Utility.InvokeProcessAsync(
Utility.FindHelperExecutable("WriteZipToSetup.exe"),
String.Format("--copy-stub-resources \"{0}\" \"{1}\"", fullName, target),
CancellationToken.None);
}
static async Task setPEVersionInfoAndIcon(string exePath, IPackage package, string iconPath = null)
{
var realExePath = Path.GetFullPath(exePath);
var company = String.Join(",", package.Authors);
var verStrings = new Dictionary<string, string>() {
{ "CompanyName", company },
{ "LegalCopyright", package.Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + company },
{ "FileDescription", package.Summary ?? package.Description ?? "Installer for " + package.Id },
{ "ProductName", package.Description ?? package.Summary ?? package.Id },
};
var args = verStrings.Aggregate(new StringBuilder("\"" + realExePath + "\""), (acc, x) => { acc.AppendFormat(" --set-version-string \"{0}\" \"{1}\"", x.Key, x.Value); return acc; });
args.AppendFormat(" --set-file-version {0} --set-product-version {0}", package.Version.ToString());
if (iconPath != null) {
args.AppendFormat(" --set-icon \"{0}\"", Path.GetFullPath(iconPath));
}
// Try to find rcedit.exe
string exe = Utility.FindHelperExecutable("rcedit.exe");
var processResult = await Utility.InvokeProcessAsync(exe, args.ToString(), CancellationToken.None);
if (processResult.Item1 != 0) {
var msg = String.Format(
"Failed to modify resources, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
exe, args, processResult.Item2);
throw new Exception(msg);
} else {
Console.WriteLine(processResult.Item2);
}
}
static async Task createMsiPackage(string setupExe, IPackage package, bool packageAs64Bit)
{
var pathToWix = pathToWixTools();
var setupExeDir = Path.GetDirectoryName(setupExe);
var company = String.Join(",", package.Authors);
var culture = CultureInfo.GetCultureInfo(package.Language ?? "").TextInfo.ANSICodePage;
var templateText = File.ReadAllText(Path.Combine(pathToWix, "template.wxs"));
var templateData = new Dictionary<string, string> {
{ "Id", package.Id },
{ "Title", package.Title },
{ "Author", company },
{ "Version", Regex.Replace(package.Version.ToString(), @"-.*$", "") },
{ "Summary", package.Summary ?? package.Description ?? package.Id },
{ "Codepage", $"{culture}" },
{ "Platform", packageAs64Bit ? "x64" : "x86" },
{ "ProgramFilesFolder", packageAs64Bit ? "ProgramFiles64Folder" : "ProgramFilesFolder" },
{ "Win64YesNo", packageAs64Bit ? "yes" : "no" }
};
// NB: We need some GUIDs that are based on the package ID, but unique (i.e.
// "Unique but consistent").
for (int i = 1; i <= 10; i++) {
templateData[String.Format("IdAsGuid{0}", i)] = Utility.CreateGuidFromHash(String.Format("{0}:{1}", package.Id, i)).ToString();
}
var templateResult = CopStache.Render(templateText, templateData);
var wxsTarget = Path.Combine(setupExeDir, "Setup.wxs");
File.WriteAllText(wxsTarget, templateResult, Encoding.UTF8);
var candleParams = String.Format("-nologo -ext WixNetFxExtension -out \"{0}\" \"{1}\"", wxsTarget.Replace(".wxs", ".wixobj"), wxsTarget);
var processResult = await Utility.InvokeProcessAsync(
Path.Combine(pathToWix, "candle.exe"), candleParams, CancellationToken.None, setupExeDir);
if (processResult.Item1 != 0) {
var msg = String.Format(
"Failed to compile WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
"candle.exe", candleParams, processResult.Item2);
throw new Exception(msg);
}
var lightParams = String.Format("-ext WixNetFxExtension -sval -out \"{0}\" \"{1}\"", wxsTarget.Replace(".wxs", ".msi"), wxsTarget.Replace(".wxs", ".wixobj"));
processResult = await Utility.InvokeProcessAsync(
Path.Combine(pathToWix, "light.exe"), lightParams, CancellationToken.None, setupExeDir);
if (processResult.Item1 != 0) {
var msg = String.Format(
"Failed to link WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
"light.exe", lightParams, processResult.Item2);
throw new Exception(msg);
}
var toDelete = new[] {
wxsTarget,
wxsTarget.Replace(".wxs", ".wixobj"),
wxsTarget.Replace(".wxs", ".wixpdb"),
};
await Utility.ForEachAsync(toDelete, x => Utility.DeleteFileHarder(x));
}
static string pathToWixTools()
{
var ourPath = AssemblyRuntimeInfo.BaseDirectory;
// Same Directory? (i.e. released)
if (File.Exists(Path.Combine(ourPath, "candle.exe"))) {
return ourPath;
}
// Debug Mode (i.e. in vendor)
var debugPath = Path.Combine(ourPath, "..", "..", "..", "vendor", "wix", "candle.exe");
if (File.Exists(debugPath)) {
return Path.GetFullPath(debugPath);
}
throw new Exception("WiX tools can't be found");
}
static string getAppNameFromDirectory(string path = null)
{
path = path ?? AssemblyRuntimeInfo.BaseDirectory;

View File

@@ -3,28 +3,24 @@ using System;
namespace Squirrel.Update
{
enum UpdateAction
{
Unset = 0, Install, Uninstall, Download, Update, Shortcut,
Deshortcut, ProcessStart, UpdateSelf, CheckForUpdate
}
internal class StartupOption
{
private readonly OptionSet optionSet;
internal bool silentInstall { get; private set; } = false;
internal UpdateAction updateAction { get; private set; } = default(UpdateAction);
internal string target { get; private set; } = default(string);
internal string releaseDir { get; private set; } = default(string);
internal string bootstrapperExe { get; private set; } = default(string);
internal string backgroundGif { get; private set; } = default(string);
internal string signingParameters { get; private set; } = default(string);
internal string baseUrl { get; private set; } = default(string);
//internal string baseUrl { get; private set; } = default(string);
internal string processStart { get; private set; } = default(string);
internal string processStartArgs { get; private set; } = default(string);
internal string setupIcon { get; private set; } = default(string);
internal string icon { get; private set; } = default(string);
internal string shortcutArgs { get; private set; } = default(string);
internal string frameworkVersion { get; private set; }
internal bool shouldWait { get; private set; } = false;
internal bool noMsi { get; private set; } = (Environment.OSVersion.Platform != PlatformID.Win32NT); // NB: WiX doesn't work under Mono / Wine
internal bool packageAs64Bit { get; private set; } = false;
internal bool noDelta { get; private set; } = false;
internal bool onlyUpdateShortcuts { get; private set; } = false;
public StartupOption(string[] args)
@@ -44,7 +40,6 @@ namespace Squirrel.Update
{ "download=", "Download the releases specified by the URL and write new results to stdout as JSON", v => { updateAction = UpdateAction.Download; target = v; } },
{ "checkForUpdate=", "Check for one available update and writes new results to stdout as JSON", v => { updateAction = UpdateAction.CheckForUpdate; target = v; } },
{ "update=", "Update the application to the latest remote version specified by URL", v => { updateAction = UpdateAction.Update; target = v; } },
{ "releasify=", "Update or generate a releases directory with a given NuGet package", v => { updateAction = UpdateAction.Releasify; target = v; } },
{ "createShortcut=", "Create a shortcut for the given executable name", v => { updateAction = UpdateAction.Shortcut; target = v; } },
{ "removeShortcut=", "Remove a shortcut for the given executable name", v => { updateAction = UpdateAction.Deshortcut; target = v; } },
{ "updateSelf=", "Copy the currently executing Update.exe into the default location", v => { updateAction = UpdateAction.UpdateSelf; target = v; } },
@@ -53,29 +48,16 @@ namespace Squirrel.Update
"",
"Options:",
{ "h|?|help", "Display Help and exit", _ => {} },
{ "r=|releaseDir=", "Path to a release directory to use with releasify", v => releaseDir = v},
{ "bootstrapperExe=", "Path to the Setup.exe to use as a template", v => bootstrapperExe = v},
{ "g=|loadingGif=|splashImage=", "Path to an animated GIF to be displayed during installation", v => backgroundGif = v},
{ "i=|icon", "Path to an ICO file that will be used for icon shortcuts", v => icon = v},
{ "setupIcon=", "Path to an ICO file that will be used for the Setup executable's icon", v => setupIcon = v},
{ "n=|signWithParams=", "Sign the installer via SignTool.exe with the parameters given", v => signingParameters = v},
{ "s|silent", "Silent install", _ => silentInstall = true},
{ "b=|baseUrl=", "Provides a base URL to prefix the RELEASES file packages with", v => baseUrl = v, true},
//{ "b=|baseUrl=", "Provides a base URL to prefix the RELEASES file packages with", v => baseUrl = v, true},
{ "a=|process-start-args=", "Arguments that will be used when starting executable", v => processStartArgs = v, true},
{ "l=|shortcut-locations=", "Comma-separated string of shortcut locations, e.g. 'Desktop,StartMenu'", v => shortcutArgs = v},
{ "no-msi", "Don't generate an MSI package", v => noMsi = true},
{ "no-delta", "Don't generate delta packages to save time", v => noDelta = true},
{ "framework-version=", "Set the required .NET framework version, e.g. net461", v => frameworkVersion = v },
{ "msi-win64", "Mark the MSI as 64-bit, which is useful in Enterprise deployment scenarios", _ => packageAs64Bit = true},
{ "updateOnly", "Update shortcuts that already exist, rather than creating new ones", _ => onlyUpdateShortcuts = true},
};
opts.Parse(args);
// NB: setupIcon and icon are just aliases for compatibility
// reasons, because of a dumb breaking rename I made in 1.0.1
setupIcon = setupIcon ?? icon;
return opts;
}

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@@ -7,12 +6,14 @@
<LangVersion>9</LangVersion>
<RootNamespace>Squirrel.Update</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>squirrel.ico</ApplicationIcon>
<ApplicationIcon>..\..\squirrel.ico</ApplicationIcon>
<NoWarn>CA1416</NoWarn>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<PublishTrimmed>true</PublishTrimmed>
<!-- Publish -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB