mirror of
https://gitlab.com/cleanflash/installer.git
synced 2026-08-13 02:41:38 +08:00
Implement CLI and silent install / uninstall
This commit is contained in:
@@ -1,50 +1,238 @@
|
||||
use crate::{process_utils, system_info, InstallError};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use crate::{system_info, InstallError};
|
||||
|
||||
/// Apply registry contents by writing a .reg file and importing with reg.exe.
|
||||
/// Apply registry entries using native Windows registry APIs.
|
||||
///
|
||||
/// The entries are in .reg file format (without the header line).
|
||||
/// Supported syntax:
|
||||
/// - `[HKEY_xxx\path]` — create/open key
|
||||
/// - `[-HKEY_xxx\path]` — delete entire key tree
|
||||
/// - `"name"="string_value"` — set REG_SZ value
|
||||
/// - `"name"=dword:XXXXXXXX` — set REG_DWORD value
|
||||
/// - `"name"=-` — delete a value
|
||||
/// - `@="string_value"` — set the default (unnamed) value
|
||||
#[cfg(windows)]
|
||||
pub fn apply_registry(entries: &[&str]) -> Result<(), InstallError> {
|
||||
let combined = entries.join("\n\n");
|
||||
let filled = system_info::fill_string(&combined);
|
||||
apply_reg_content(&filled)
|
||||
}
|
||||
|
||||
let content = format!("Windows Registry Editor Version 5.00\n\n{}", filled);
|
||||
#[cfg(windows)]
|
||||
fn apply_reg_content(content: &str) -> Result<(), InstallError> {
|
||||
use windows_sys::Win32::System::Registry::{
|
||||
RegCloseKey, RegCreateKeyExW, RegDeleteValueW, RegSetValueExW,
|
||||
KEY_WOW64_64KEY, KEY_WRITE, REG_DWORD, REG_OPTION_NON_VOLATILE, REG_SZ,
|
||||
};
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
// Include the process ID to avoid collisions when two instances run concurrently,
|
||||
// matching the unique-file guarantee of C#'s Path.GetTempFileName().
|
||||
let reg_file = temp_dir.join(format!("cleanflash_reg_{}.tmp", std::process::id()));
|
||||
let mut current_hkey: Option<windows_sys::Win32::System::Registry::HKEY> = None;
|
||||
|
||||
// Write as UTF-16LE with BOM (Windows .reg format).
|
||||
{
|
||||
let mut f = fs::File::create(®_file)
|
||||
.map_err(|e| InstallError::new(format!("Failed to create temp reg file: {}", e)))?;
|
||||
let utf16: Vec<u16> = content.encode_utf16().collect();
|
||||
// BOM
|
||||
f.write_all(&[0xFF, 0xFE])
|
||||
.map_err(|e| InstallError::new(format!("Failed to write BOM: {}", e)))?;
|
||||
for word in &utf16 {
|
||||
f.write_all(&word.to_le_bytes())
|
||||
.map_err(|e| InstallError::new(format!("Failed to write reg data: {}", e)))?;
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete key: [-HKEY_xxx\path]
|
||||
if line.starts_with("[-") && line.ends_with(']') {
|
||||
if let Some(hk) = current_hkey.take() {
|
||||
unsafe { RegCloseKey(hk) };
|
||||
}
|
||||
|
||||
let key_path = &line[2..line.len() - 1];
|
||||
if let Some((root, subkey)) = parse_root_and_subkey(key_path) {
|
||||
delete_key_tree(root, subkey);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Open/create key: [HKEY_xxx\path]
|
||||
if line.starts_with('[') && line.ends_with(']') {
|
||||
if let Some(hk) = current_hkey.take() {
|
||||
unsafe { RegCloseKey(hk) };
|
||||
}
|
||||
|
||||
let key_path = &line[1..line.len() - 1];
|
||||
if let Some((root, subkey)) = parse_root_and_subkey(key_path) {
|
||||
let subkey_wide: Vec<u16> =
|
||||
subkey.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
unsafe {
|
||||
let mut hkey = std::ptr::null_mut();
|
||||
let mut disposition: u32 = 0;
|
||||
let result = RegCreateKeyExW(
|
||||
root,
|
||||
subkey_wide.as_ptr(),
|
||||
0,
|
||||
std::ptr::null(),
|
||||
REG_OPTION_NON_VOLATILE,
|
||||
KEY_WRITE | KEY_WOW64_64KEY,
|
||||
std::ptr::null(),
|
||||
&mut hkey,
|
||||
&mut disposition,
|
||||
);
|
||||
if result == 0 {
|
||||
current_hkey = Some(hkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Value operations require an open key.
|
||||
let Some(hkey) = current_hkey else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Default value: @="value"
|
||||
if let Some(rest) = line.strip_prefix("@=") {
|
||||
if let Some(val) = parse_reg_string(rest) {
|
||||
let val_wide: Vec<u16> =
|
||||
val.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
unsafe {
|
||||
RegSetValueExW(
|
||||
hkey,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
REG_SZ as u32,
|
||||
val_wide.as_ptr() as *const u8,
|
||||
(val_wide.len() * 2) as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Named value: "name"=...
|
||||
if !line.starts_with('"') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some((name, value_part)) = parse_name_and_value(line) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let name_wide: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
|
||||
// Delete value: "name"=-
|
||||
if value_part == "-" {
|
||||
unsafe {
|
||||
RegDeleteValueW(hkey, name_wide.as_ptr());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// DWORD value: "name"=dword:XXXXXXXX
|
||||
if let Some(hex_str) = value_part.strip_prefix("dword:") {
|
||||
if let Ok(dword_val) = u32::from_str_radix(hex_str, 16) {
|
||||
unsafe {
|
||||
RegSetValueExW(
|
||||
hkey,
|
||||
name_wide.as_ptr(),
|
||||
0,
|
||||
REG_DWORD as u32,
|
||||
&dword_val as *const u32 as *const u8,
|
||||
4,
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// String value: "name"="value"
|
||||
if let Some(val) = parse_reg_string(value_part) {
|
||||
let val_wide: Vec<u16> = val.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
unsafe {
|
||||
RegSetValueExW(
|
||||
hkey,
|
||||
name_wide.as_ptr(),
|
||||
0,
|
||||
REG_SZ as u32,
|
||||
val_wide.as_ptr() as *const u8,
|
||||
(val_wide.len() * 2) as u32,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let reg_filename = reg_file.to_string_lossy().to_string();
|
||||
let result = process_utils::run_process("reg.exe", &["import", ®_filename]);
|
||||
|
||||
let _ = fs::remove_file(®_file);
|
||||
|
||||
if !result.is_successful() {
|
||||
return Err(InstallError::new(format!(
|
||||
"Failed to apply changes to registry: error code {}\n\n{}",
|
||||
result.exit_code, result.output
|
||||
)));
|
||||
if let Some(hk) = current_hkey {
|
||||
unsafe { RegCloseKey(hk) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse a root key name and subkey path from a combined string like
|
||||
/// `HKEY_LOCAL_MACHINE\Software\Something`.
|
||||
#[cfg(windows)]
|
||||
fn parse_root_and_subkey(
|
||||
path: &str,
|
||||
) -> Option<(windows_sys::Win32::System::Registry::HKEY, &str)> {
|
||||
use windows_sys::Win32::System::Registry::{
|
||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS,
|
||||
};
|
||||
|
||||
let (root_name, subkey) = path.split_once('\\')?;
|
||||
let root = match root_name {
|
||||
"HKEY_LOCAL_MACHINE" => HKEY_LOCAL_MACHINE,
|
||||
"HKEY_CURRENT_USER" => HKEY_CURRENT_USER,
|
||||
"HKEY_CLASSES_ROOT" => HKEY_CLASSES_ROOT,
|
||||
"HKEY_USERS" => HKEY_USERS,
|
||||
_ => return None,
|
||||
};
|
||||
Some((root, subkey))
|
||||
}
|
||||
|
||||
/// Delete an entire registry key tree.
|
||||
#[cfg(windows)]
|
||||
fn delete_key_tree(root: windows_sys::Win32::System::Registry::HKEY, subkey: &str) {
|
||||
use windows_sys::Win32::System::Registry::{
|
||||
RegCloseKey, RegDeleteTreeW, RegOpenKeyExW, KEY_WOW64_64KEY, KEY_WRITE,
|
||||
};
|
||||
|
||||
if let Some((parent, leaf)) = subkey.rsplit_once('\\') {
|
||||
let parent_wide: Vec<u16> = parent.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let leaf_wide: Vec<u16> = leaf.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
unsafe {
|
||||
let mut hkey = std::ptr::null_mut();
|
||||
let result =
|
||||
RegOpenKeyExW(root, parent_wide.as_ptr(), 0, KEY_WRITE | KEY_WOW64_64KEY, &mut hkey);
|
||||
if result == 0 {
|
||||
RegDeleteTreeW(hkey, leaf_wide.as_ptr());
|
||||
RegCloseKey(hkey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let subkey_wide: Vec<u16> = subkey.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
unsafe {
|
||||
RegDeleteTreeW(root, subkey_wide.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a quoted string value: `"some value"` → `some value`.
|
||||
/// Handles escaped backslashes (`\\` → `\`) inside the string.
|
||||
#[cfg(windows)]
|
||||
fn parse_reg_string(s: &str) -> Option<String> {
|
||||
let s = s.trim();
|
||||
if !s.starts_with('"') || !s.ends_with('"') || s.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let inner = &s[1..s.len() - 1];
|
||||
Some(inner.replace("\\\\", "\\"))
|
||||
}
|
||||
|
||||
/// Parse `"name"=value` into `(name, value)`.
|
||||
#[cfg(windows)]
|
||||
fn parse_name_and_value(line: &str) -> Option<(String, &str)> {
|
||||
let rest = &line[1..]; // skip opening "
|
||||
let end_quote = rest.find('"')?;
|
||||
let name = &rest[..end_quote];
|
||||
let after_name = &rest[end_quote + 1..]; // skip closing "
|
||||
let value_part = after_name.strip_prefix('=')?;
|
||||
Some((name.to_string(), value_part))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn apply_registry(_entries: &[&str]) -> Result<(), InstallError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use std::fs;
|
||||
/// This removes the native messaging host binary, the Pepper (pp64) files
|
||||
/// installed alongside it, and the browser manifests.
|
||||
pub fn uninstall(form: &dyn ProgressCallback) -> Result<(), InstallError> {
|
||||
form.update_progress_label("Removing native messaging host...", true);
|
||||
native_host::uninstall_native_host(form);
|
||||
|
||||
// Remove the entire install directory (host binary + pp64 files).
|
||||
|
||||
Reference in New Issue
Block a user