Re-implemented msi installer based on Clowd.Squirrel

This re-implements the msi installer that was present inside of Clowd.Squirrel. This is hidden behind some hidden options.
This commit is contained in:
Kevin Bost
2025-01-26 21:52:58 -08:00
committed by Caelan
parent 55fbc17450
commit e9871e1656
29 changed files with 375 additions and 56 deletions

View File

@@ -20,6 +20,8 @@ namespace Velopack
Portable = 3,
/// <summary> An application installer archive. </summary>
Installer = 4,
/// <summary> A Windows Installer package (.msi) for the application.</summary>
Msi = 5,
}
/// <summary>

View File

@@ -94,6 +94,10 @@ public class PackTask : MSBuildAsyncTask
public string? Compression { get; set; }
public bool BuildMsi { get; set; }
public string? MsiVersionOverride { get; set; }
protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
{
//System.Diagnostics.Debugger.Launch();

View File

@@ -1,7 +1,6 @@
using System;
using System.IO;
using Riok.Mapperly.Abstractions;
using Velopack.Core;
using Velopack.Packaging;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;

View File

@@ -40,6 +40,16 @@ public static class DefaultName
throw new PlatformNotSupportedException("Platform not supported.");
}
public static string GetSuggestedMsiName(string id, string channel, RuntimeOs os)
{
var suffix = GetUniqueAssetSuffix(channel);
if (os == RuntimeOs.Windows)
return $"{id}{suffix}-DeploymentTool.msi";
else
throw new PlatformNotSupportedException("Platform not supported.");
}
private static string GetUniqueAssetSuffix(string channel)
{
return "-" + channel;

View File

@@ -37,4 +37,5 @@ public class LinuxPackOptions : IPackOptions
public string Categories { get; set; }
public string Compression { get; set; }
public bool BuildMsi => false;
}

View File

@@ -35,4 +35,5 @@ public class OsxPackOptions : OsxBundleOptions, IPackOptions
public string Channel { get; set; }
public string Exclude { get; set; }
public bool BuildMsi => false;
}

View File

@@ -1,6 +1,10 @@
using System.Runtime.Versioning;
using System.Globalization;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NuGet.Versioning;
using Velopack.Compression;
using Velopack.Core;
using Velopack.Core.Abstractions;
@@ -25,7 +29,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
.Select(x => x.FullName)
.ToArray();
SignFilesImpl(Options, progress, filesToSign);
SignFilesImpl(progress, filesToSign);
return Task.CompletedTask;
}
@@ -198,7 +202,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg);
progress(50);
Log.Debug("Signing Setup bundle");
SignFilesImpl(Options, CoreUtil.CreateProgressDelegate(progress, 50, 100), targetSetupExe);
SignFilesImpl(CoreUtil.CreateProgressDelegate(progress, 50, 100), targetSetupExe);
Log.Debug($"Setup bundle created '{Path.GetFileName(targetSetupExe)}'.");
progress(100);
@@ -237,6 +241,14 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
return dict;
}
protected override Task CreateMsiPackage(Action<int> progress, string setupExePath, string msiPath)
{
if (VelopackRuntimeInfo.IsWindows) {
CompileWixTemplateToMsi(progress, setupExePath, msiPath);
}
return Task.CompletedTask;
}
private void CreateExecutableStubForExe(string exeToCopy, string targetStubPath)
{
if (!File.Exists(exeToCopy)) {
@@ -253,12 +265,12 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
}
}
private void SignFilesImpl(WindowsSigningOptions options, Action<int> progress, params string[] filePaths)
private void SignFilesImpl(Action<int> progress, params string[] filePaths)
{
var signParams = options.SignParameters;
var signTemplate = options.SignTemplate;
var signParallel = options.SignParallel;
var trustedSignMetadataPath = options.AzureTrustedSignFile;
var signParams = Options.SignParameters;
var signTemplate = Options.SignTemplate;
var signParallel = Options.SignParallel;
var trustedSignMetadataPath = Options.AzureTrustedSignFile;
var helper = new CodeSign(Log);
if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate) && string.IsNullOrEmpty(trustedSignMetadataPath)) {
@@ -317,6 +329,83 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
// return dlibPath;
}
[SupportedOSPlatform("windows")]
private void CompileWixTemplateToMsi(Action<int> progress,
string setupExePath, string msiFilePath)
{
bool packageAs64Bit =
Options.TargetRuntime.Architecture is RuntimeCpu.x64 or RuntimeCpu.arm64;
Log.Info($"Compiling machine-wide msi deployment tool in {(packageAs64Bit ? "64-bit" : "32-bit")} mode");
var outputDirectory = Path.GetDirectoryName(setupExePath);
var setupName = Path.GetFileNameWithoutExtension(setupExePath);
var culture = CultureInfo.GetCultureInfo("en-US").TextInfo.ANSICodePage;
// WiX Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or
// periods(.). Every identifier must begin with either a letter or an underscore.
var wixId = Regex.Replace(Options.PackId, @"[^\w\.]", "_");
if (char.GetUnicodeCategory(wixId[0]) == UnicodeCategory.DecimalDigitNumber)
wixId = "_" + wixId;
Regex stacheRegex = new(@"\{\{(?<key>[^\}]+)\}\}", RegexOptions.Compiled);
var wxsFile = Path.Combine(outputDirectory, wixId + ".wxs");
var objFile = Path.Combine(outputDirectory, wixId + ".wixobj");
var msiVersion = Options.MsiVersionOverride;
if (string.IsNullOrWhiteSpace(msiVersion)) {
var parsedVersion = SemanticVersion.Parse(Options.PackVersion);
msiVersion = $"{parsedVersion.Major}.{parsedVersion.Minor}.{parsedVersion.Patch}.0";
}
try {
// apply dictionary to wsx template
var templateText = File.ReadAllText(HelperFile.WixTemplatePath);
var templateResult = stacheRegex.Replace(templateText, match => {
string key = match.Groups["key"].Value;
return key switch {
"Id" => wixId,
"Title" => GetEffectiveTitle(),
"Author" => GetEffectiveAuthors(),
"Version" => msiVersion,
"Summary" => GetEffectiveTitle(),
"Codepage" => $"{culture}",
"Platform" => packageAs64Bit ? "x64" : "x86",
"ProgramFilesFolder" => packageAs64Bit ? "ProgramFiles64Folder" : "ProgramFilesFolder",
"Win64YesNo" => packageAs64Bit ? "yes" : "no",
"SetupName" => setupName,
_ when key.StartsWith("IdAsGuid") => GuidUtil.CreateGuidFromHash($"{Options.PackId}:{key.Substring(8)}").ToString(),
_ => match.Value,
};
});
File.WriteAllText(wxsFile, templateResult, Encoding.UTF8);
// Candle reprocesses and compiles WiX source files into object files (.wixobj).
Log.Info("Compiling WiX Template (candle.exe)");
var candleCommand = $"{HelperFile.WixCandlePath} -nologo -ext WixNetFxExtension -out \"{objFile}\" \"{wxsFile}\"";
_ = Exe.RunHostedCommand(candleCommand);
progress(45);
// Light links and binds one or more .wixobj files and creates a Windows Installer database (.msi or .msm).
Log.Info("Linking WiX Template (light.exe)");
var lightCommand = $"{HelperFile.WixLightPath} -ext WixNetFxExtension -spdb -sval -out \"{msiFilePath}\" \"{objFile}\"";
_ = Exe.RunHostedCommand(lightCommand);
progress(90);
} finally {
IoUtil.DeleteFileOrDirectoryHard(wxsFile, throwOnFailure: false);
IoUtil.DeleteFileOrDirectoryHard(objFile, throwOnFailure: false);
}
progress(100);
}
protected override string[] GetMainExeSearchPaths(string packDirectory, string mainExeName)
{
return [

View File

@@ -23,4 +23,8 @@ public class WindowsPackOptions : WindowsReleasifyOptions, INugetPackCommand, IP
public bool NoInst { get; set; }
public string Shortcuts { get; set; }
public bool BuildMsi { get; set; }
public string MsiVersionOverride { get; set; }
}

View File

@@ -1,6 +1,4 @@
using Velopack.Core;
namespace Velopack.Packaging.Windows.Commands;
namespace Velopack.Packaging.Windows.Commands;
public class WindowsReleasifyOptions : WindowsSigningOptions
{

View File

@@ -152,7 +152,7 @@ public class ResourceEdit
var file = PEFile.FromBytes(File.ReadAllBytes(otherExeFile));
var image = PEImage.FromFile(file);
_resources = image.Resources;
_resources = image.Resources ?? new ResourceDirectory((uint) 0);
}
public void Commit()

View File

@@ -9,4 +9,5 @@ public interface IPackOptions : INugetPackCommand, IPlatformOptions
string Exclude { get; set; }
bool NoPortable { get; set; }
bool NoInst { get; set; }
bool BuildMsi { get; }
}

View File

@@ -69,7 +69,7 @@ public static class Exe
var stdout = IoUtil.Retry(() => File.ReadAllText(outputFile).Trim(), 10, 1000);
var result = (process.ExitCode, stdout, "", command);
ProcessFailedException.ThrowIfNonZero(result);
return result.Item2;
return stdout;
}
public static void RunHostedCommandNoWait(string command, string workDir = null)

View File

@@ -1,4 +1,5 @@
using System.Runtime.Versioning;
using System.Text;
using Microsoft.Extensions.Logging;
#if !DEBUG
@@ -70,6 +71,13 @@ public static class HelperFile
public static string StubExecutablePath => FindHelperFile("stub.exe");
[SupportedOSPlatform("windows")]
public static string WixTemplatePath => FindHelperFile("wix\\template.wxs");
[SupportedOSPlatform("windows")]
public static string WixCandlePath => FindHelperFile("wix\\candle.exe");
[SupportedOSPlatform("windows")]
public static string WixLightPath => FindHelperFile("wix\\light.exe");
[SupportedOSPlatform("windows")]
public static string SignToolPath => FindHelperFile("signing\\signtool.exe");
@@ -135,8 +143,14 @@ public static class HelperFile
files = files.Where(predicate);
var result = files.FirstOrDefault();
if (result == null && throwWhenNotFound)
throw new Exception($"HelperFile could not find '{toFind}'.");
if (result == null && throwWhenNotFound) {
StringBuilder msg = new();
msg.AppendLine($"HelperFile could not find '{toFind}'.");
msg.AppendLine("Search paths:");
foreach (var path in _searchPaths)
msg.AppendLine($" {Path.GetFullPath(path)}");
throw new Exception(msg.ToString());
}
return result;
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Security;
using System.Security;
using System.Text.RegularExpressions;
using Markdig;
using Microsoft.Extensions.Logging;
@@ -149,12 +148,13 @@ public abstract class PackageBuilder<T> : ICommand<T>
});
Task setupTask = null;
string setupExePath = null;
if (!Options.NoInst && TargetOs != RuntimeOs.Linux) {
setupTask = ctx.RunTask(
"Building setup package",
async (progress) => {
var suggestedName = DefaultName.GetSuggestedSetupName(packId, channel, TargetOs);
var path = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Installer);
var path = setupExePath = assetCache.MakeAssetPath(suggestedName, VelopackAssetType.Installer);
await CreateSetupPackage(progress, releasePath, packDirectory, path);
});
}
@@ -177,6 +177,16 @@ public abstract class PackageBuilder<T> : ICommand<T>
if (TargetOs != RuntimeOs.Linux && portableTask != null) await portableTask;
if (setupTask != null) await setupTask;
if (!Options.NoInst && Options.BuildMsi && TargetOs == RuntimeOs.Windows) {
await ctx.RunTask(
"Building MSI package",
async (progress) => {
var msiName = DefaultName.GetSuggestedMsiName(packId, channel, TargetOs);
var msiPath = assetCache.MakeAssetPath(msiName, VelopackAssetType.Msi);
await CreateMsiPackage(progress, setupExePath, msiPath);
});
}
await ctx.RunTask(
"Post-process steps",
(progress) => {
@@ -196,8 +206,8 @@ public abstract class PackageBuilder<T> : ICommand<T>
protected virtual string GenerateNuspecContent()
{
var packId = Options.PackId;
var packTitle = Options.PackTitle ?? Options.PackId;
var packAuthors = Options.PackAuthors ?? Options.PackId;
var packTitle = GetEffectiveTitle();
var packAuthors = GetEffectiveAuthors();
var packVersion = Options.PackVersion;
var releaseNotes = Options.ReleaseNotes;
var rid = Options.TargetRuntime;
@@ -240,8 +250,8 @@ public abstract class PackageBuilder<T> : ICommand<T>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>{packId}</id>
<title>{packTitle ?? packId}</title>
<description>{packTitle ?? packId}</description>
<title>{packTitle}</title>
<description>{packTitle}</description>
<authors>{packAuthors ?? packId}</authors>
<version>{packVersion}</version>
<channel>{Options.Channel}</channel>
@@ -277,6 +287,11 @@ public abstract class PackageBuilder<T> : ICommand<T>
return Task.CompletedTask;
}
protected virtual Task CreateMsiPackage(Action<int> progress, string setupExePath, string msiPath)
{
return Task.CompletedTask;
}
protected virtual async Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath)
{
var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage");
@@ -395,4 +410,8 @@ public abstract class PackageBuilder<T> : ICommand<T>
""";
File.WriteAllText(Path.Combine(relsDir, ".rels"), rels);
}
protected string GetEffectiveTitle() => Options.PackTitle ?? Options.PackId;
protected string GetEffectiveAuthors() => Options.PackAuthors ?? Options.PackId;
}

View File

@@ -20,6 +20,10 @@ public class WindowsPackCommand : PackCommand
public string Shortcuts { get; private set; }
public bool BuildMsi { get; private set; }
public string MsiVersionOverride { get; private set; }
public WindowsPackCommand()
: base("pack", "Creates a release from a folder containing application files.", RuntimeOs.Windows)
{
@@ -69,6 +73,17 @@ public class WindowsPackCommand : PackCommand
.SetArgumentHelpName("PATH");
this.AreMutuallyExclusive(signTemplate, signParams, azTrustedSign);
AddOption<bool>((v) => BuildMsi = v, "--msi")
.SetDescription("Compile a .msi machine-wide deployment tool.")
.SetHidden()
.SetArgumentHelpName("BITNESS");
AddOption<string>((v) => MsiVersionOverride = v, "--msiVersion")
.SetDescription("Override the product version for the generated msi.")
.SetArgumentHelpName("VERSION")
.SetHidden()
.MustBeValidMsiVersion();
}
}
}

View File

@@ -15,4 +15,10 @@
<PackageReference Include="System.Formats.Asn1" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Deployment.WindowsInstaller">
<HintPath>..\..\vendor\wix\Microsoft.Deployment.WindowsInstaller.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -2,17 +2,18 @@
using System.Globalization;
using System.Runtime.Versioning;
using System.Xml.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Win32;
using NuGet.Packaging;
using Velopack.Compression;
using Velopack.Core;
using Velopack.Packaging.Commands;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows.Commands;
using Velopack.Util;
using Velopack.Vpk;
using Velopack.Vpk.Logging;
using Velopack.Windows;
using static Azure.Core.HttpHeader;
namespace Velopack.Packaging.Tests;
@@ -26,7 +27,7 @@ public class WindowsPackTests
_output = output;
}
private WindowsPackCommandRunner GetPackRunner(ILogger logger)
private static WindowsPackCommandRunner GetPackRunner(ILogger logger)
{
var console = new BasicConsole(logger, new VelopackDefaults(false));
return new WindowsPackCommandRunner(logger, console);
@@ -199,6 +200,7 @@ public class WindowsPackTests
TargetRuntime = RID.Parse("win-x64"),
PackDirectory = tmpOutput,
Shortcuts = "Desktop,StartMenuRoot",
NoPortable = true
};
var runner = GetPackRunner(logger);
@@ -207,7 +209,7 @@ public class WindowsPackTests
var setupPath1 = Path.Combine(tmpReleaseDir, $"{id}-win-Setup.exe");
Assert.True(File.Exists(setupPath1));
RunNoCoverage(setupPath1, new[] { "--silent", "--installto", tmpInstallDir }, Environment.CurrentDirectory, logger);
RunNoCoverage(setupPath1, ["--silent", "--installto", tmpInstallDir], Environment.CurrentDirectory, logger);
var updatePath = Path.Combine(tmpInstallDir, "Update.exe");
Assert.True(File.Exists(updatePath));
@@ -244,17 +246,16 @@ public class WindowsPackTests
var date = DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
Assert.Equal(date, installDate.Trim('\0'));
var uninstOutput = RunNoCoverage(updatePath, new string[] { "--silent", "--uninstall" }, Environment.CurrentDirectory, logger);
var uninstOutput = RunNoCoverage(updatePath, ["--silent", "--uninstall"], Environment.CurrentDirectory, logger);
Assert.EndsWith(Environment.NewLine + "Y", uninstOutput); // this checks that the self-delete succeeded
Assert.False(File.Exists(startLnk));
Assert.False(File.Exists(desktopLnk));
Assert.False(File.Exists(appPath));
using (var key2 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.OpenSubKey(uninstallRegSubKey + "\\" + id, RegistryKeyPermissionCheck.ReadSubTree)) {
Assert.Null(key2);
}
using var key2 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.OpenSubKey(uninstallRegSubKey + "\\" + id, RegistryKeyPermissionCheck.ReadSubTree);
Assert.Null(key2);
}
[SkippableFact]
@@ -274,7 +275,7 @@ public class WindowsPackTests
var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe");
RunNoCoverage(
setupPath1,
new string[] { "--silent", "--installto", installDir },
["--silent", "--installto", installDir],
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
logger);
@@ -287,19 +288,19 @@ public class WindowsPackTests
var mvTo = Path.Combine(installDir, "packages", fileName);
File.Copy(mvFrom, mvTo);
RunCoveredDotnet(appPath, new string[] { "--autoupdate" }, installDir, logger, exitCode: null);
RunCoveredDotnet(appPath, ["--autoupdate"], installDir, logger, exitCode: null);
Thread.Sleep(3000); // update.exe runs in separate process
var chk1version = RunCoveredDotnet(appPath, new string[] { "version" }, installDir, logger);
var chk1version = RunCoveredDotnet(appPath, ["version"], installDir, logger);
Assert.EndsWith(Environment.NewLine + "2.0.0", chk1version);
}
[SkippableFact]
public void TestPackGeneratesValidDelta()
{
using var _1 = TempUtil.GetTempDirectory(out var releaseDir);
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
using var _1 = TempUtil.GetTempDirectory(out var releaseDir);
using var logger = _output.BuildLoggerFor<WindowsPackTests>();
string id = "SquirrelDeltaTest";
PackTestApp(id, "1.0.0", "version 1 test", releaseDir, logger);
@@ -338,7 +339,7 @@ public class WindowsPackTests
new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"),
OutputFile = output,
PatchFiles = new[] { new FileInfo(deltaPath) },
PatchFiles = [new FileInfo(deltaPath)],
}).GetAwaiterResult();
// are the packages the same?
@@ -437,7 +438,7 @@ public class WindowsPackTests
var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe");
RunNoCoverage(
setupPath1,
new string[] { "--silent", "--installto", installDir },
["--silent", "--installto", installDir],
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
logger);
@@ -451,11 +452,11 @@ public class WindowsPackTests
logger.Info("TEST: v1 installed");
// check app output
var chk1test = RunCoveredDotnet(appPath, new string[] { "test" }, installDir, logger);
var chk1test = RunCoveredDotnet(appPath, ["test"], installDir, logger);
Assert.EndsWith(Environment.NewLine + "version 1 test", chk1test);
var chk1version = RunCoveredDotnet(appPath, new string[] { "version" }, installDir, logger);
var chk1version = RunCoveredDotnet(appPath, ["version"], installDir, logger);
Assert.EndsWith(Environment.NewLine + "1.0.0", chk1version);
var chk1check = RunCoveredDotnet(appPath, new string[] { "check", releaseDir }, installDir, logger);
var chk1check = RunCoveredDotnet(appPath, ["check", releaseDir], installDir, logger);
Assert.EndsWith(Environment.NewLine + "no updates", chk1check);
logger.Info("TEST: v1 output verified");
@@ -463,7 +464,7 @@ public class WindowsPackTests
PackTestApp(id, "2.0.0", "version 2 test", releaseDir, logger);
// check can find v2 update
var chk2check = RunCoveredDotnet(appPath, new string[] { "check", releaseDir }, installDir, logger);
var chk2check = RunCoveredDotnet(appPath, ["check", releaseDir], installDir, logger);
Assert.EndsWith(Environment.NewLine + "update: 2.0.0", chk2check);
logger.Info("TEST: found v2 update");
@@ -476,17 +477,17 @@ public class WindowsPackTests
// perform full update, check that we get v3
// apply should fail if there's not an update downloaded
RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: -1);
RunCoveredDotnet(appPath, new string[] { "download", releaseDir }, installDir, logger);
RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: null);
RunCoveredDotnet(appPath, ["apply", releaseDir], installDir, logger, exitCode: -1);
RunCoveredDotnet(appPath, ["download", releaseDir], installDir, logger);
RunCoveredDotnet(appPath, ["apply", releaseDir], installDir, logger, exitCode: null);
logger.Info("TEST: v3 applied");
// check app output
var chk3test = RunCoveredDotnet(appPath, new string[] { "test" }, installDir, logger);
var chk3test = RunCoveredDotnet(appPath, ["test"], installDir, logger);
Assert.EndsWith(Environment.NewLine + "version 3 test", chk3test);
var chk3version = RunCoveredDotnet(appPath, new string[] { "version" }, installDir, logger);
var chk3version = RunCoveredDotnet(appPath, ["version"], installDir, logger);
Assert.EndsWith(Environment.NewLine + "3.0.0", chk3version);
var ch3check2 = RunCoveredDotnet(appPath, new string[] { "check", releaseDir }, installDir, logger);
var ch3check2 = RunCoveredDotnet(appPath, ["check", releaseDir], installDir, logger);
Assert.EndsWith(Environment.NewLine + "no updates", ch3check2);
logger.Info("TEST: v3 output verified");
@@ -504,7 +505,7 @@ public class WindowsPackTests
// uninstall
var updatePath = Path.Combine(installDir, "Update.exe");
RunNoCoverage(updatePath, new string[] { "--silent", "--uninstall" }, Environment.CurrentDirectory, logger);
RunNoCoverage(updatePath, ["--silent", "--uninstall"], Environment.CurrentDirectory, logger);
logger.Info("TEST: uninstalled / complete");
}
@@ -543,8 +544,8 @@ public class WindowsPackTests
using var _1 = TempUtil.GetTempDirectory(out var releaseDir);
PackTestApp("LegacyTestApp", "2.0.0", "hello!", releaseDir, logger);
RunNoCoverage(appExe, new string[] { "download", releaseDir }, currentDir, logger, exitCode: 0);
RunNoCoverage(appExe, new string[] { "apply", releaseDir }, currentDir, logger, exitCode: null);
RunNoCoverage(appExe, ["download", releaseDir], currentDir, logger, exitCode: 0);
RunNoCoverage(appExe, ["apply", releaseDir], currentDir, logger, exitCode: null);
logger.Info("TEST: " + DateTime.Now.ToLongTimeString());
@@ -555,7 +556,7 @@ public class WindowsPackTests
logger.Info("TEST: " + DateTime.Now.ToLongTimeString());
if (origDirName != "current") {
Assert.True(!Directory.Exists(currentDir));
Assert.False(Directory.Exists(currentDir));
currentDir = Path.Combine(rootDir, "current");
}
@@ -569,10 +570,90 @@ public class WindowsPackTests
// this is the file written by TestApp when it's detected the squirrel restart. if this is here, everything went smoothly.
Assert.True(File.Exists(Path.Combine(rootDir, "restarted")));
var chk3version = RunNoCoverage(appExe, new string[] { "version" }, currentDir, logger);
var chk3version = RunNoCoverage(appExe, ["version"], currentDir, logger);
Assert.EndsWith(Environment.NewLine + "2.0.0", chk3version);
}
[SkippableFact]
public async Task TestPackGeneratesMsi()
{
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
using var logger = _output.BuildLoggerFor<WindowsPackTests>();
using var _1 = TempUtil.GetTempDirectory(out var tmpOutput);
using var _2 = TempUtil.GetTempDirectory(out var tmpReleaseDir);
var exe = "testapp.exe";
var pdb = Path.ChangeExtension(exe, ".pdb");
var id = "Test.Squirrel-App";
var version = "1.2.3";
PathHelper.CopyRustAssetTo(exe, tmpOutput);
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
var options = new WindowsPackOptions {
EntryExecutableName = exe,
ReleaseDir = new DirectoryInfo(tmpReleaseDir),
PackId = id,
PackVersion = version,
TargetRuntime = RID.Parse("win-x64"),
PackDirectory = tmpOutput,
Shortcuts = "Desktop,StartMenuRoot",
BuildMsi = true
};
var runner = GetPackRunner(logger);
await runner.Run(options);
string msiPath = Path.Combine(tmpReleaseDir, $"{id}-win-DeploymentTool.msi");
Assert.True(File.Exists(msiPath));
using Database db = new Database(msiPath);
var msiVersion = db.ExecuteScalar("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductVersion'") as string;
Assert.Equal("1.2.3.0", msiVersion);
}
[SkippableFact]
public async Task TestPackGeneratesMsiWithSpecifiedVersion()
{
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
using var logger = _output.BuildLoggerFor<WindowsPackTests>();
using var _1 = TempUtil.GetTempDirectory(out var tmpOutput);
using var _2 = TempUtil.GetTempDirectory(out var tmpReleaseDir);
var exe = "testapp.exe";
var pdb = Path.ChangeExtension(exe, ".pdb");
var id = "Test.Squirrel-App";
var version = "1.0.0";
PathHelper.CopyRustAssetTo(exe, tmpOutput);
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
var options = new WindowsPackOptions {
EntryExecutableName = exe,
ReleaseDir = new DirectoryInfo(tmpReleaseDir),
PackId = id,
PackVersion = version,
TargetRuntime = RID.Parse("win-x64"),
PackDirectory = tmpOutput,
Shortcuts = "Desktop,StartMenuRoot",
BuildMsi = true,
MsiVersionOverride = "4.5.6.1"
};
var runner = GetPackRunner(logger);
await runner.Run(options);
string msiPath = Path.Combine(tmpReleaseDir, $"{id}-win-DeploymentTool.msi");
Assert.True(File.Exists(msiPath));
using Database db = new Database(msiPath);
var msiVersion = db.ExecuteScalar("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductVersion'") as string;
Assert.Equal("4.5.6.1", msiVersion);
}
private static string ReadFileWithRetry(string path, ILogger logger)
{
return IoUtil.Retry(
@@ -610,7 +691,7 @@ public class WindowsPackTests
// return RunImpl(psi, logger, exitCode);
//}
private string RunImpl(ProcessStartInfo psi, ILogger logger, int? exitCode = 0)
private static string RunImpl(ProcessStartInfo psi, ILogger logger, int? exitCode = 0)
{
//logger.Info($"TEST: Running {psi.FileName} {psi.ArgumentList.Aggregate((a, b) => $"{a} {b}")}");
//using var p = Process.Start(psi);
@@ -679,7 +760,7 @@ public class WindowsPackTests
}
}
private string RunCoveredDotnet(string exe, string[] args, string workingDir, ILogger logger, int? exitCode = 0)
private static string RunCoveredDotnet(string exe, string[] args, string workingDir, ILogger logger, int? exitCode = 0)
{
var outputfile = PathHelper.GetTestRootPath($"coverage.rundotnet.{RandomString(8)}.xml");
@@ -703,7 +784,7 @@ public class WindowsPackTests
return RunImpl(psi, logger, exitCode);
}
private static Random _random = new Random();
private static readonly Random _random = Random.Shared;
private static string RandomString(int length)
{
@@ -727,7 +808,7 @@ public class WindowsPackTests
return RunImpl(psi, logger, exitCode);
}
private void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger, bool addNewFile = false)
private static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger, bool addNewFile = false)
{
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");

Binary file not shown.

Binary file not shown.

BIN
vendor/wix/WixNetFxExtension.dll vendored Normal file

Binary file not shown.

BIN
vendor/wix/candle.exe vendored Normal file

Binary file not shown.

18
vendor/wix/candle.exe.config vendored Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
<copyright file="app.config" company="Outercurve Foundation">
Copyright (c) 2004, Outercurve Foundation.
This software is released under Microsoft Reciprocal License (MS-RL).
The license and further copyright text can be found in the file
LICENSE.TXT at the root directory of the distribution.
</copyright>
-->
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true"/>
</runtime>
</configuration>

BIN
vendor/wix/darice.cub vendored Normal file

Binary file not shown.

BIN
vendor/wix/light.exe vendored Normal file

Binary file not shown.

18
vendor/wix/light.exe.config vendored Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
<copyright file="app.config" company="Outercurve Foundation">
Copyright (c) 2004, Outercurve Foundation.
This software is released under Microsoft Reciprocal License (MS-RL).
The license and further copyright text can be found in the file
LICENSE.TXT at the root directory of the distribution.
</copyright>
-->
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true"/>
</runtime>
</configuration>

39
vendor/wix/template.wxs vendored Normal file
View File

@@ -0,0 +1,39 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<Product Id="*" Name="{{Title}} Deployment Tool" Language="1033" Codepage="{{Codepage}}" Version="{{Version}}" UpgradeCode="{{IdAsGuid1}}" Manufacturer="{{Author}}">
<Package Description="This package installs a deployment tool for {{Title}}. Not {{Title}} itself. {{Title}} is only installed if a user logs into the machine." InstallScope="perMachine" Comments="Comments" InstallerVersion="200" Compressed="yes" Platform="{{Platform}}"/>
<MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A later version of this product is already installed. Setup will now exit."/>
<Media Id="1" Cabinet="contents.cab" EmbedCab="yes" CompressionLevel="high"/>
<PropertyRef Id="NETFRAMEWORK45" />
<Condition Message="This application requires .NET Framework 4.5 or higher. Please install the latest .NET Framework then run this installer again.">
<![CDATA[Installed OR NETFRAMEWORK45]]>
</Condition>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="{{ProgramFilesFolder}}">
<Directory Id="APPLICATIONROOTDIRECTORY" Name="{{Title}} Deployment Tool" />
</Directory>
</Directory>
<DirectoryRef Id="APPLICATIONROOTDIRECTORY">
<Component Id="{{Id}}.exe" Guid="{{IdAsGuid2}}" Win64="{{Win64YesNo}}">
<File Id="{{Id}}.exe" Name="{{Id}}DeploymentTool.exe" Source="./{{SetupName}}.exe" KeyPath="yes"/>
</Component>
</DirectoryRef>
<DirectoryRef Id="TARGETDIR">
<Component Id="RegistryEntries" Guid="{{IdAsGuid3}}" Win64="{{Win64YesNo}}">
<RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Run">
<RegistryValue Type="expandable" Name="{{Id}}Deployment" Value="&quot;[#{{Id}}.exe]&quot; --checkInstall" />
</RegistryKey>
</Component>
</DirectoryRef>
<Feature Id="MainApplication" Title="Main Application" Level="1">
<ComponentRef Id="{{Id}}.exe" />
<ComponentRef Id="RegistryEntries" />
</Feature>
</Product>
</Wix>

BIN
vendor/wix/wconsole.dll vendored Normal file

Binary file not shown.

BIN
vendor/wix/winterop.dll vendored Normal file

Binary file not shown.

BIN
vendor/wix/wix.dll vendored Normal file

Binary file not shown.