mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Re-introduce WIX/MSI support in the new setup architecture
This commit is contained in:
25
build.ps1
25
build.ps1
@@ -19,23 +19,22 @@ foreach ($Folder in $Folders) {
|
||||
&"$MSBuildPath" /verbosity:minimal /restore /p:Configuration=Release
|
||||
|
||||
# Build single-exe packaged projects
|
||||
dotnet publish -v minimal -c Release -r win-x86 --self-contained=true "$PSScriptRoot\src\Update\Update.csproj" -o "$Out"
|
||||
# New-Item -Path "$Out" -Name "win-x86" -ItemType "directory"
|
||||
$BinOut = $Out
|
||||
dotnet publish -v minimal -c Release -r win-x86 --self-contained=true "$PSScriptRoot\src\SquirrelCli\SquirrelCli.csproj" -o "$Out"
|
||||
dotnet publish -v minimal -c Release -r win-x86 --self-contained=true "$PSScriptRoot\src\Update\Update.csproj" -o "$BinOut"
|
||||
|
||||
# Copy over all files we need
|
||||
Copy-Item "$In\Win32\Setup.exe" -Destination "$Out"
|
||||
# Copy-Item "$In\Win32\Setup.pdb" -Destination "$Out"
|
||||
Copy-Item "$In\Win32\StubExecutable.exe" -Destination "$Out"
|
||||
# Copy-Item "$In\Win32\WriteZipToSetup.exe" -Destination "$Out"
|
||||
# Copy-Item "$In\Win32\WriteZipToSetup.pdb" -Destination "$Out"
|
||||
|
||||
Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$Out" -Recurse
|
||||
# Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse
|
||||
Copy-Item "$PSScriptRoot\vendor\nuget.exe" -Destination "$Out"
|
||||
Copy-Item "$PSScriptRoot\vendor\rcedit.exe" -Destination "$Out"
|
||||
Copy-Item "$PSScriptRoot\vendor\signtool.exe" -Destination "$Out"
|
||||
Copy-Item "$PSScriptRoot\vendor\singlefilehost.exe" -Destination "$Out"
|
||||
Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$BinOut" -Recurse
|
||||
Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$BinOut" -Recurse
|
||||
Copy-Item "$In\Win32\Setup.exe" -Destination "$BinOut"
|
||||
Copy-Item "$In\Win32\StubExecutable.exe" -Destination "$BinOut"
|
||||
Copy-Item "$PSScriptRoot\vendor\nuget.exe" -Destination "$BinOut"
|
||||
Copy-Item "$PSScriptRoot\vendor\rcedit.exe" -Destination "$BinOut"
|
||||
Copy-Item "$PSScriptRoot\vendor\signtool.exe" -Destination "$BinOut"
|
||||
Copy-Item "$PSScriptRoot\vendor\singlefilehost.exe" -Destination "$BinOut"
|
||||
|
||||
Remove-Item "$Out\*.pdb"
|
||||
Remove-Item "$BinOut\*.pdb"
|
||||
|
||||
Write-Output "Successfully copied files to './build/publish'"
|
||||
|
||||
@@ -84,7 +84,7 @@ void unzipSingleFile(BYTE* zipBuf, DWORD cZipBuf, wstring fileLocation, std::fun
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/17387176/184746
|
||||
void ThrowLastWin32Error()
|
||||
void throwLastWin32Error()
|
||||
{
|
||||
DWORD errorMessageID = ::GetLastError();
|
||||
if (errorMessageID == 0) {
|
||||
@@ -110,7 +110,7 @@ void wexec(const wchar_t* cmd)
|
||||
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
if (!CreateProcess(NULL, szCmdline, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
|
||||
ThrowLastWin32Error();
|
||||
throwLastWin32Error();
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
@@ -143,18 +143,19 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
|
||||
});
|
||||
unzipSingleFile(zipBuf, cZipBuf, updaterPath, endsWithSquirrel);
|
||||
|
||||
// run installer
|
||||
wstring cmd = L"\"" + updaterPath + L"\" --setup \"" + myPath + L"\"";
|
||||
// run installer and forward our command line arguments
|
||||
wstring cmd = L"\"" + updaterPath + L"\" --setup \"" + myPath + L"\" " + pCmdLine;
|
||||
wexec(cmd.c_str());
|
||||
}
|
||||
catch (std::exception ex) {
|
||||
string msg = ex.what();
|
||||
// just use the _A function because std does not have a wexception
|
||||
MessageBoxA(0, string("An error occurred preventing setup from starting: " + msg + ". Please contact the application author.").c_str(), "Setup Error", MB_OK | MB_ICONERROR);
|
||||
MessageBoxA(0, string("An error occurred while running setup: " + msg + ". Please contact the application author.").c_str(), "Setup Error", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
catch (...) {
|
||||
MessageBox(0, L"An unknown error occurred while starting setup. Please contact the application author.", L"Setup Error", MB_OK | MB_ICONERROR);
|
||||
MessageBox(0, L"An unknown error occurred while running setup. Please contact the application author.", L"Setup Error", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
// clean-up after ourselves
|
||||
DeleteFile(updaterPath.c_str());
|
||||
}
|
||||
@@ -6,7 +6,8 @@ namespace Squirrel.Lib
|
||||
{
|
||||
internal class BundledSetupInfo
|
||||
{
|
||||
public string AppName { get; set; } = "This Application";
|
||||
public string AppId { get; set; }
|
||||
public string AppFriendlyName { get; set; } = "This Application";
|
||||
public string[] RequiredFrameworks { get; set; } = new string[0];
|
||||
public string BundledPackageName { get; set; }
|
||||
public byte[] BundledPackageBytes { get; set; }
|
||||
@@ -20,7 +21,8 @@ namespace Squirrel.Lib
|
||||
{
|
||||
var bundle = new BundledSetupInfo();
|
||||
using var reader = new ResourceReader(exePath);
|
||||
bundle.AppName = ReadString(reader, 201);
|
||||
bundle.AppId = ReadString(reader, 200);
|
||||
bundle.AppFriendlyName = ReadString(reader, 201);
|
||||
bundle.SplashImageBytes = ReadBytes(reader, 202);
|
||||
bundle.RequiredFrameworks = ReadString(reader, 203)?.Split(',') ?? new string[0];
|
||||
bundle.BundledPackageName = ReadString(reader, 204);
|
||||
@@ -45,7 +47,8 @@ namespace Squirrel.Lib
|
||||
public void WriteToFile(string exePath)
|
||||
{
|
||||
using var writer = new ResourceUpdater(exePath);
|
||||
WriteValue(writer, 201, AppName);
|
||||
WriteValue(writer, 200, AppId);
|
||||
WriteValue(writer, 201, AppFriendlyName);
|
||||
WriteValue(writer, 202, SplashImageBytes);
|
||||
WriteValue(writer, 203, String.Join(",", RequiredFrameworks ?? new string[0]));
|
||||
WriteValue(writer, 204, BundledPackageName);
|
||||
|
||||
@@ -19,12 +19,15 @@ namespace Squirrel
|
||||
public static string UpdatePath => FindHelperExecutable("Update.exe", _searchPaths);
|
||||
public static string StubExecutablePath => FindHelperExecutable("StubExecutable.exe", _searchPaths);
|
||||
public static string SingleFileHostPath => FindHelperExecutable("singlefilehost.exe", _searchPaths);
|
||||
public static string WixTemplatePath => FindHelperExecutable("template.wxs", _searchPaths);
|
||||
|
||||
// private so we don't expose paths to internal tools. these should be exposed as a helper function
|
||||
private static string NugetPath => FindHelperExecutable("NuGet.exe", _searchPaths);
|
||||
private static string RceditPath => FindHelperExecutable("rcedit.exe", _searchPaths);
|
||||
private static string SevenZipPath => FindHelperExecutable("7z.exe", _searchPaths);
|
||||
private static string SignToolPath => FindHelperExecutable("signtool.exe", _searchPaths);
|
||||
private static string WixCandlePath => FindHelperExecutable("candle.exe", _searchPaths);
|
||||
private static string WixLightPath => FindHelperExecutable("light.exe", _searchPaths);
|
||||
|
||||
private static List<string> _searchPaths = new List<string>();
|
||||
private static IFullLogger Log = SquirrelLocator.CurrentMutable.GetService<ILogManager>().GetLogger(typeof(HelperExe));
|
||||
@@ -42,6 +45,8 @@ namespace Squirrel
|
||||
AddSearchPath(Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "..", "..", "..", "vendor"));
|
||||
AddSearchPath(Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "..", "..", "..", "vendor", "7zip"));
|
||||
AddSearchPath(Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "..", "..", "..", "vendor", "wix"));
|
||||
#else
|
||||
AddSearchPath(Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "bin"));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -59,7 +64,9 @@ namespace Squirrel
|
||||
|
||||
additionalDirs = additionalDirs ?? Enumerable.Empty<string>();
|
||||
var dirs = (new[] { AppContext.BaseDirectory, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) })
|
||||
.Concat(additionalDirs ?? Enumerable.Empty<string>()).Select(Path.GetFullPath);
|
||||
.Concat(additionalDirs ?? Enumerable.Empty<string>())
|
||||
.Where(d => !String.IsNullOrEmpty(d))
|
||||
.Select(Path.GetFullPath);
|
||||
|
||||
var exe = @".\" + toFind;
|
||||
var result = dirs
|
||||
@@ -72,6 +79,41 @@ namespace Squirrel
|
||||
return result ?? exe;
|
||||
}
|
||||
|
||||
public static async Task CompileWixTemplateToMsi(string wxsTarget, string outputFile)
|
||||
{
|
||||
var workingDir = Path.GetDirectoryName(wxsTarget);
|
||||
var targetName = Path.GetFileNameWithoutExtension(wxsTarget);
|
||||
var objFile = Path.Combine(workingDir, targetName + ".wixobj");
|
||||
|
||||
try {
|
||||
// Candle reprocesses and compiles WiX source files into object files (.wixobj).
|
||||
var candleParams = new string[] { "-nologo", "-ext", "WixNetFxExtension", "-out", objFile, wxsTarget };
|
||||
var processResult = await Utility.InvokeProcessAsync(WixCandlePath, candleParams, CancellationToken.None, workingDir);
|
||||
|
||||
if (processResult.Item1 != 0) {
|
||||
var msg = String.Format(
|
||||
"Failed to compile WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
|
||||
"candle.exe", candleParams, processResult.Item2);
|
||||
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
// Light links and binds one or more .wixobj files and creates a Windows Installer database (.msi or .msm).
|
||||
var lightParams = new string[] { "-ext", "WixNetFxExtension", "-spdb", "-sval", "-out", outputFile, objFile };
|
||||
processResult = await Utility.InvokeProcessAsync(WixLightPath, lightParams, CancellationToken.None, workingDir);
|
||||
|
||||
if (processResult.Item1 != 0) {
|
||||
var msg = String.Format(
|
||||
"Failed to link WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}",
|
||||
"light.exe", lightParams, processResult.Item2);
|
||||
|
||||
throw new Exception(msg);
|
||||
}
|
||||
} finally {
|
||||
Utility.DeleteFileHarder(objFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
@@ -224,12 +224,12 @@ namespace Squirrel
|
||||
/// This function will escape command line arguments such that CommandLineToArgvW is guarenteed to produce the same output as the 'args' parameter.
|
||||
/// It also will automatically execute wine if trying to run an exe while not on windows.
|
||||
/// </summary>
|
||||
public static Task<(int ExitCode, string StdOutput)> InvokeProcessAsync(string fileName, IEnumerable<string> args, CancellationToken ct)
|
||||
public static Task<(int ExitCode, string StdOutput)> InvokeProcessAsync(string fileName, IEnumerable<string> args, CancellationToken ct, string workingDirectory = "")
|
||||
{
|
||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT && fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) {
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo("wine", ArgsToCommandLine(new string[] { fileName }.Concat(args))), ct);
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo("wine", ArgsToCommandLine(new string[] { fileName }.Concat(args)), workingDirectory), ct);
|
||||
} else {
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo(fileName, ArgsToCommandLine(args)), ct);
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo(fileName, ArgsToCommandLine(args), workingDirectory), ct);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
src/SquirrelCli/CopStache.cs
Normal file
29
src/SquirrelCli/CopStache.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SquirrelCli
|
||||
{
|
||||
internal static class CopStache
|
||||
{
|
||||
public static string Render(string template, Dictionary<string, string> identifiers)
|
||||
{
|
||||
var buf = new StringBuilder();
|
||||
|
||||
foreach (var line in template.Split('\n')) {
|
||||
identifiers["RandomGuid"] = (Guid.NewGuid()).ToString();
|
||||
|
||||
foreach (var key in identifiers.Keys) {
|
||||
buf.Replace("{{" + key + "}}", SecurityElement.Escape(identifiers[key]));
|
||||
}
|
||||
|
||||
buf.AppendLine(line);
|
||||
}
|
||||
|
||||
return buf.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ namespace SquirrelCli
|
||||
public string updateIcon { get; private set; }
|
||||
public string appIcon { get; private set; }
|
||||
public string setupIcon { get; private set; }
|
||||
public string setupName { get; private set; } = "Setup";
|
||||
public bool noDelta { get; private set; }
|
||||
public bool allowUnaware { get; private set; }
|
||||
public string msi { get; private set; }
|
||||
|
||||
public ReleasifyOptions()
|
||||
{
|
||||
@@ -46,21 +46,34 @@ namespace SquirrelCli
|
||||
Add("updateIcon=", "ICO file that will be used for Update.exe", v => updateIcon = v);
|
||||
Add("appIcon=", "ICO file that will be used in the 'Apps and Features' list.", v => appIcon = v);
|
||||
Add("setupIcon=", "ICO file that will be used for Setup.exe", v => setupIcon = v);
|
||||
Add("setupName=", "The name of the app installer exe without the extension (default: 'Setup')", v => setupName = v);
|
||||
Add("splashImage=", "Image to be displayed during installation (can be jpg, png, gif, etc)", v => splashImage = v);
|
||||
Add("noDelta", "Skip the generation of delta packages to save time", v => noDelta = true);
|
||||
Add("addSearchPath=", "Add additional search directories when looking for helper exe's such as Setup.exe, Update.exe, etc", v => HelperExe.AddSearchPath(v));
|
||||
Add("msi=", "Will generate a .msi machine-wide deployment tool. If installed on a machine, this msi will silently install the releasified " +
|
||||
"app each time a user signs in if it is not already installed. This value must be either 'x86' 'x64'.", v => msi = v.ToLower());
|
||||
}
|
||||
|
||||
public override void Validate()
|
||||
{
|
||||
ValidateInternal(true);
|
||||
}
|
||||
|
||||
protected virtual void ValidateInternal(bool checkPackage)
|
||||
{
|
||||
IsValidFile(nameof(appIcon), ".ico");
|
||||
IsValidFile(nameof(setupIcon), ".ico");
|
||||
IsValidFile(nameof(updateIcon), ".ico");
|
||||
IsValidFile(nameof(splashImage));
|
||||
IsValidUrl(nameof(baseUrl));
|
||||
IsRequired(nameof(package));
|
||||
IsValidFile(nameof(package), ".nupkg");
|
||||
|
||||
if (checkPackage) {
|
||||
IsRequired(nameof(package));
|
||||
IsValidFile(nameof(package), ".nupkg");
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(msi))
|
||||
if (!msi.Equals("x86") && !msi.Equals("x64"))
|
||||
throw new OptionValidationException($"Argument 'msi': File must be either 'x86' or 'x64'. Actual value was '{msi}'.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +101,7 @@ namespace SquirrelCli
|
||||
public override void Validate()
|
||||
{
|
||||
IsRequired(nameof(packName), nameof(packVersion), nameof(packAuthors), nameof(packDirectory));
|
||||
IsValidFile(nameof(setupIcon), ".ico");
|
||||
IsValidFile(nameof(splashImage));
|
||||
IsValidUrl(nameof(baseUrl));
|
||||
base.ValidateInternal(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace SquirrelCli
|
||||
ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath);
|
||||
|
||||
var bundledzp = new ZipPackage(package);
|
||||
var targetSetupExe = Path.Combine(di.FullName, options.setupName + ".exe");
|
||||
var targetSetupExe = Path.Combine(di.FullName, $"{bundledzp.Id}Setup.exe");
|
||||
File.Copy(HelperExe.SetupPath, targetSetupExe, true);
|
||||
Utility.Retry(() => HelperExe.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, setupIcon).Wait());
|
||||
|
||||
@@ -274,7 +274,8 @@ namespace SquirrelCli
|
||||
|
||||
Log.Info($"Creating Setup bundle");
|
||||
var infosave = new BundledSetupInfo() {
|
||||
AppName = bundledzp.Title ?? bundledzp.Id,
|
||||
AppId = bundledzp.Id,
|
||||
AppFriendlyName = bundledzp.Title ?? bundledzp.Id,
|
||||
BundledPackageBytes = File.ReadAllBytes(newestReleasePath),
|
||||
BundledPackageName = Path.GetFileName(newestReleasePath),
|
||||
RequiredFrameworks = requiredFrameworks.ToArray(),
|
||||
@@ -287,23 +288,57 @@ namespace SquirrelCli
|
||||
|
||||
HelperExe.SignPEFile(targetSetupExe, signingOpts).Wait();
|
||||
|
||||
//setupPath = @"C:\Source\Clowd\clowd-windows\modules\Clowd.Squirrel\build\Debug\Win32\Setup.exe";
|
||||
//string onlineSource = "https://caesay.com/clowd-updates";
|
||||
//byte[] offlineSourcesZip = new byte[0];
|
||||
|
||||
//SetupResourceWriter.WriteZipToSetup(targetSetupExe, newestReleasePath, frameworkVersion, backgroundGif);
|
||||
|
||||
//if (generateMsi) {
|
||||
// createMsiPackage(targetSetupExe, new ZipPackage(package), packageAs64Bit).Wait();
|
||||
|
||||
// if (signingOpts != null) {
|
||||
// signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait();
|
||||
// }
|
||||
//}
|
||||
if (!String.IsNullOrEmpty(options.msi)) {
|
||||
bool x64 = options.msi.Equals("x64");
|
||||
createMsiPackage(targetSetupExe, new ZipPackage(package), x64).Wait();
|
||||
HelperExe.SignPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait();
|
||||
}
|
||||
|
||||
Log.Info("Done");
|
||||
}
|
||||
|
||||
static async Task createMsiPackage(string setupExe, IPackage package, bool packageAs64Bit)
|
||||
{
|
||||
Log.Info($"Compiling machine-wide msi deployment tool in {(packageAs64Bit ? "64-bit" : "32-bit")} mode");
|
||||
|
||||
var setupExeDir = Path.GetDirectoryName(setupExe);
|
||||
var setupName = Path.GetFileNameWithoutExtension(setupExe);
|
||||
var company = String.Join(",", package.Authors);
|
||||
var culture = CultureInfo.GetCultureInfo(package.Language ?? "").TextInfo.ANSICodePage;
|
||||
|
||||
var templateText = File.ReadAllText(HelperExe.WixTemplatePath);
|
||||
var templateData = new Dictionary<string, string> {
|
||||
{ "Id", package.Id },
|
||||
{ "Title", package.Title },
|
||||
{ "Author", company },
|
||||
{ "Version", Regex.Replace(package.Version.ToString(), @"-.*$", "") },
|
||||
{ "Summary", package.Summary ?? package.Description ?? package.Id },
|
||||
{ "Codepage", $"{culture}" },
|
||||
{ "Platform", packageAs64Bit ? "x64" : "x86" },
|
||||
{ "ProgramFilesFolder", packageAs64Bit ? "ProgramFiles64Folder" : "ProgramFilesFolder" },
|
||||
{ "Win64YesNo", packageAs64Bit ? "yes" : "no" },
|
||||
{ "SetupName", setupName }
|
||||
};
|
||||
|
||||
// NB: We need some GUIDs that are based on the package ID, but unique (i.e.
|
||||
// "Unique but consistent").
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
templateData[String.Format("IdAsGuid{0}", i)] = Utility.CreateGuidFromHash(String.Format("{0}:{1}", package.Id, i)).ToString();
|
||||
}
|
||||
|
||||
var templateResult = CopStache.Render(templateText, templateData);
|
||||
|
||||
var wxsTarget = Path.Combine(setupExeDir, setupName + ".wxs");
|
||||
File.WriteAllText(wxsTarget, templateResult, Encoding.UTF8);
|
||||
|
||||
try {
|
||||
var msiTarget = Path.Combine(setupExeDir, setupName + "_DeploymentTool.msi");
|
||||
await HelperExe.CompileWixTemplateToMsi(wxsTarget, msiTarget);
|
||||
} finally {
|
||||
File.Delete(wxsTarget);
|
||||
}
|
||||
}
|
||||
|
||||
static async Task createExecutableStubForExe(string exeToCopy)
|
||||
{
|
||||
var target = Path.Combine(
|
||||
|
||||
@@ -48,8 +48,10 @@ namespace Squirrel.Update
|
||||
|
||||
// NB: Trying to delete the app directory while we have Setup.log
|
||||
// open will actually crash the uninstaller
|
||||
bool logToTemp = opt.updateAction == UpdateAction.Uninstall
|
||||
|| opt.updateAction == UpdateAction.Setup;
|
||||
bool logToTemp =
|
||||
opt.updateAction == UpdateAction.Uninstall ||
|
||||
opt.updateAction == UpdateAction.Setup ||
|
||||
opt.updateAction == UpdateAction.Install;
|
||||
|
||||
var logger = new SetupLogLogger(logToTemp, opt.updateAction) { Level = LogLevel.Info };
|
||||
SquirrelLocator.CurrentMutable.Register(() => logger, typeof(SimpleSplat.ILogger));
|
||||
@@ -79,7 +81,7 @@ namespace Squirrel.Update
|
||||
|
||||
switch (opt.updateAction) {
|
||||
case UpdateAction.Setup:
|
||||
Setup(opt.target, opt.silentInstall).Wait();
|
||||
Setup(opt.target, opt.silentInstall, opt.checkInstall).Wait();
|
||||
break;
|
||||
case UpdateAction.Install:
|
||||
var progressSource = new ProgressSource();
|
||||
@@ -115,11 +117,42 @@ namespace Squirrel.Update
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async Task Setup(string setupPath, bool silentInstall)
|
||||
static async Task Setup(string setupPath, bool silentInstall, bool checkInstall)
|
||||
{
|
||||
Log.Info($"Extracting bundled app data from '{setupPath}'.");
|
||||
var info = BundledSetupInfo.ReadFromFile(setupPath);
|
||||
|
||||
if (checkInstall) {
|
||||
// CS: migrated from MachineInstaller.cpp
|
||||
// here we are being run by the MSI deployment tool. We should check if the app
|
||||
// is installed. if it is, we exit. If not, we install it silently. The goal is
|
||||
// to ensure that all users of a system will always have this app installed.
|
||||
Log.Info($"Has --checkInstall argument, verifying machine-wide installation");
|
||||
|
||||
// NB: Users often get into the sitch where they install the MSI, then try to
|
||||
// install the standalone package on top of that. In previous versions we tried
|
||||
// to detect if the app was properly installed, but now we're taking the much
|
||||
// more conservative approach, that if the package dir exists in any way, we're
|
||||
// bailing out
|
||||
|
||||
// C:\Users\Username\AppData\Local\$pkgName
|
||||
var localadinstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), info.AppId);
|
||||
if (Directory.Exists(localadinstall)) {
|
||||
Log.Info($"App install detected at '{localadinstall}', exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// C:\ProgramData\$pkgName\$username
|
||||
var programdatainstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), info.AppId, Environment.UserName);
|
||||
if (Directory.Exists(programdatainstall)) {
|
||||
Log.Info($"App install detected at '{programdatainstall}', exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info("Installation for this user was not found. Beginning silent setup...");
|
||||
silentInstall = true;
|
||||
}
|
||||
|
||||
using var _t = Utility.WithTempDirectory(out var tempFolder);
|
||||
|
||||
// show splash screen
|
||||
@@ -142,9 +175,9 @@ namespace Squirrel.Update
|
||||
if (missingFrameworks.Any()) {
|
||||
Log.Info($"The following components are missing: " + String.Join(", ", missingFrameworks.Select(m => m.Id)));
|
||||
string message = missingFrameworks.Length > 1
|
||||
? $"{info.AppName} is missing the following system components: {String.Join(", ", missingFrameworks.Select(s => s.DisplayName))}. " +
|
||||
? $"{info.AppFriendlyName} is missing the following system components: {String.Join(", ", missingFrameworks.Select(s => s.DisplayName))}. " +
|
||||
$"Would you like to install these now?"
|
||||
: $"{info.AppName} requires {missingFrameworks.First().DisplayName} to continue, would you like to install it now?";
|
||||
: $"{info.AppFriendlyName} requires {missingFrameworks.First().DisplayName} to continue, would you like to install it now?";
|
||||
|
||||
// if running in attended mode, ask the user if they want to continue. otherwise, proceed
|
||||
if (splash != null) {
|
||||
|
||||
@@ -13,16 +13,16 @@ namespace Squirrel.Update
|
||||
internal class StartupOption
|
||||
{
|
||||
private readonly OptionSet optionSet;
|
||||
internal bool silentInstall { get; private set; } = false;
|
||||
internal UpdateAction updateAction { get; private set; } = default(UpdateAction);
|
||||
internal string target { get; private set; } = default(string);
|
||||
//internal string baseUrl { get; private set; } = default(string);
|
||||
internal string processStart { get; private set; } = default(string);
|
||||
internal string processStartArgs { get; private set; } = default(string);
|
||||
internal string icon { get; private set; } = default(string);
|
||||
internal string shortcutArgs { get; private set; } = default(string);
|
||||
internal bool shouldWait { get; private set; } = false;
|
||||
internal bool onlyUpdateShortcuts { get; private set; } = false;
|
||||
internal bool checkInstall { get; private set; } = false;
|
||||
internal bool silentInstall { get; private set; } = false;
|
||||
|
||||
public StartupOption(string[] args)
|
||||
{
|
||||
@@ -40,8 +40,7 @@ namespace Squirrel.Update
|
||||
$"Usage: {exeName} command [OPTS]",
|
||||
"",
|
||||
"Commands:",
|
||||
{ "install=", "Install the app whose package is in the specified directory or url", v => { updateAction = UpdateAction.Install; target = v; } },
|
||||
{ "uninstall", "Uninstall the app the same dir as Update.exe", v => updateAction = UpdateAction.Uninstall},
|
||||
{ "uninstall", "Uninstall the app in the same directory as Update.exe", v => updateAction = UpdateAction.Uninstall},
|
||||
{ "download=", "Download the releases specified by the URL and write new results to stdout as JSON", v => { updateAction = UpdateAction.Download; target = v; } },
|
||||
{ "checkForUpdate=", "Check for one available update and writes new results to stdout as JSON", v => { updateAction = UpdateAction.CheckForUpdate; target = v; } },
|
||||
{ "update=", "Update the application to the latest remote version specified by URL", v => { updateAction = UpdateAction.Update; target = v; } },
|
||||
@@ -52,15 +51,17 @@ namespace Squirrel.Update
|
||||
"Options:",
|
||||
{ "h|?|help", "Display Help and exit", _ => {} },
|
||||
{ "i=|icon", "Path to an ICO file that will be used for icon shortcuts", v => icon = v},
|
||||
{ "s|silent", "Silent install", _ => silentInstall = true},
|
||||
{ "l=|shortcut-locations=", "Comma-separated string of shortcut locations, e.g. 'Desktop,StartMenu'", v => shortcutArgs = v},
|
||||
{ "updateOnly", "Update shortcuts that already exist, rather than creating new ones", _ => onlyUpdateShortcuts = true},
|
||||
|
||||
// hidden arguments
|
||||
// hidden arguments, used internally by Squirrel and should not be used by Squirrel applications themselves
|
||||
{ "install=", "Install the app whose package is in the specified directory or url", v => { updateAction = UpdateAction.Install; target = v; }, true },
|
||||
{ "s|silent", "Silent install", _ => silentInstall = true, true},
|
||||
{ "processStart=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; }, true},
|
||||
{ "processStartAndWait=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; shouldWait = true; }, true},
|
||||
{ "a=|process-start-args=", "Arguments that will be used when starting executable", v => processStartArgs = v, true},
|
||||
{ "setup=", "Does an initial install with the help of the Setup.exe resources", v => { updateAction = UpdateAction.Setup; target = v; }, true },
|
||||
{ "checkInstall", "Will install the app silently if it is not currently installed. Used for machine-wide deployments", v => { checkInstall = true; }, true },
|
||||
};
|
||||
|
||||
opts.Parse(args);
|
||||
|
||||
4
vendor/wix/template.wxs
vendored
4
vendor/wix/template.wxs
vendored
@@ -13,13 +13,13 @@
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="{{ProgramFilesFolder}}">
|
||||
<Directory Id="APPLICATIONROOTDIRECTORY" Name="{{Title}} Deployment" />
|
||||
<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="./Setup.exe" KeyPath="yes"/>
|
||||
<File Id="{{Id}}.exe" Name="{{Id}}DeploymentTool.exe" Source="./{{SetupName}}.exe" KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user