mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add bootstrapping and replace custom actions with rust dll
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -2363,6 +2363,17 @@ dependencies = [
|
||||
"velopack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "velopack_wix"
|
||||
version = "0.0.0-local"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"remove_dir_all",
|
||||
"velopack",
|
||||
"velopack_bins",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
"src/lib-rust",
|
||||
"src/lib-nodejs/velopack_nodeffi",
|
||||
"src/lib-cpp",
|
||||
"src/wix-dll",
|
||||
]
|
||||
exclude = [
|
||||
"samples/RustIced",
|
||||
@@ -24,6 +25,7 @@ rust-version = "1.75"
|
||||
|
||||
[workspace.dependencies]
|
||||
velopack = { path = "src/lib-rust", features = ["file-logging", "public-utils"] }
|
||||
velopack_bins = { path = "src/bins" }
|
||||
log = "0.4"
|
||||
log-derive = "0.4.1"
|
||||
ureq = "3.0"
|
||||
|
||||
@@ -68,7 +68,12 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
|
||||
|
||||
info!("Applying package {} to current: {}", new_version, old_version);
|
||||
|
||||
if !crate::windows::prerequisite::prompt_and_install_all_missing(&new_app_manifest, Some(&old_version))? {
|
||||
if !crate::windows::prerequisite::prompt_and_install_all_missing(
|
||||
&new_app_manifest.title,
|
||||
&new_version.to_string(),
|
||||
&new_app_manifest.runtime_dependencies,
|
||||
Some(&old_version),
|
||||
)? {
|
||||
bail!("Stopping apply. Pre-requisites are missing and user cancelled.");
|
||||
}
|
||||
|
||||
@@ -121,8 +126,9 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
|
||||
// fifth, we try to replace the current dir with temp_path_new
|
||||
// if this fails we will yolo a rollback...
|
||||
info!("Replacing current dir with {:?}", &temp_path_new);
|
||||
shared::retry_io_ex(|| fs::rename(&temp_path_new, ¤t_dir), 1000, 30)
|
||||
.context("Unable to complete the update, and the app was left in a broken state. You may need to re-install or repair this application manually.")?;
|
||||
shared::retry_io_ex(|| fs::rename(&temp_path_new, ¤t_dir), 1000, 30).context(
|
||||
"Unable to complete the update, and the app was left in a broken state. You may need to re-install or repair this application manually.",
|
||||
)?;
|
||||
|
||||
// if !requires_robocopy {
|
||||
// // if we didn't need robocopy for the backup, we don't need it for the deploy hopefully
|
||||
|
||||
@@ -30,7 +30,7 @@ pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Op
|
||||
info!(" Package Machine Architecture: {}", &app.machine_architecture);
|
||||
info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies);
|
||||
|
||||
if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? {
|
||||
if !windows::prerequisite::prompt_and_install_all_missing(&app.title, &app.version.to_string(), &app.runtime_dependencies, None)? {
|
||||
info!("Cancelling setup. Pre-requisites not installed.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -18,20 +18,15 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
pub fn show_restart_required(app: &Manifest) {
|
||||
pub fn show_restart_required(app_name: &str, app_version: &str) {
|
||||
show_warn(
|
||||
format!("{} Setup {}", app.title, app.version).as_str(),
|
||||
format!("{} Setup {}", app_name, app_version).as_str(),
|
||||
Some("Restart Required"),
|
||||
"A restart is required before Setup can continue. Please restart your computer and try again.",
|
||||
);
|
||||
}
|
||||
|
||||
pub fn show_update_missing_dependencies_dialog(
|
||||
app: &Manifest,
|
||||
depedency_string: &str,
|
||||
from: &semver::Version,
|
||||
to: &semver::Version,
|
||||
) -> bool {
|
||||
pub fn show_update_missing_dependencies_dialog(app_name: &str, depedency_string: &str, from_ver: &str, to_ver: &str) -> bool {
|
||||
if get_silent() {
|
||||
// this has different behavior to show_setup_missing_dependencies_dialog,
|
||||
// if silent is true then we will bail because the app is probably exiting
|
||||
@@ -41,27 +36,23 @@ pub fn show_update_missing_dependencies_dialog(
|
||||
}
|
||||
|
||||
show_ok_cancel(
|
||||
format!("{} Update", app.title).as_str(),
|
||||
Some(format!("{} would like to update from {} to {}", app.title, from, to).as_str()),
|
||||
format!(
|
||||
"{} {to} has missing dependencies which need to be installed: {}, would you like to continue?",
|
||||
app.title, depedency_string
|
||||
)
|
||||
.as_str(),
|
||||
format!("{} Update", app_name).as_str(),
|
||||
Some(format!("{} would like to update from {} to {}", app_name, from_ver, to_ver).as_str()),
|
||||
format!("{} {to_ver} has missing dependencies which need to be installed: {}, would you like to continue?", app_name, depedency_string)
|
||||
.as_str(),
|
||||
Some("Install & Update"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string: &str) -> bool {
|
||||
pub fn show_setup_missing_dependencies_dialog(app_name: &str, app_version: &str, depedency_string: &str) -> bool {
|
||||
if get_silent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
show_ok_cancel(
|
||||
format!("{} Setup {}", app.title, app.version).as_str(),
|
||||
Some(format!("{} has missing system dependencies.", app.title).as_str()),
|
||||
format!("{} requires the following packages to be installed: {}, would you like to continue?", app.title, depedency_string)
|
||||
.as_str(),
|
||||
format!("{} Setup {}", app_name, app_version).as_str(),
|
||||
Some(format!("{} has missing system dependencies.", app_name).as_str()),
|
||||
format!("{} requires the following packages to be installed: {}, would you like to continue?", app_name, depedency_string).as_str(),
|
||||
Some("Install"),
|
||||
)
|
||||
}
|
||||
@@ -259,14 +250,7 @@ pub fn generate_confirm(
|
||||
Ok(DialogResult::from_win(pnbutton))
|
||||
}
|
||||
|
||||
pub fn generate_alert(
|
||||
title: &str,
|
||||
header: Option<&str>,
|
||||
body: &str,
|
||||
ok_text: Option<&str>,
|
||||
btns: DialogButton,
|
||||
ico: DialogIcon,
|
||||
) -> Result<()> {
|
||||
pub fn generate_alert(title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>, btns: DialogButton, ico: DialogIcon) -> Result<()> {
|
||||
let _ = generate_confirm(title, header, body, ok_text, btns, ico)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -274,7 +258,6 @@ pub fn generate_alert(
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn show_all_windows_dialogs() {
|
||||
use semver::Version;
|
||||
let app = Manifest {
|
||||
id: "test.app".to_string(),
|
||||
title: "Test Application".to_string(),
|
||||
@@ -285,9 +268,9 @@ fn show_all_windows_dialogs() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
show_restart_required(&app);
|
||||
show_update_missing_dependencies_dialog(&app, "net8-x64", &Version::new(1, 0, 0), &Version::new(2, 0, 0));
|
||||
show_setup_missing_dependencies_dialog(&app, "net8-x64");
|
||||
show_restart_required(&app.title, &app.version.to_string());
|
||||
show_update_missing_dependencies_dialog(&app.title, "net8-x64", "1.0.0", "2.0.0");
|
||||
show_setup_missing_dependencies_dialog(&app.title, &app.version.to_string(), "net8-x64");
|
||||
show_uninstall_complete_with_errors_dialog("Test Application", Some(&PathBuf::from("C:\\audio.log")));
|
||||
show_processes_locking_folder_dialog(&app.title, &app.version.to_string(), "TestProcess1, TestProcess2");
|
||||
show_overwrite_repair_dialog(&app, &PathBuf::from("C:\\Program Files\\TestApp"), false);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use super::{runtimes, splash};
|
||||
use crate::shared::dialogs;
|
||||
use anyhow::Result;
|
||||
use velopack::{bundle, download};
|
||||
use velopack::download;
|
||||
|
||||
pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result<bool> {
|
||||
pub fn prompt_and_install_all_missing(
|
||||
app_name: &str,
|
||||
app_version: &str,
|
||||
dependencies: &str,
|
||||
updating_from: Option<&semver::Version>,
|
||||
) -> Result<bool> {
|
||||
info!("Checking application pre-requisites...");
|
||||
let dependencies = super::runtimes::parse_dependency_list(&app.runtime_dependencies);
|
||||
let dependencies = super::runtimes::parse_dependency_list(dependencies);
|
||||
let mut missing: Vec<&Box<dyn runtimes::RuntimeInfo>> = Vec::new();
|
||||
let mut missing_str = String::new();
|
||||
|
||||
@@ -25,12 +30,12 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
|
||||
|
||||
if !missing.is_empty() {
|
||||
if let Some(from_version) = updating_from {
|
||||
if !dialogs::show_update_missing_dependencies_dialog(&app, &missing_str, &from_version, &app.version) {
|
||||
if !dialogs::show_update_missing_dependencies_dialog(app_name, &missing_str, &from_version.to_string(), app_version) {
|
||||
error!("User cancelled pre-requisite installation.");
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
if !dialogs::show_setup_missing_dependencies_dialog(&app, &missing_str) {
|
||||
if !dialogs::show_setup_missing_dependencies_dialog(app_name, app_version, &missing_str) {
|
||||
error!("User cancelled pre-requisite installation.");
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -68,7 +73,7 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
|
||||
let result = dep.install(&exe_path, quiet)?;
|
||||
if result == runtimes::RuntimeInstallResult::RestartRequired {
|
||||
warn!("A restart is required to complete the installation of {}.", dep.display_name());
|
||||
dialogs::show_restart_required(&app);
|
||||
dialogs::show_restart_required(&app_name, app_version);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ use std::path::Path;
|
||||
use crate::{misc, Error};
|
||||
|
||||
/// Downloads a file from a URL and writes it to a file while reporting progress from 0-100.
|
||||
pub fn download_url_to_file<A>(url: &str, file_path: &Path, mut progress: A) -> Result<(), Error>
|
||||
pub fn download_url_to_file<A, S: AsRef<Path>>(url: &str, file_path: S, mut progress: A) -> Result<(), Error>
|
||||
where
|
||||
A: FnMut(i16),
|
||||
{
|
||||
let file_path = file_path.as_ref();
|
||||
let agent = get_download_agent()?;
|
||||
let (head, body) = agent.get(url).call()?.into_parts();
|
||||
|
||||
|
||||
@@ -175,8 +175,13 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
protected override Task CreateSetupPackage(Action<int> progress, string releasePkg, string packDir, string targetSetupExe,
|
||||
Func<string, VelopackAssetType, string> createAsset)
|
||||
{
|
||||
var setupExeProgress = Options.BuildMsi ? CoreUtil.CreateProgressDelegate(progress, 0, 50) : progress;
|
||||
var msiProgress = CoreUtil.CreateProgressDelegate(progress, 50, 100);
|
||||
var setupExeProgress = Options.BuildMsi
|
||||
? CoreUtil.CreateProgressDelegate(progress, 0, 33)
|
||||
: CoreUtil.CreateProgressDelegate(progress, 0, 66);
|
||||
var msiProgress = CoreUtil.CreateProgressDelegate(progress, 33, 66);
|
||||
var signingProgress = CoreUtil.CreateProgressDelegate(progress, 66, 100);
|
||||
|
||||
List<string> filesToSign = new();
|
||||
|
||||
var bundledZip = new ZipPackage(releasePkg);
|
||||
IoUtil.Retry(() => File.Copy(HelperFile.SetupPath, targetSetupExe, true));
|
||||
@@ -191,11 +196,9 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
editor.Commit();
|
||||
|
||||
setupExeProgress(25);
|
||||
Log.Debug($"Creating Setup bundle");
|
||||
Log.Debug("Creating Setup bundle");
|
||||
SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg);
|
||||
setupExeProgress(50);
|
||||
Log.Debug("Signing Setup bundle");
|
||||
SignFilesImpl(CoreUtil.CreateProgressDelegate(setupExeProgress, 50, 100), targetSetupExe);
|
||||
filesToSign.Add(targetSetupExe);
|
||||
Log.Info($"Setup bundle created '{Path.GetFileName(targetSetupExe)}'.");
|
||||
setupExeProgress(100);
|
||||
|
||||
@@ -205,11 +208,17 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
var portablePackage = new DirectoryInfo(Path.Combine(TempDir.FullName, "CreatePortablePackage"));
|
||||
if (portablePackage.Exists) {
|
||||
CompileWixTemplateToMsi(msiProgress, portablePackage, msiPath);
|
||||
Log.Info($"MSI created '{Path.GetFileName(msiPath)}'.");
|
||||
filesToSign.Add(msiPath);
|
||||
msiProgress(100);
|
||||
} else {
|
||||
Log.Warn("Portable package not found, skipping MSI creation.");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Debug("Signing Setup files");
|
||||
SignFilesImpl(signingProgress, filesToSign.ToArray());
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -232,7 +241,11 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
// create a .portable file to indicate this is a portable package
|
||||
File.Create(Path.Combine(dir.FullName, ".portable")).Close();
|
||||
|
||||
await EasyZip.CreateZipFromDirectoryAsync(Log.ToVelopackLogger(), outputPath, dir.FullName, CoreUtil.CreateProgressDelegate(progress, 40, 100));
|
||||
await EasyZip.CreateZipFromDirectoryAsync(
|
||||
Log.ToVelopackLogger(),
|
||||
outputPath,
|
||||
dir.FullName,
|
||||
CoreUtil.CreateProgressDelegate(progress, 40, 100));
|
||||
progress(100);
|
||||
}
|
||||
|
||||
@@ -350,7 +363,12 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
}
|
||||
|
||||
var licenseRtfPath = GetLicenseRtfFile();
|
||||
var templateData = MsiBuilder.ConvertOptionsToTemplateData(portableDirectory, GetShortcuts(), licenseRtfPath, GetRuntimeDependencies(), Options);
|
||||
var templateData = MsiBuilder.ConvertOptionsToTemplateData(
|
||||
portableDirectory,
|
||||
GetShortcuts(),
|
||||
licenseRtfPath,
|
||||
GetRuntimeDependencies(),
|
||||
Options);
|
||||
MsiBuilder.CompileWixMsi(Log, templateData, progress, msiFilePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ public static class MsiBuilder
|
||||
return (template(data), locale(data));
|
||||
}
|
||||
|
||||
public static MsiTemplateData ConvertOptionsToTemplateData(DirectoryInfo portableDir, ShortcutLocation shortcuts, string licenseRtfPath, string runtimeDeps,
|
||||
public static MsiTemplateData ConvertOptionsToTemplateData(DirectoryInfo portableDir, ShortcutLocation shortcuts, string licenseRtfPath,
|
||||
string runtimeDeps,
|
||||
WindowsPackOptions options)
|
||||
{
|
||||
// WiX Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or
|
||||
@@ -38,9 +39,9 @@ public static class MsiBuilder
|
||||
if (char.GetUnicodeCategory(wixId[0]) == UnicodeCategory.DecimalDigitNumber)
|
||||
wixId = "_" + wixId;
|
||||
|
||||
var parsedVersion = SemanticVersion.Parse(options.PackVersion);
|
||||
var msiVersion = options.MsiVersionOverride;
|
||||
if (string.IsNullOrWhiteSpace(msiVersion)) {
|
||||
var parsedVersion = SemanticVersion.Parse(options.PackVersion);
|
||||
msiVersion = $"{parsedVersion.Major}.{parsedVersion.Minor}.{parsedVersion.Patch}.0";
|
||||
}
|
||||
|
||||
@@ -54,6 +55,7 @@ public static class MsiBuilder
|
||||
AppPublisher = options.PackAuthors ?? options.PackId,
|
||||
AppTitle = options.PackTitle ?? options.PackId,
|
||||
AppMsiVersion = msiVersion,
|
||||
AppVersion = parsedVersion.ToFullString(),
|
||||
SourceDirectoryPath = portableDir.FullName,
|
||||
Is64Bit = options.TargetRuntime.Architecture is not RuntimeCpu.x86,
|
||||
CultureLCID = CultureInfo.GetCultureInfo("en-US").TextInfo.ANSICodePage,
|
||||
@@ -116,7 +118,8 @@ public static class MsiBuilder
|
||||
string[] manifestResourceNames = assy.GetManifestResourceNames();
|
||||
string resourceNameFull = manifestResourceNames.SingleOrDefault(name => name.EndsWith(resourceName));
|
||||
if (string.IsNullOrEmpty(resourceNameFull))
|
||||
throw new InvalidOperationException($"Resource '{resourceName}' not found in assembly. Available resources: {string.Join(", ", manifestResourceNames)}");
|
||||
throw new InvalidOperationException(
|
||||
$"Resource '{resourceName}' not found in assembly. Available resources: {string.Join(", ", manifestResourceNames)}");
|
||||
|
||||
using var stream = assy.GetManifestResourceStream(resourceNameFull);
|
||||
if (stream == null)
|
||||
|
||||
@@ -20,6 +20,7 @@ public class MsiTemplateData
|
||||
public string AppPublisher;
|
||||
public string AppPublisherSanitized => MsiUtil.SanitizeDirectoryString(AppPublisher);
|
||||
public string AppMsiVersion;
|
||||
public string AppVersion;
|
||||
|
||||
public string StubFileName;
|
||||
public string RuntimeDependencies;
|
||||
@@ -52,9 +53,4 @@ public class MsiTemplateData
|
||||
|
||||
public bool HasSideBannerImage => !string.IsNullOrWhiteSpace(SideBannerImagePath) && File.Exists(SideBannerImagePath);
|
||||
public string SideBannerImagePath;
|
||||
|
||||
public string WelcomeNextPage => HasLicense ? "LicenseAgreementDlg" : LicenseNextPage;
|
||||
public string LicenseNextPage => InstallLocationEither ? "InstallScopeDlg" : "VerifyReadyDlg";
|
||||
public string InstallScopePrevPage => HasLicense ? "LicenseAgreementDlg" : "WelcomeDlg";
|
||||
public string VerifyReadyPrevPage => InstallLocationEither ? "InstallScopeDlg" : InstallScopePrevPage;
|
||||
}
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes"/>
|
||||
<StandardDirectory Id="TARGETDIR">
|
||||
<Directory Id="INSTALLFOLDER" Name="{{AppTitleSanitized}}"
|
||||
ComponentGuidGenerationSeed="{{ComponentGenerationSeedGuid}}">
|
||||
<Directory Id="INSTALLFOLDER" Name="{{AppTitleSanitized}}" ComponentGuidGenerationSeed="{{ComponentGenerationSeedGuid}}">
|
||||
<Directory Name="current"/>
|
||||
<Directory Id="PACKAGES_DIR" Name="packages"/>
|
||||
</Directory>
|
||||
@@ -20,14 +19,10 @@
|
||||
{{#if DesktopShortcut}}
|
||||
<StandardDirectory Id="DesktopFolder">
|
||||
<Component Id="ApplicationDesktopShortcut">
|
||||
<Shortcut Id="ApplicationDesktopShortcut"
|
||||
Name="{{AppTitle}}"
|
||||
Description="Desktop shortcut for {{AppTitle}}"
|
||||
Target="[INSTALLFOLDER]{{StubFileName}}"
|
||||
WorkingDirectory="INSTALLFOLDER"/>
|
||||
<Shortcut Id="ApplicationDesktopShortcut" Name="{{AppTitle}}" Description="Desktop shortcut for {{AppTitle}}"
|
||||
Target="[INSTALLFOLDER]{{StubFileName}}" WorkingDirectory="INSTALLFOLDER"/>
|
||||
<RemoveFolder Id="CleanUpDesktopShortcut" Directory="INSTALLFOLDER" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\{{AppPublisherSanitized}}\{{AppId}}.DesktopShortcut"
|
||||
<RegistryValue Root="HKCU" Key="Software\{{AppPublisherSanitized}}\{{AppId}}.DesktopShortcut"
|
||||
Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
@@ -36,14 +31,10 @@
|
||||
{{#if StartMenuShortcut}}
|
||||
<StandardDirectory Id="StartMenuFolder">
|
||||
<Component Id="ApplicationStartMenuShortcut">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="{{AppTitle}}"
|
||||
Description="Start Menu shortcut for {{AppTitle}}"
|
||||
Target="[INSTALLFOLDER]{{StubFileName}}"
|
||||
WorkingDirectory="INSTALLFOLDER"/>
|
||||
<Shortcut Id="ApplicationStartMenuShortcut" Name="{{AppTitle}}" Description="Start Menu shortcut for {{AppTitle}}"
|
||||
Target="[INSTALLFOLDER]{{StubFileName}}" WorkingDirectory="INSTALLFOLDER"/>
|
||||
<RemoveFolder Id="CleanUpStartMenuShortcut" Directory="INSTALLFOLDER" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\{{AppPublisherSanitized}}\{{AppId}}.StartMenuShortcut"
|
||||
<RegistryValue Root="HKCU" Key="Software\{{AppPublisherSanitized}}\{{AppId}}.StartMenuShortcut"
|
||||
Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
@@ -86,40 +77,46 @@
|
||||
{{/if}}
|
||||
|
||||
<UI>
|
||||
<ui:WixUI Id="WixUI_Velopack"
|
||||
InstallDirectory="INSTALLFOLDER"/>
|
||||
<ui:WixUI Id="WixUI_Velopack" InstallDirectory="INSTALLFOLDER"/>
|
||||
|
||||
<Publish Dialog="ExitDialog"
|
||||
Control="Finish"
|
||||
Event="DoAction"
|
||||
Value="LaunchApplication"
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="RustLaunchApplication"
|
||||
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed"/>
|
||||
</UI>
|
||||
|
||||
<Files Include="{{SourceDirectoryPath}}\**"/>
|
||||
|
||||
<CustomAction Id="RemoveAppDirectory" Directory="INSTALLFOLDER" Impersonate="no"
|
||||
ExeCommand="cmd.exe /C rmdir /S /Q "[INSTALLFOLDER]"" Execute="deferred"
|
||||
Return="ignore"/>
|
||||
<CustomAction Id="RemoveTempDirectory" Directory="TempFolder" Impersonate="yes"
|
||||
ExeCommand="cmd.exe /C rmdir /S /Q "%TEMP%\velopack_{{AppId}}""
|
||||
Execute="deferred" Return="ignore"/>
|
||||
<CustomAction Id="LaunchApplication" Directory="INSTALLFOLDER" Impersonate="yes"
|
||||
ExeCommand=""[INSTALLFOLDER]{{StubFileName}}"" Execute="immediate" Return="ignore"/>
|
||||
<!-- <CustomAction Id="RemoveAppDirectory" Directory="INSTALLFOLDER" Impersonate="no"-->
|
||||
<!-- ExeCommand="cmd.exe /C rmdir /S /Q "[INSTALLFOLDER]"" Execute="deferred" Return="ignore"/>-->
|
||||
<!-- <CustomAction Id="RemoveTempDirectory" Directory="TempFolder" Impersonate="yes"-->
|
||||
<!-- ExeCommand="cmd.exe /C rmdir /S /Q "%TEMP%\velopack_{{AppId}}"" Execute="deferred" Return="ignore"/>-->
|
||||
<!-- <CustomAction Id="LaunchApplication" Directory="INSTALLFOLDER" Impersonate="yes"-->
|
||||
<!-- ExeCommand=""[INSTALLFOLDER]{{StubFileName}}"" Execute="immediate" Return="ignore"/>-->
|
||||
|
||||
<!-- Add our custom Rust module for custom actions -->
|
||||
<Binary Id="RustDll" SourceFile="{{RustNativeModulePath}}"/>
|
||||
<Property Id="RustAppId" Value="{{AppTitle}}"/>
|
||||
<Property Id="RustAppTitle" Value="{{AppTitle}}"/>
|
||||
<Property Id="RustAppVersion" Value="{{AppVersion}}"/>
|
||||
<Property Id="RustRuntimeDependencies" Value="{{RuntimeDependencies}}"/>
|
||||
<CustomAction Id="RustBootstrap" BinaryRef="RustDll" DllEntry="Bootstrap" Execute="immediate" Return="check"/>
|
||||
<CustomAction Id="RustCheckMissing" BinaryRef="RustDll" DllEntry="CheckMissing" Execute="immediate"
|
||||
<Property Id="RustStubFileName" Value="{{StubFileName}}"/>
|
||||
|
||||
<CustomAction Id="RustEarlyBootstrap" BinaryRef="RustDll" DllEntry="EarlyBootstrap" Execute="immediate" Return="check"/>
|
||||
|
||||
<!-- deferred actions do not have access to msi properties and read all data from "CustomActionData" which is a string,
|
||||
so we delimit with " which is unlikely (or disallowed) from to appearing in filenames / paths.-->
|
||||
<CustomAction Id="SetRustCleanupData" Property="RustCleanup" Value="[INSTALLFOLDER]"[RustAppId]" Execute="immediate"
|
||||
Return="check"/>
|
||||
<CustomAction Id="RustCleanup" BinaryRef="RustDll" DllEntry="CleanupDeferred" Execute="deferred" Impersonate="no" Return="ignore"/>
|
||||
<CustomAction Id="RustLaunchApplication" BinaryRef="RustDll" DllEntry="LaunchApplication" Impersonate="yes" Execute="immediate"
|
||||
Return="ignore"/>
|
||||
|
||||
<InstallUISequence>
|
||||
<Custom Action="RustEarlyBootstrap" Before="AppSearch" Condition="REMOVE="""/>
|
||||
</InstallUISequence>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="RemoveAppDirectory" Before="RemoveFolders"
|
||||
Condition="(REMOVE="ALL") AND (NOT UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="RemoveTempDirectory" Before="InstallFinalize"
|
||||
Condition="(REMOVE="ALL") AND (NOT UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="RustBootstrap" Before="InstallInitialize" Condition="(REMOVE="")"/>
|
||||
<Custom Action="SetRustCleanupData" Before="RustCleanup" Condition="(REMOVE="ALL")"/>
|
||||
<Custom Action="RustCleanup" Before="RemoveFolders" Condition="(REMOVE="ALL")"/>
|
||||
</InstallExecuteSequence>
|
||||
</Package>
|
||||
|
||||
@@ -155,49 +152,6 @@
|
||||
|
||||
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal"/>
|
||||
|
||||
<Dialog Id="InstallPrerequisitesDlg" Width="370" Height="270" Title="!(loc.VerifyReadyDlg_Title)">
|
||||
|
||||
<Control Id="InstallTitle" Type="Text" X="15" Y="15" Width="300" Height="15" Transparent="yes"
|
||||
NoPrefix="yes" Text="{\WixUI_Font_Title}!(loc.InstallPrerequisitesDlgInstallTitle)"/>
|
||||
<Control Id="InstallText" Type="Text" X="25" Y="70" Width="320" Height="80"
|
||||
Text="!(loc.InstallPrerequisitesDlgInstallText) [MISSING_DEPENDENCIES]"/>
|
||||
<Control Id="InstallProgressBar" Type="ProgressBar" X="25" Y="180" Width="320" Height="16"
|
||||
Property="MISSING_DEPENDENCIES_PROGRESS"/>
|
||||
<!-- MISSING_DEPENDENCIES_STARTED, MISSING_DEPENDENCIES_COMPLETE, MISSING_DEPENDENCIES_PROGRESS, MISSING_DEPENDENCIES -->
|
||||
|
||||
<Control Id="Continue" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17"
|
||||
Hidden="yes" Disabled="yes" Text="!(loc.InstallPrerequisitesDlgContinue)"
|
||||
ShowCondition="MISSING_DEPENDENCIES_COMPLETE" EnableCondition="MISSING_DEPENDENCIES_COMPLETE">
|
||||
<Publish Event="EndDialog" Value="Return" Condition="OutOfDiskSpace <> 1"/>
|
||||
<Publish Event="SpawnDialog" Value="OutOfRbDiskDlg"
|
||||
Condition="OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)"/>
|
||||
<Publish Event="EndDialog" Value="Return"
|
||||
Condition="OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D""/>
|
||||
<Publish Event="EnableRollback" Value="False"
|
||||
Condition="OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D""/>
|
||||
<Publish Event="SpawnDialog" Value="OutOfDiskDlg"
|
||||
Condition="(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")"/>
|
||||
</Control>
|
||||
|
||||
<Control Id="Install" Type="PushButton" ElevationShield="yes" X="212" Y="243" Width="80" Height="17"
|
||||
Default="yes" Hidden="yes" Disabled="yes" Text="!(loc.VerifyReadyDlgInstall)"
|
||||
ShowCondition="NOT MISSING_DEPENDENCIES_COMPLETE"
|
||||
EnableCondition="NOT MISSING_DEPENDENCIES_STARTED">
|
||||
|
||||
</Control>
|
||||
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes"
|
||||
Text="!(loc.WixUICancel)">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg"/>
|
||||
</Control>
|
||||
<Control Id="Back" Type="PushButton" X="156" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)"/>
|
||||
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no"
|
||||
Text="!(loc.VerifyReadyDlgBannerBitmap)"/>
|
||||
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="373" Height="0"/>
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="373" Height="0"/>
|
||||
|
||||
</Dialog>
|
||||
|
||||
<DialogRef Id="BrowseDlg"/>
|
||||
<DialogRef Id="DiskCostDlg"/>
|
||||
<DialogRef Id="ErrorDlg"/>
|
||||
@@ -209,22 +163,39 @@
|
||||
<DialogRef Id="ResumeDlg"/>
|
||||
<DialogRef Id="UserExit"/>
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"
|
||||
Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1""/>
|
||||
Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>1"/>
|
||||
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999"/>
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="{{WelcomeNextPage}}"
|
||||
Condition="NOT Installed"/>
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg"
|
||||
Condition="Installed AND PATCH"/>
|
||||
|
||||
{{#if HasLicense}}
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg" Condition="NOT Installed"/>
|
||||
{{else}}
|
||||
{{#if InstallLocationEither}}
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallScopeDlg" Condition="NOT Installed"/>
|
||||
{{else}}
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Condition="NOT Installed"/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Condition="Installed AND PATCH"/>
|
||||
|
||||
{{#if HasLicense}}
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg"/>
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="{{LicenseNextPage}}"
|
||||
Condition="LicenseAccepted = "1""/>
|
||||
{{#if InstallLocationEither}}
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="InstallScopeDlg"
|
||||
Condition="LicenseAccepted = "1""/>
|
||||
{{else}}
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg"
|
||||
Condition="LicenseAccepted = "1""/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if InstallLocationEither}}
|
||||
<Publish Dialog="InstallScopeDlg" Control="Back" Event="NewDialog" Value="{{InstallScopePrevPage}}"/>
|
||||
{{#if HasLicense}}
|
||||
<Publish Dialog="InstallScopeDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg"/>
|
||||
{{else}}
|
||||
<Publish Dialog="InstallScopeDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg"/>
|
||||
{{/if}}
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Property="WixAppFolder" Value="WixPerUserFolder"
|
||||
Order="1" Condition="!(wix.WixUISupportPerUser) AND NOT Privileged"/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Property="ALLUSERS" Value="{}" Order="2"
|
||||
@@ -232,15 +203,14 @@
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Property="ALLUSERS" Value="1" Order="3"
|
||||
Condition="WixAppFolder = "WixPerMachineFolder""/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Property="INSTALLFOLDER"
|
||||
Value="[LocalAppDataFolder][ApplicationFolderName]" Order=" 4"
|
||||
Value="[LocalAppDataFolder][ApplicationFolderName]" Order="4"
|
||||
Condition="WixAppFolder = "WixPerUserFolder""/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Property="INSTALLFOLDER"
|
||||
Value="{{ProgramFilesFolderName}}[ApplicationFolderName]" Order="5"
|
||||
Condition="WixAppFolder = "WixPerMachineFolder""/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Event="SetTargetPath" Value="INSTALLFOLDER" Order="6"/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="7"/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Event="DoAction" Value="FindRelatedProducts"
|
||||
Order="8"/>
|
||||
<Publish Dialog="InstallScopeDlg" Control="Next" Event="DoAction" Value="FindRelatedProducts" Order="8"/>
|
||||
{{/if}}
|
||||
|
||||
{{#if InstallLocationCurrentUserOnly}}
|
||||
@@ -260,19 +230,25 @@
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="SetTargetPath" Value="INSTALLFOLDER" Order="3"/>
|
||||
{{/if}}
|
||||
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="{{VerifyReadyPrevPage}}" Order="1"
|
||||
Condition="NOT Installed"/>
|
||||
{{#if InstallLocationEither}}
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallScopeDlg" Order="1"
|
||||
Condition="NOT Installed"/>
|
||||
{{else}}
|
||||
{{#if HasLicense}}
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg" Order="1"
|
||||
Condition="NOT Installed"/>
|
||||
{{else}}
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="1"
|
||||
Condition="NOT Installed"/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2"
|
||||
Condition="Installed AND NOT PATCH"/>
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2"
|
||||
Condition="Installed AND PATCH"/>
|
||||
|
||||
<!-- <Publish Dialog="VerifyReadyDlg" Control="Install" Event="DoAction" Value="RustCheckMissing" Order="1"/>-->
|
||||
<!-- <Publish Dialog="VerifyReadyDlg" Control="Install" Event="NewDialog" Value="InstallPrerequisitesDlg"-->
|
||||
<!-- Order="1"/>-->
|
||||
|
||||
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg"/>
|
||||
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg"/>
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg"/>
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg"/>
|
||||
|
||||
25
src/wix-dll/Cargo.toml
Normal file
25
src/wix-dll/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
[package]
|
||||
name = "velopack_wix"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
velopack.workspace = true
|
||||
velopack_bins.workspace = true
|
||||
remove_dir_all.workspace = true
|
||||
windows = { workspace = true, features = ["Win32_Foundation"] }
|
||||
101
src/wix-dll/src/lib.rs
Normal file
101
src/wix-dll/src/lib.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
mod msi;
|
||||
use msi::*;
|
||||
|
||||
use std::{ffi::c_uint, path::PathBuf};
|
||||
use velopack::process;
|
||||
use velopack_bins::{dialogs, windows::prerequisite};
|
||||
use windows::Win32::{
|
||||
Foundation::{ERROR_INSTALL_USEREXIT, ERROR_SUCCESS},
|
||||
System::ApplicationInstallationAndServicing::MSIHANDLE,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn EarlyBootstrap(h_install: MSIHANDLE) -> c_uint {
|
||||
let dependencies = msi_get_property(h_install, "RustRuntimeDependencies");
|
||||
let app_name = msi_get_property(h_install, "RustAppTitle");
|
||||
let app_version = msi_get_property(h_install, "RustAppVersion");
|
||||
|
||||
show_debug_message(
|
||||
"EarlyBootstrap",
|
||||
format!("RustRuntimeDependencies={:?} RustAppTitle={:?} RustAppVersion={:?}", dependencies, app_name, app_version),
|
||||
);
|
||||
|
||||
if let Some(dependencies) = dependencies {
|
||||
let app_name = app_name.unwrap_or("Application".into());
|
||||
let app_version = app_version.unwrap_or("0.0.0".into());
|
||||
match prerequisite::prompt_and_install_all_missing(&app_name, &app_version, &dependencies, None) {
|
||||
Ok(true) => ERROR_SUCCESS.0,
|
||||
Ok(false) => ERROR_INSTALL_USEREXIT.0,
|
||||
Err(e) => {
|
||||
let title = format!("{} Setup", app_name);
|
||||
let err = format!("An error occurred: {}", e);
|
||||
dialogs::show_error(&title, Some("Setup can not continue"), &err);
|
||||
ERROR_INSTALL_USEREXIT.0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ERROR_SUCCESS.0
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn CleanupDeferred(h_install: MSIHANDLE) -> c_uint {
|
||||
let custom_data = msi_get_property(h_install, "CustomActionData");
|
||||
show_debug_message("CleanupDeferred", format!("CustomActionData={:?}", custom_data));
|
||||
|
||||
if let Some(custom_data) = custom_data {
|
||||
// custom data will be a list delimited by " (0x22)
|
||||
let mut custom_data = custom_data.split('"');
|
||||
let install_dir = custom_data.next();
|
||||
let app_id = custom_data.next();
|
||||
|
||||
show_debug_message("CleanupDeferred", format!("install_dir={:?}, app_id={:?}", install_dir, app_id));
|
||||
|
||||
if let Some(install_dir) = install_dir {
|
||||
if let Err(e) = remove_dir_all::remove_dir_all(install_dir) {
|
||||
show_debug_message("CleanupDeferred", format!("Failed to remove install directory: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(app_id) = app_id {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
if let Err(e) = remove_dir_all::remove_dir_all(temp_dir.join(format!("velopack_{}", app_id))) {
|
||||
show_debug_message("CleanupDeferred", format!("Failed to remove temp directory: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
show_debug_message("CleanupDeferred", "Done!".to_string());
|
||||
}
|
||||
|
||||
ERROR_SUCCESS.0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn LaunchApplication(h_install: MSIHANDLE) -> c_uint {
|
||||
let install_dir = msi_get_property(h_install, "INSTALLFOLDER");
|
||||
let stub_file = msi_get_property(h_install, "RustStubFileName");
|
||||
|
||||
show_debug_message("LaunchApplication", format!("INSTALLFOLDER={:?}, RustStubFileName={:?}", install_dir, stub_file));
|
||||
|
||||
if let Some(install_dir) = install_dir {
|
||||
if let Some(stub_file) = stub_file {
|
||||
let stub_path = PathBuf::from(&install_dir).join(stub_file);
|
||||
if let Err(e) = process::run_process(stub_path, vec![], Some(&install_dir), false, None) {
|
||||
show_debug_message("LaunchApplication", format!("Failed to launch application: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_SUCCESS.0
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn show_debug_message(fn_name: &str, message: String) {
|
||||
let message = format!("{}: {}", fn_name, message);
|
||||
dialogs::show_warn(fn_name, None, &message);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn show_debug_message(fn_name: &str, message: String) {
|
||||
// no-op
|
||||
}
|
||||
164
src/wix-dll/src/msi.rs
Normal file
164
src/wix-dll/src/msi.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
#![allow(dead_code)]
|
||||
use velopack::wide_strings::*;
|
||||
use windows::{
|
||||
core::PWSTR,
|
||||
Win32::{Foundation::ERROR_SUCCESS, System::ApplicationInstallationAndServicing::*, UI::WindowsAndMessaging::*},
|
||||
};
|
||||
|
||||
pub fn msi_get_property<S: AsRef<str>>(h_install: MSIHANDLE, name: S) -> Option<String> {
|
||||
let name = string_to_wide(name.as_ref());
|
||||
let mut empty = string_to_wide("");
|
||||
let mut size = 0u32;
|
||||
|
||||
unsafe {
|
||||
let _ = MsiGetPropertyW(h_install, name.as_pcwstr(), Some(empty.as_pwstr()), Some(&mut size));
|
||||
// show_error(h_install, format!("prop1: {ret} size1: {size}")); //234
|
||||
|
||||
if size == 0 {
|
||||
return None; // No data found
|
||||
}
|
||||
|
||||
size += 1; // +1 for null terminator
|
||||
|
||||
let mut buf = vec![0u16; size as usize];
|
||||
let ret2 = MsiGetPropertyW(h_install, name.as_pcwstr(), Some(PWSTR(buf.as_mut_ptr())), Some(&mut size));
|
||||
// show_error(h_install, format!("prop2: {ret2} size2: {size}")); //234
|
||||
|
||||
if ret2 == ERROR_SUCCESS.0 {
|
||||
Some(wide_to_string_lossy(buf))
|
||||
} else {
|
||||
None // Failed to get property
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msi_set_property_string<S1: AsRef<str>, S2: AsRef<str>>(h_install: MSIHANDLE, name: S1, value: S2) {
|
||||
let name = string_to_wide(name.as_ref());
|
||||
let value = string_to_wide(value.as_ref());
|
||||
unsafe {
|
||||
let _ = MsiSetPropertyW(h_install, name.as_pcwstr(), value.as_pcwstr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msi_set_property_bool<S1: AsRef<str>>(h_install: MSIHANDLE, name: S1, value: bool) {
|
||||
let name = string_to_wide(name.as_ref());
|
||||
let value = string_to_wide(if value { "1" } else { "" });
|
||||
unsafe {
|
||||
let _ = MsiSetPropertyW(h_install, name.as_pcwstr(), value.as_pcwstr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msi_set_property_i32<S1: AsRef<str>>(h_install: MSIHANDLE, name: S1, value: i32) {
|
||||
let name = string_to_wide(name.as_ref());
|
||||
let value = string_to_wide(value.to_string());
|
||||
unsafe {
|
||||
let _ = MsiSetPropertyW(h_install, name.as_pcwstr(), value.as_pcwstr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msi_show_question<S: AsRef<str>>(h_install: MSIHANDLE, message: S) -> bool {
|
||||
let isnt_message = INSTALLMESSAGE_USER.0 | MB_OKCANCEL.0 as i32 | MB_ICONQUESTION.0 as i32;
|
||||
let res = unsafe { show_dialog_impl(h_install, message, isnt_message) };
|
||||
res == IDOK.0
|
||||
}
|
||||
|
||||
pub fn msi_show_info<S: AsRef<str>>(h_install: MSIHANDLE, message: S) {
|
||||
let isnt_message = INSTALLMESSAGE_USER.0 | MB_OK.0 as i32 | MB_ICONINFORMATION.0 as i32;
|
||||
unsafe { show_dialog_impl(h_install, message, isnt_message) };
|
||||
}
|
||||
|
||||
pub fn msi_show_warn<S: AsRef<str>>(h_install: MSIHANDLE, message: S) {
|
||||
let isnt_message = INSTALLMESSAGE_USER.0 | MB_OK.0 as i32 | MB_ICONWARNING.0 as i32;
|
||||
unsafe { show_dialog_impl(h_install, message, isnt_message) };
|
||||
}
|
||||
|
||||
pub fn msi_show_error<S: AsRef<str>>(h_install: MSIHANDLE, message: S) {
|
||||
let isnt_message = INSTALLMESSAGE_ERROR.0 | MB_OK.0 as i32 | MB_ICONERROR.0 as i32;
|
||||
unsafe { show_dialog_impl(h_install, message, isnt_message) };
|
||||
}
|
||||
|
||||
unsafe fn show_dialog_impl<S: AsRef<str>>(h_install: MSIHANDLE, message: S, flags: i32) -> i32 {
|
||||
let message = string_to_wide(message.as_ref());
|
||||
let rec = MsiCreateRecord(1);
|
||||
MsiRecordSetStringW(rec, 0, message.as_pcwstr());
|
||||
let ret = MsiProcessMessage(h_install, INSTALLMESSAGE(flags), rec);
|
||||
MsiCloseHandle(rec);
|
||||
ret
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/msiquery/nf-msiquery-msiprocessmessage#record-fields-for-progress-bar-messages
|
||||
// https://learn.microsoft.com/en-us/windows/win32/msi/adding-custom-actions-to-the-progressbar
|
||||
pub struct ProgressContext {
|
||||
h_install: MSIHANDLE,
|
||||
current_ticks: i32,
|
||||
total_ticks: i32,
|
||||
}
|
||||
|
||||
impl ProgressContext {
|
||||
pub fn new(h_install: MSIHANDLE, jobs: usize) -> Self {
|
||||
let jobs = jobs as i32; // Convert job index to i32 for calculations
|
||||
Self { h_install, current_ticks: 0, total_ticks: 100 * jobs }
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
unsafe {
|
||||
progress_reset(self.h_install, self.total_ticks);
|
||||
self.current_ticks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_progress(&mut self, progress: i32, job: usize) {
|
||||
let job = job as i32; // Convert job index to i32 for calculations
|
||||
let progress = progress + (job * 100); // Adjust progress based on the job index
|
||||
unsafe {
|
||||
if progress > self.current_ticks {
|
||||
// If the progress is greater than the current ticks, we increment the progress bar
|
||||
let diff = progress - self.current_ticks;
|
||||
progress_increment(self.h_install, diff);
|
||||
self.current_ticks += diff;
|
||||
}
|
||||
// else {
|
||||
// // If the progress is less than the current ticks, we reset the progress bar
|
||||
// progress_reset(self.h_install, self.total_ticks);
|
||||
// progress_increment(self.h_install, progress);
|
||||
// self.current_ticks = progress;
|
||||
// }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn progress_reset(h_install: MSIHANDLE, ticks: i32) {
|
||||
let rec = MsiCreateRecord(3);
|
||||
MsiRecordSetInteger(rec, 1, 0); // reset command
|
||||
MsiRecordSetInteger(rec, 2, ticks); // expected number of ticks
|
||||
MsiRecordSetInteger(rec, 3, 0); // forward progress bar (left to right)
|
||||
MsiProcessMessage(h_install, INSTALLMESSAGE_PROGRESS, rec);
|
||||
MsiCloseHandle(rec);
|
||||
}
|
||||
|
||||
// unsafe fn progress_set_explicit_progress(h_install: MSIHANDLE) {
|
||||
// let rec = MsiCreateRecord(3);
|
||||
// MsiRecordSetInteger(rec, 1, 1); // information command
|
||||
// MsiRecordSetInteger(rec, 2, 1); // explicit progress
|
||||
// MsiRecordSetInteger(rec, 3, 0); // unused
|
||||
// MsiProcessMessage(h_install, INSTALLMESSAGE_PROGRESS, rec);
|
||||
// MsiCloseHandle(rec);
|
||||
// }
|
||||
|
||||
unsafe fn progress_increment(h_install: MSIHANDLE, ticks: i32) {
|
||||
let rec = MsiCreateRecord(3);
|
||||
MsiRecordSetInteger(rec, 1, 2); // increment command
|
||||
MsiRecordSetInteger(rec, 2, ticks); // ticks to increment
|
||||
MsiRecordSetInteger(rec, 3, 0); // unused
|
||||
MsiProcessMessage(h_install, INSTALLMESSAGE_PROGRESS, rec);
|
||||
MsiCloseHandle(rec);
|
||||
}
|
||||
|
||||
// unsafe fn progress_add_extra_ticks(h_install: MSIHANDLE, ticks: i32) {
|
||||
// let rec = MsiCreateRecord(3);
|
||||
// MsiRecordSetInteger(rec, 1, 3); // add ticks command
|
||||
// MsiRecordSetInteger(rec, 2, ticks); // ticks to add to the total progress
|
||||
// MsiRecordSetInteger(rec, 3, 0); // unused
|
||||
// MsiProcessMessage(h_install, INSTALLMESSAGE_PROGRESS, rec);
|
||||
// MsiCloseHandle(rec);
|
||||
// }
|
||||
Reference in New Issue
Block a user