mirror of
https://github.com/velopack/velopack.git
synced 2025-10-24 15:19:23 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,4 +37,5 @@ public class LinuxPackOptions : IPackOptions
|
||||
public string Categories { get; set; }
|
||||
|
||||
public string Compression { get; set; }
|
||||
public bool BuildMsi => false;
|
||||
}
|
||||
|
||||
@@ -35,4 +35,5 @@ public class OsxPackOptions : OsxBundleOptions, IPackOptions
|
||||
public string Channel { get; set; }
|
||||
|
||||
public string Exclude { get; set; }
|
||||
public bool BuildMsi => false;
|
||||
}
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Velopack.Core;
|
||||
|
||||
namespace Velopack.Packaging.Windows.Commands;
|
||||
namespace Velopack.Packaging.Windows.Commands;
|
||||
|
||||
public class WindowsReleasifyOptions : WindowsSigningOptions
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -9,4 +9,5 @@ public interface IPackOptions : INugetPackCommand, IPlatformOptions
|
||||
string Exclude { get; set; }
|
||||
bool NoPortable { get; set; }
|
||||
bool NoInst { get; set; }
|
||||
bool BuildMsi { get; }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
BIN
vendor/wix/Microsoft.Deployment.Resources.dll
vendored
Normal file
BIN
vendor/wix/Microsoft.Deployment.Resources.dll
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/Microsoft.Deployment.WindowsInstaller.dll
vendored
Normal file
BIN
vendor/wix/Microsoft.Deployment.WindowsInstaller.dll
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/WixNetFxExtension.dll
vendored
Normal file
BIN
vendor/wix/WixNetFxExtension.dll
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/candle.exe
vendored
Normal file
BIN
vendor/wix/candle.exe
vendored
Normal file
Binary file not shown.
18
vendor/wix/candle.exe.config
vendored
Normal file
18
vendor/wix/candle.exe.config
vendored
Normal 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
BIN
vendor/wix/darice.cub
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/light.exe
vendored
Normal file
BIN
vendor/wix/light.exe
vendored
Normal file
Binary file not shown.
18
vendor/wix/light.exe.config
vendored
Normal file
18
vendor/wix/light.exe.config
vendored
Normal 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
39
vendor/wix/template.wxs
vendored
Normal 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=""[#{{Id}}.exe]" --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
BIN
vendor/wix/wconsole.dll
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/winterop.dll
vendored
Normal file
BIN
vendor/wix/winterop.dll
vendored
Normal file
Binary file not shown.
BIN
vendor/wix/wix.dll
vendored
Normal file
BIN
vendor/wix/wix.dll
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user