mirror of
				https://github.com/velopack/velopack.git
				synced 2025-10-25 15:19:22 +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, |         Portable = 3, | ||||||
|         /// <summary> An application installer archive. </summary> |         /// <summary> An application installer archive. </summary> | ||||||
|         Installer = 4, |         Installer = 4, | ||||||
|  |         /// <summary> A Windows Installer package (.msi) for the application.</summary> | ||||||
|  |         Msi = 5, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -94,6 +94,10 @@ public class PackTask : MSBuildAsyncTask | |||||||
|      |      | ||||||
|     public string? Compression { get; set; } |     public string? Compression { get; set; } | ||||||
| 
 | 
 | ||||||
|  |     public bool BuildMsi { get; set; } | ||||||
|  | 
 | ||||||
|  |     public string? MsiVersionOverride { get; set; } | ||||||
|  | 
 | ||||||
|     protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken) |     protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         //System.Diagnostics.Debugger.Launch(); |         //System.Diagnostics.Debugger.Launch(); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| using System.IO; | using System.IO; | ||||||
| using Riok.Mapperly.Abstractions; | using Riok.Mapperly.Abstractions; | ||||||
| using Velopack.Core; |  | ||||||
| using Velopack.Packaging; | using Velopack.Packaging; | ||||||
| using Velopack.Packaging.Unix.Commands; | using Velopack.Packaging.Unix.Commands; | ||||||
| using Velopack.Packaging.Windows.Commands; | using Velopack.Packaging.Windows.Commands; | ||||||
|   | |||||||
| @@ -40,6 +40,16 @@ public static class DefaultName | |||||||
|             throw new PlatformNotSupportedException("Platform not supported."); |             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) |     private static string GetUniqueAssetSuffix(string channel) | ||||||
|     { |     { | ||||||
|         return "-" + channel; |         return "-" + channel; | ||||||
|   | |||||||
| @@ -37,4 +37,5 @@ public class LinuxPackOptions : IPackOptions | |||||||
|     public string Categories { get; set; } |     public string Categories { get; set; } | ||||||
|      |      | ||||||
|     public string Compression { 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 Channel { get; set; } | ||||||
| 
 | 
 | ||||||
|     public string Exclude { 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 System.Text.RegularExpressions; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|  | using Microsoft.Extensions.Options; | ||||||
|  | using NuGet.Versioning; | ||||||
| using Velopack.Compression; | using Velopack.Compression; | ||||||
| using Velopack.Core; | using Velopack.Core; | ||||||
| using Velopack.Core.Abstractions; | using Velopack.Core.Abstractions; | ||||||
| @@ -25,7 +29,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions> | |||||||
|             .Select(x => x.FullName) |             .Select(x => x.FullName) | ||||||
|             .ToArray(); |             .ToArray(); | ||||||
| 
 | 
 | ||||||
|         SignFilesImpl(Options, progress, filesToSign); |         SignFilesImpl(progress, filesToSign); | ||||||
| 
 | 
 | ||||||
|         return Task.CompletedTask; |         return Task.CompletedTask; | ||||||
|     } |     } | ||||||
| @@ -198,7 +202,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions> | |||||||
|         SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg); |         SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg); | ||||||
|         progress(50); |         progress(50); | ||||||
|         Log.Debug("Signing Setup bundle"); |         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)}'."); |         Log.Debug($"Setup bundle created '{Path.GetFileName(targetSetupExe)}'."); | ||||||
|         progress(100); |         progress(100); | ||||||
| 
 | 
 | ||||||
| @@ -237,6 +241,14 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions> | |||||||
|         return dict; |         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) |     private void CreateExecutableStubForExe(string exeToCopy, string targetStubPath) | ||||||
|     { |     { | ||||||
|         if (!File.Exists(exeToCopy)) { |         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 signParams = Options.SignParameters; | ||||||
|         var signTemplate = options.SignTemplate; |         var signTemplate = Options.SignTemplate; | ||||||
|         var signParallel = options.SignParallel; |         var signParallel = Options.SignParallel; | ||||||
|         var trustedSignMetadataPath = options.AzureTrustedSignFile; |         var trustedSignMetadataPath = Options.AzureTrustedSignFile; | ||||||
|         var helper = new CodeSign(Log); |         var helper = new CodeSign(Log); | ||||||
| 
 | 
 | ||||||
|         if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate) && string.IsNullOrEmpty(trustedSignMetadataPath)) { |         if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate) && string.IsNullOrEmpty(trustedSignMetadataPath)) { | ||||||
| @@ -317,6 +329,83 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions> | |||||||
|         // return dlibPath; |         // 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) |     protected override string[] GetMainExeSearchPaths(string packDirectory, string mainExeName) | ||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|   | |||||||
| @@ -23,4 +23,8 @@ public class WindowsPackOptions : WindowsReleasifyOptions, INugetPackCommand, IP | |||||||
|     public bool NoInst { get; set; } |     public bool NoInst { get; set; } | ||||||
| 
 | 
 | ||||||
|     public string Shortcuts { 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 | public class WindowsReleasifyOptions : WindowsSigningOptions | ||||||
| { | { | ||||||
|   | |||||||
| @@ -152,7 +152,7 @@ public class ResourceEdit | |||||||
| 
 | 
 | ||||||
|         var file = PEFile.FromBytes(File.ReadAllBytes(otherExeFile)); |         var file = PEFile.FromBytes(File.ReadAllBytes(otherExeFile)); | ||||||
|         var image = PEImage.FromFile(file); |         var image = PEImage.FromFile(file); | ||||||
|         _resources = image.Resources; |         _resources = image.Resources ?? new ResourceDirectory((uint) 0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void Commit() |     public void Commit() | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ public interface IPackOptions : INugetPackCommand, IPlatformOptions | |||||||
|     string Exclude { get; set; } |     string Exclude { get; set; } | ||||||
|     bool NoPortable { get; set; } |     bool NoPortable { get; set; } | ||||||
|     bool NoInst { 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 stdout = IoUtil.Retry(() => File.ReadAllText(outputFile).Trim(), 10, 1000); | ||||||
|         var result = (process.ExitCode, stdout, "", command); |         var result = (process.ExitCode, stdout, "", command); | ||||||
|         ProcessFailedException.ThrowIfNonZero(result); |         ProcessFailedException.ThrowIfNonZero(result); | ||||||
|         return result.Item2; |         return stdout; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void RunHostedCommandNoWait(string command, string workDir = null) |     public static void RunHostedCommandNoWait(string command, string workDir = null) | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.Runtime.Versioning; | using System.Runtime.Versioning; | ||||||
|  | using System.Text; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| 
 | 
 | ||||||
| #if !DEBUG | #if !DEBUG | ||||||
| @@ -70,6 +71,13 @@ public static class HelperFile | |||||||
| 
 | 
 | ||||||
|     public static string StubExecutablePath => FindHelperFile("stub.exe"); |     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")] |     [SupportedOSPlatform("windows")] | ||||||
|     public static string SignToolPath => FindHelperFile("signing\\signtool.exe"); |     public static string SignToolPath => FindHelperFile("signing\\signtool.exe"); | ||||||
| 
 | 
 | ||||||
| @@ -135,8 +143,14 @@ public static class HelperFile | |||||||
|             files = files.Where(predicate); |             files = files.Where(predicate); | ||||||
| 
 | 
 | ||||||
|         var result = files.FirstOrDefault(); |         var result = files.FirstOrDefault(); | ||||||
|         if (result == null && throwWhenNotFound) |         if (result == null && throwWhenNotFound) { | ||||||
|             throw new Exception($"HelperFile could not find '{toFind}'."); |             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; |         return result; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| using System.Collections.Concurrent; | using System.Security; | ||||||
| using System.Security; |  | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
| using Markdig; | using Markdig; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| @@ -149,12 +148,13 @@ public abstract class PackageBuilder<T> : ICommand<T> | |||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                 Task setupTask = null; |                 Task setupTask = null; | ||||||
|  |                 string setupExePath = null; | ||||||
|                 if (!Options.NoInst && TargetOs != RuntimeOs.Linux) { |                 if (!Options.NoInst && TargetOs != RuntimeOs.Linux) { | ||||||
|                     setupTask = ctx.RunTask( |                     setupTask = ctx.RunTask( | ||||||
|                         "Building setup package", |                         "Building setup package", | ||||||
|                         async (progress) => { |                         async (progress) => { | ||||||
|                             var suggestedName = DefaultName.GetSuggestedSetupName(packId, channel, TargetOs); |                             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); |                             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 (TargetOs != RuntimeOs.Linux && portableTask != null) await portableTask; | ||||||
|                 if (setupTask != null) await setupTask; |                 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( |                 await ctx.RunTask( | ||||||
|                     "Post-process steps", |                     "Post-process steps", | ||||||
|                     (progress) => { |                     (progress) => { | ||||||
| @@ -196,8 +206,8 @@ public abstract class PackageBuilder<T> : ICommand<T> | |||||||
|     protected virtual string GenerateNuspecContent() |     protected virtual string GenerateNuspecContent() | ||||||
|     { |     { | ||||||
|         var packId = Options.PackId; |         var packId = Options.PackId; | ||||||
|         var packTitle = Options.PackTitle ?? Options.PackId; |         var packTitle = GetEffectiveTitle(); | ||||||
|         var packAuthors = Options.PackAuthors ?? Options.PackId; |         var packAuthors = GetEffectiveAuthors(); | ||||||
|         var packVersion = Options.PackVersion; |         var packVersion = Options.PackVersion; | ||||||
|         var releaseNotes = Options.ReleaseNotes; |         var releaseNotes = Options.ReleaseNotes; | ||||||
|         var rid = Options.TargetRuntime; |         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"> |             <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | ||||||
|             <metadata> |             <metadata> | ||||||
|             <id>{packId}</id> |             <id>{packId}</id> | ||||||
|             <title>{packTitle ?? packId}</title> |             <title>{packTitle}</title> | ||||||
|             <description>{packTitle ?? packId}</description> |             <description>{packTitle}</description> | ||||||
|             <authors>{packAuthors ?? packId}</authors> |             <authors>{packAuthors ?? packId}</authors> | ||||||
|             <version>{packVersion}</version> |             <version>{packVersion}</version> | ||||||
|             <channel>{Options.Channel}</channel> |             <channel>{Options.Channel}</channel> | ||||||
| @@ -277,6 +287,11 @@ public abstract class PackageBuilder<T> : ICommand<T> | |||||||
|         return Task.CompletedTask; |         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) |     protected virtual async Task CreateReleasePackage(Action<int> progress, string packDir, string outputPath) | ||||||
|     { |     { | ||||||
|         var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage"); |         var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage"); | ||||||
| @@ -395,4 +410,8 @@ public abstract class PackageBuilder<T> : ICommand<T> | |||||||
|             """;
 |             """;
 | ||||||
|         File.WriteAllText(Path.Combine(relsDir, ".rels"), rels); |         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 string Shortcuts { get; private set; } | ||||||
| 
 | 
 | ||||||
|  |     public bool BuildMsi { get; private set; } | ||||||
|  | 
 | ||||||
|  |     public string MsiVersionOverride { get; private set; } | ||||||
|  | 
 | ||||||
|     public WindowsPackCommand() |     public WindowsPackCommand() | ||||||
|         : base("pack", "Creates a release from a folder containing application files.", RuntimeOs.Windows) |         : base("pack", "Creates a release from a folder containing application files.", RuntimeOs.Windows) | ||||||
|     { |     { | ||||||
| @@ -69,6 +73,17 @@ public class WindowsPackCommand : PackCommand | |||||||
|                 .SetArgumentHelpName("PATH"); |                 .SetArgumentHelpName("PATH"); | ||||||
| 
 | 
 | ||||||
|             this.AreMutuallyExclusive(signTemplate, signParams, azTrustedSign); |             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" /> |     <PackageReference Include="System.Formats.Asn1" Version="9.0.1" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <Reference Include="Microsoft.Deployment.WindowsInstaller"> | ||||||
|  |       <HintPath>..\..\vendor\wix\Microsoft.Deployment.WindowsInstaller.dll</HintPath> | ||||||
|  |     </Reference> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -2,17 +2,18 @@ | |||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.Runtime.Versioning; | using System.Runtime.Versioning; | ||||||
| using System.Xml.Linq; | using System.Xml.Linq; | ||||||
|  | using Microsoft.Deployment.WindowsInstaller; | ||||||
| using Microsoft.Win32; | using Microsoft.Win32; | ||||||
| using NuGet.Packaging; | using NuGet.Packaging; | ||||||
| using Velopack.Compression; | using Velopack.Compression; | ||||||
| using Velopack.Core; | using Velopack.Core; | ||||||
| using Velopack.Packaging.Commands; | using Velopack.Packaging.Commands; | ||||||
| using Velopack.Packaging.Exceptions; |  | ||||||
| using Velopack.Packaging.Windows.Commands; | using Velopack.Packaging.Windows.Commands; | ||||||
| using Velopack.Util; | using Velopack.Util; | ||||||
| using Velopack.Vpk; | using Velopack.Vpk; | ||||||
| using Velopack.Vpk.Logging; | using Velopack.Vpk.Logging; | ||||||
| using Velopack.Windows; | using Velopack.Windows; | ||||||
|  | using static Azure.Core.HttpHeader; | ||||||
| 
 | 
 | ||||||
| namespace Velopack.Packaging.Tests; | namespace Velopack.Packaging.Tests; | ||||||
| 
 | 
 | ||||||
| @@ -26,7 +27,7 @@ public class WindowsPackTests | |||||||
|         _output = output; |         _output = output; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private WindowsPackCommandRunner GetPackRunner(ILogger logger) |     private static WindowsPackCommandRunner GetPackRunner(ILogger logger) | ||||||
|     { |     { | ||||||
|         var console = new BasicConsole(logger, new VelopackDefaults(false)); |         var console = new BasicConsole(logger, new VelopackDefaults(false)); | ||||||
|         return new WindowsPackCommandRunner(logger, console); |         return new WindowsPackCommandRunner(logger, console); | ||||||
| @@ -199,6 +200,7 @@ public class WindowsPackTests | |||||||
|             TargetRuntime = RID.Parse("win-x64"), |             TargetRuntime = RID.Parse("win-x64"), | ||||||
|             PackDirectory = tmpOutput, |             PackDirectory = tmpOutput, | ||||||
|             Shortcuts = "Desktop,StartMenuRoot", |             Shortcuts = "Desktop,StartMenuRoot", | ||||||
|  |             NoPortable = true | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         var runner = GetPackRunner(logger); |         var runner = GetPackRunner(logger); | ||||||
| @@ -207,7 +209,7 @@ public class WindowsPackTests | |||||||
|         var setupPath1 = Path.Combine(tmpReleaseDir, $"{id}-win-Setup.exe"); |         var setupPath1 = Path.Combine(tmpReleaseDir, $"{id}-win-Setup.exe"); | ||||||
|         Assert.True(File.Exists(setupPath1)); |         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"); |         var updatePath = Path.Combine(tmpInstallDir, "Update.exe"); | ||||||
|         Assert.True(File.Exists(updatePath)); |         Assert.True(File.Exists(updatePath)); | ||||||
| @@ -244,17 +246,16 @@ public class WindowsPackTests | |||||||
|         var date = DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); |         var date = DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); | ||||||
|         Assert.Equal(date, installDate.Trim('\0')); |         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.EndsWith(Environment.NewLine + "Y", uninstOutput); // this checks that the self-delete succeeded | ||||||
| 
 | 
 | ||||||
|         Assert.False(File.Exists(startLnk)); |         Assert.False(File.Exists(startLnk)); | ||||||
|         Assert.False(File.Exists(desktopLnk)); |         Assert.False(File.Exists(desktopLnk)); | ||||||
|         Assert.False(File.Exists(appPath)); |         Assert.False(File.Exists(appPath)); | ||||||
| 
 | 
 | ||||||
|         using (var key2 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default) |         using var key2 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default) | ||||||
|                    .OpenSubKey(uninstallRegSubKey + "\\" + id, RegistryKeyPermissionCheck.ReadSubTree)) { |                    .OpenSubKey(uninstallRegSubKey + "\\" + id, RegistryKeyPermissionCheck.ReadSubTree); | ||||||
|             Assert.Null(key2); |         Assert.Null(key2); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [SkippableFact] |     [SkippableFact] | ||||||
| @@ -274,7 +275,7 @@ public class WindowsPackTests | |||||||
|         var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe"); |         var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe"); | ||||||
|         RunNoCoverage( |         RunNoCoverage( | ||||||
|             setupPath1, |             setupPath1, | ||||||
|             new string[] { "--silent", "--installto", installDir }, |             ["--silent", "--installto", installDir], | ||||||
|             Environment.GetFolderPath(Environment.SpecialFolder.Desktop), |             Environment.GetFolderPath(Environment.SpecialFolder.Desktop), | ||||||
|             logger); |             logger); | ||||||
| 
 | 
 | ||||||
| @@ -287,19 +288,19 @@ public class WindowsPackTests | |||||||
|         var mvTo = Path.Combine(installDir, "packages", fileName); |         var mvTo = Path.Combine(installDir, "packages", fileName); | ||||||
|         File.Copy(mvFrom, mvTo); |         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 |         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); |         Assert.EndsWith(Environment.NewLine + "2.0.0", chk1version); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [SkippableFact] |     [SkippableFact] | ||||||
|     public void TestPackGeneratesValidDelta() |     public void TestPackGeneratesValidDelta() | ||||||
|     { |     { | ||||||
|         using var _1 = TempUtil.GetTempDirectory(out var releaseDir); |  | ||||||
|         Skip.IfNot(VelopackRuntimeInfo.IsWindows); |         Skip.IfNot(VelopackRuntimeInfo.IsWindows); | ||||||
|  |         using var _1 = TempUtil.GetTempDirectory(out var releaseDir); | ||||||
|         using var logger = _output.BuildLoggerFor<WindowsPackTests>(); |         using var logger = _output.BuildLoggerFor<WindowsPackTests>(); | ||||||
|         string id = "SquirrelDeltaTest"; |         string id = "SquirrelDeltaTest"; | ||||||
|         PackTestApp(id, "1.0.0", "version 1 test", releaseDir, logger); |         PackTestApp(id, "1.0.0", "version 1 test", releaseDir, logger); | ||||||
| @@ -338,7 +339,7 @@ public class WindowsPackTests | |||||||
|             new DeltaPatchOptions { |             new DeltaPatchOptions { | ||||||
|                 BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"), |                 BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"), | ||||||
|                 OutputFile = output, |                 OutputFile = output, | ||||||
|                 PatchFiles = new[] { new FileInfo(deltaPath) }, |                 PatchFiles = [new FileInfo(deltaPath)], | ||||||
|             }).GetAwaiterResult(); |             }).GetAwaiterResult(); | ||||||
| 
 | 
 | ||||||
|         // are the packages the same? |         // are the packages the same? | ||||||
| @@ -437,7 +438,7 @@ public class WindowsPackTests | |||||||
|         var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe"); |         var setupPath1 = Path.Combine(releaseDir, $"{id}-win-Setup.exe"); | ||||||
|         RunNoCoverage( |         RunNoCoverage( | ||||||
|             setupPath1, |             setupPath1, | ||||||
|             new string[] { "--silent", "--installto", installDir }, |             ["--silent", "--installto", installDir], | ||||||
|             Environment.GetFolderPath(Environment.SpecialFolder.Desktop), |             Environment.GetFolderPath(Environment.SpecialFolder.Desktop), | ||||||
|             logger); |             logger); | ||||||
| 
 | 
 | ||||||
| @@ -451,11 +452,11 @@ public class WindowsPackTests | |||||||
|         logger.Info("TEST: v1 installed"); |         logger.Info("TEST: v1 installed"); | ||||||
| 
 | 
 | ||||||
|         // check app output |         // 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); |         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); |         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); |         Assert.EndsWith(Environment.NewLine + "no updates", chk1check); | ||||||
|         logger.Info("TEST: v1 output verified"); |         logger.Info("TEST: v1 output verified"); | ||||||
| 
 | 
 | ||||||
| @@ -463,7 +464,7 @@ public class WindowsPackTests | |||||||
|         PackTestApp(id, "2.0.0", "version 2 test", releaseDir, logger); |         PackTestApp(id, "2.0.0", "version 2 test", releaseDir, logger); | ||||||
| 
 | 
 | ||||||
|         // check can find v2 update |         // 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); |         Assert.EndsWith(Environment.NewLine + "update: 2.0.0", chk2check); | ||||||
|         logger.Info("TEST: found v2 update"); |         logger.Info("TEST: found v2 update"); | ||||||
| 
 | 
 | ||||||
| @@ -476,17 +477,17 @@ public class WindowsPackTests | |||||||
| 
 | 
 | ||||||
|         // perform full update, check that we get v3 |         // perform full update, check that we get v3 | ||||||
|         // apply should fail if there's not an update downloaded |         // apply should fail if there's not an update downloaded | ||||||
|         RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: -1); |         RunCoveredDotnet(appPath, ["apply", releaseDir], installDir, logger, exitCode: -1); | ||||||
|         RunCoveredDotnet(appPath, new string[] { "download", releaseDir }, installDir, logger); |         RunCoveredDotnet(appPath, ["download", releaseDir], installDir, logger); | ||||||
|         RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: null); |         RunCoveredDotnet(appPath, ["apply", releaseDir], installDir, logger, exitCode: null); | ||||||
|         logger.Info("TEST: v3 applied"); |         logger.Info("TEST: v3 applied"); | ||||||
| 
 | 
 | ||||||
|         // check app output |         // 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); |         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); |         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); |         Assert.EndsWith(Environment.NewLine + "no updates", ch3check2); | ||||||
|         logger.Info("TEST: v3 output verified"); |         logger.Info("TEST: v3 output verified"); | ||||||
| 
 | 
 | ||||||
| @@ -504,7 +505,7 @@ public class WindowsPackTests | |||||||
| 
 | 
 | ||||||
|         // uninstall |         // uninstall | ||||||
|         var updatePath = Path.Combine(installDir, "Update.exe"); |         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"); |         logger.Info("TEST: uninstalled / complete"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -543,8 +544,8 @@ public class WindowsPackTests | |||||||
|         using var _1 = TempUtil.GetTempDirectory(out var releaseDir); |         using var _1 = TempUtil.GetTempDirectory(out var releaseDir); | ||||||
|         PackTestApp("LegacyTestApp", "2.0.0", "hello!", releaseDir, logger); |         PackTestApp("LegacyTestApp", "2.0.0", "hello!", releaseDir, logger); | ||||||
| 
 | 
 | ||||||
|         RunNoCoverage(appExe, new string[] { "download", releaseDir }, currentDir, logger, exitCode: 0); |         RunNoCoverage(appExe, ["download", releaseDir], currentDir, logger, exitCode: 0); | ||||||
|         RunNoCoverage(appExe, new string[] { "apply", releaseDir }, currentDir, logger, exitCode: null); |         RunNoCoverage(appExe, ["apply", releaseDir], currentDir, logger, exitCode: null); | ||||||
| 
 | 
 | ||||||
|         logger.Info("TEST: " + DateTime.Now.ToLongTimeString()); |         logger.Info("TEST: " + DateTime.Now.ToLongTimeString()); | ||||||
| 
 | 
 | ||||||
| @@ -555,7 +556,7 @@ public class WindowsPackTests | |||||||
|         logger.Info("TEST: " + DateTime.Now.ToLongTimeString()); |         logger.Info("TEST: " + DateTime.Now.ToLongTimeString()); | ||||||
| 
 | 
 | ||||||
|         if (origDirName != "current") { |         if (origDirName != "current") { | ||||||
|             Assert.True(!Directory.Exists(currentDir)); |             Assert.False(Directory.Exists(currentDir)); | ||||||
|             currentDir = Path.Combine(rootDir, "current"); |             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. |         // 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"))); |         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); |         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) |     private static string ReadFileWithRetry(string path, ILogger logger) | ||||||
|     { |     { | ||||||
|         return IoUtil.Retry( |         return IoUtil.Retry( | ||||||
| @@ -610,7 +691,7 @@ public class WindowsPackTests | |||||||
|     //    return RunImpl(psi, logger, exitCode); |     //    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}")}"); |         //logger.Info($"TEST: Running {psi.FileName} {psi.ArgumentList.Aggregate((a, b) => $"{a} {b}")}"); | ||||||
|         //using var p = Process.Start(psi); |         //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"); |         var outputfile = PathHelper.GetTestRootPath($"coverage.rundotnet.{RandomString(8)}.xml"); | ||||||
| 
 | 
 | ||||||
| @@ -703,7 +784,7 @@ public class WindowsPackTests | |||||||
|         return RunImpl(psi, logger, exitCode); |         return RunImpl(psi, logger, exitCode); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static Random _random = new Random(); |     private static readonly Random _random = Random.Shared; | ||||||
| 
 | 
 | ||||||
|     private static string RandomString(int length) |     private static string RandomString(int length) | ||||||
|     { |     { | ||||||
| @@ -727,7 +808,7 @@ public class WindowsPackTests | |||||||
|         return RunImpl(psi, logger, exitCode); |         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 projDir = PathHelper.GetTestRootPath("TestApp"); | ||||||
|         var testStringFile = Path.Combine(projDir, "Const.cs"); |         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