Fix Windows installer cleanup and preflight

This commit is contained in:
PerishCode
2026-05-07 13:36:06 +08:00
parent fa3bf99bd7
commit c9b1e1a315
3 changed files with 179 additions and 38 deletions
+77 -12
View File
@@ -128,6 +128,7 @@ RequestExecutionLevel user
!include "MUI2.nsh"
!include "LogicLib.nsh"
!include "nsDialogs.nsh"
!include "WinMessages.nsh"
Name "${productName}"
OutFile "\${OUTPUT_EXE}"
@@ -141,6 +142,7 @@ ShowUninstDetails hide
!define MUI_ABORTWARNING
!define MUI_ICON "\${APP_ICON}"
!define MUI_UNICON "\${APP_ICON}"
Page custom RunningInstancesPage RunningInstancesPageLeave
!insertmacro MUI_PAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DirectoryPageLeave
!insertmacro MUI_PAGE_DIRECTORY
@@ -163,7 +165,11 @@ ${createNsisLangString("RemoveDesktopShortcut", "Remove desktop shortcut", { LAN
${createNsisLangString("RemoveLocalData", "Delete local data for this installation", { LANG_SIMPCHINESE: "删除此安装的本地数据" })}
${createNsisLangString("UninstallOptionsTitle", "Uninstall options", { LANG_SIMPCHINESE: "卸载选项" })}
${createNsisLangString("UninstallOptionsSubtitle", "Choose which local items to remove.", { LANG_SIMPCHINESE: "选择要删除的本地项目。" })}
${createNsisLangString("RunningInstancesMessage", `${productName} is still running. Close all ${productName} windows and background processes, then choose Retry.`, { LANG_SIMPCHINESE: `${productName} 仍在运行。请关闭所有 ${productName} 窗口和后台进程,然后选择重试。` })}
${createNsisLangString("RunningInstancesTitle", `${productName} is still running`, { LANG_SIMPCHINESE: `${productName} 仍在运行` })}
${createNsisLangString("RunningInstancesSubtitle", "Close it before continuing installation.", { LANG_SIMPCHINESE: "继续安装前需要关闭它。" })}
${createNsisLangString("RunningInstancesMessage", `${productName} must be closed before installation can continue.`, { LANG_SIMPCHINESE: `继续安装前需要关闭 ${productName}。` })}
${createNsisLangString("CloseAndContinue", "Close and continue", { LANG_SIMPCHINESE: "关闭并继续" })}
${createNsisLangString("RunningInstancesCloseFailed", `${productName} could not be closed. Close it manually, then try again.`, { LANG_SIMPCHINESE: `无法关闭 ${productName}。请手动关闭后重试。` })}
${createNsisLangString("RunningInstancesSilentAbort", `${productName} is still running. Close it before running the installer silently.`, { LANG_SIMPCHINESE: `${productName} 仍在运行。请先关闭它,再运行静默安装。` })}
${createNsisLangString("ExistingInstallMessage", `${productName} is already installed in the selected folder. Choose OK to overwrite it, or Cancel to stop installation.`, { LANG_SIMPCHINESE: `所选文件夹中已经安装了 ${productName}。选择确定覆盖,或取消安装。` })}
${createNsisLangString("ExistingInstallSilentOverwrite", "Existing installation found; silent install will overwrite it.", { LANG_SIMPCHINESE: "发现已有安装;静默安装将覆盖它。" })}
@@ -173,6 +179,7 @@ Var RemoveLocalDataCheckbox
Var RemoveDesktopShortcutState
Var RemoveLocalDataState
Var RunningInstancesOutput
Var ExistingInstallLocation
Function LogInstallerEvent
Exch $0
@@ -208,7 +215,7 @@ FunctionEnd
Function DetectRunningInstances
Push $0
Push $1
nsExec::ExecToStack 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$ns = ''${namespace}''; $flag = ''--od-stamp-namespace='' + $ns; $install = ''$INSTDIR''.ToLowerInvariant(); $matches = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ( $_.CommandLine.Contains($flag) -or ( $install.Length -gt 0 -and $_.ExecutablePath -and $_.ExecutablePath.ToLowerInvariant().StartsWith($install) ) ) }; if ($matches) { ($matches | ForEach-Object { [string]$_.ProcessId + '' '' + $_.Name }) -join ''; '' }"'
nsExec::ExecToStack 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "& { param($$install, $$registered); $$paths = @($$install, $$registered) | Where-Object { $$_ } | ForEach-Object { $$_.TrimEnd([char]92).ToLowerInvariant() } | Select-Object -Unique; $$matches = Get-CimInstance Win32_Process | Where-Object { $$matched = $$false; $$exe = $$_.ExecutablePath; if ($$null -ne $$exe) { $$exe = $$exe.ToLowerInvariant(); foreach ($$path in $$paths) { if ($$path -and $$exe.StartsWith($$path)) { $$matched = $$true; break } } }; $$matched }; if ($$matches) { $$matches | ForEach-Object { [string]$$_.ProcessId + [char]32 + $$_.Name } } }" "$INSTDIR" "$ExistingInstallLocation"'
Pop $0
Pop $1
\${If} $0 == "0"
@@ -222,18 +229,29 @@ Function DetectRunningInstances
Pop $0
FunctionEnd
Function CloseRunningInstances
Push $0
Push $1
nsExec::ExecToStack 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "& { param($$install, $$registered); $$paths = @($$install, $$registered) | Where-Object { $$_ } | ForEach-Object { $$_.TrimEnd([char]92).ToLowerInvariant() } | Select-Object -Unique; $$matches = Get-CimInstance Win32_Process | Where-Object { $$matched = $$false; $$exe = $$_.ExecutablePath; if ($$null -ne $$exe) { $$exe = $$exe.ToLowerInvariant(); foreach ($$path in $$paths) { if ($$path -and $$exe.StartsWith($$path)) { $$matched = $$true; break } } }; $$matched }; $$ids = @($$matches | ForEach-Object { $$_.ProcessId }); foreach ($$id in $$ids) { try { [void][System.Diagnostics.Process]::GetProcessById($$id).CloseMainWindow() } catch {} }; Start-Sleep -Milliseconds 1500; foreach ($$id in $$ids) { try { $$p = [System.Diagnostics.Process]::GetProcessById($$id); if (-not $$p.HasExited) { Stop-Process -Id $$id -Force -ErrorAction SilentlyContinue } } catch {} }; if ($$ids) { $$ids -join ([char]32) } }" "$INSTDIR" "$ExistingInstallLocation"'
Pop $0
Pop $1
Push "running instances close exit=$0 output=$1"
Call LogInstallerEvent
Pop $1
Pop $0
FunctionEnd
Function .onInit
SetShellVarContext current
ReadRegStr $ExistingInstallLocation HKCU "${registryKey}" "InstallLocation"
check_running:
IfSilent silent_check no_existing_install
silent_check:
Call DetectRunningInstances
\${If} $RunningInstancesOutput != ""
IfSilent 0 interactive_running
Push "install aborted: running instances detected: $RunningInstancesOutput"
Call LogInstallerEvent
Abort "$(RunningInstancesSilentAbort)"
interactive_running:
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(RunningInstancesMessage)$\\r$\\n$\\r$\\n$RunningInstancesOutput" IDRETRY check_running IDCANCEL cancel_install
Push "install aborted: running instances detected: $RunningInstancesOutput"
Call LogInstallerEvent
Abort "$(RunningInstancesSilentAbort)"
\${EndIf}
IfFileExists "$INSTDIR\\${exeName}" existing_install no_existing_install
@@ -251,6 +269,45 @@ cancel_install:
no_existing_install:
FunctionEnd
Function RunningInstancesPage
IfSilent done
Call DetectRunningInstances
\${If} $RunningInstancesOutput == ""
Abort
\${EndIf}
Push "running instances detected before install: $RunningInstancesOutput"
Call LogInstallerEvent
!insertmacro MUI_HEADER_TEXT "$(RunningInstancesTitle)" "$(RunningInstancesSubtitle)"
nsDialogs::Create 1018
Pop $0
\${If} $0 == error
Abort
\${EndIf}
\${NSD_CreateLabel} 0 0 100% 36u "$(RunningInstancesMessage)"
Pop $0
GetDlgItem $0 $HWNDPARENT 1
SendMessage $0 \${WM_SETTEXT} 0 "STR:$(CloseAndContinue)"
GetDlgItem $0 $HWNDPARENT 3
ShowWindow $0 0
nsDialogs::Show
done:
FunctionEnd
Function RunningInstancesPageLeave
Call CloseRunningInstances
Call DetectRunningInstances
\${If} $RunningInstancesOutput != ""
Push "running instances still detected after close: $RunningInstancesOutput"
Call LogInstallerEvent
MessageBox MB_OK|MB_ICONEXCLAMATION "$(RunningInstancesCloseFailed)"
Abort
\${EndIf}
FunctionEnd
Function DirectoryPageLeave
IfSilent done
IfFileExists "$INSTDIR\\${exeName}" existing_install done
@@ -265,12 +322,13 @@ FunctionEnd
Function CreateDesktopShortcut
SetShellVarContext current
SetOutPath "$INSTDIR"
CreateShortCut "$DESKTOP\\${shortcutName}" "$INSTDIR\\${exeName}" "" "$INSTDIR\\${exeName}" 0
FunctionEnd
Function RemoveInstallDir
Push $0
nsExec::ExecToLog 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "if (Test-Path -LiteralPath ''$INSTDIR'') { Remove-Item -LiteralPath ''$INSTDIR'' -Recurse -Force -ErrorAction SilentlyContinue }"'
nsExec::ExecToLog 'cmd.exe /d /s /c if exist "$INSTDIR" rmdir /s /q "$INSTDIR"'
Pop $0
Push "install dir remove exit=$0"
Call LogInstallerEvent
@@ -308,7 +366,7 @@ FunctionEnd
Function un.RemoveInstallDirContents
Push $0
nsExec::ExecToLog 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "if (Test-Path -LiteralPath ''$INSTDIR'') { Get-ChildItem -LiteralPath ''$INSTDIR'' -Force | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue }"'
nsExec::ExecToLog 'cmd.exe /d /s /c if exist "$INSTDIR" rmdir /s /q "$INSTDIR"'
Pop $0
Push "install dir fast remove exit=$0"
Call un.LogInstallerEvent
@@ -317,7 +375,7 @@ FunctionEnd
Function un.RemoveLocalDataRoot
Push $0
nsExec::ExecToLog 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "if (Test-Path -LiteralPath ''${localDataRoot}'') { Remove-Item -LiteralPath ''${localDataRoot}'' -Recurse -Force -ErrorAction SilentlyContinue }"'
nsExec::ExecToLog 'cmd.exe /d /s /c if exist "${localDataRoot}" rmdir /s /q "${localDataRoot}"'
Pop $0
Push "local data remove exit=$0"
Call un.LogInstallerEvent
@@ -352,6 +410,7 @@ prepare_install_dir:
\${EndIf}
WriteUninstaller "$INSTDIR\\${uninstallerName}"
SetOutPath "$INSTDIR"
IfSilent 0 skip_silent_desktop_shortcut
CreateShortCut "$DESKTOP\\${shortcutName}" "$INSTDIR\\${exeName}" "" "$INSTDIR\\${exeName}" 0
skip_silent_desktop_shortcut:
@@ -371,9 +430,15 @@ Section "Uninstall"
SetShellVarContext current
Push "uninstall section start"
Call un.LogInstallerEvent
IfSilent delete_desktop_shortcut check_desktop_shortcut_state
check_desktop_shortcut_state:
\${If} $RemoveDesktopShortcutState == \${BST_CHECKED}
Delete "$DESKTOP\\${shortcutName}"
\${EndIf}
Goto after_desktop_shortcut
delete_desktop_shortcut:
Delete "$DESKTOP\\${shortcutName}"
after_desktop_shortcut:
Delete "$SMPROGRAMS\\${shortcutName}"
DeleteRegKey HKCU "${registryKey}"
DeleteRegKey HKCU "${appPathsKey}"
+24 -20
View File
@@ -34,7 +34,7 @@ import {
resolveWinProductNamespaceRoot,
resolveWinProductUserDataRoot,
} from "./paths.js";
import { cleanupWinRegistryResidues, queryWinRegistryEntries } from "./registry.js";
import { cleanupWinRegistryResidues, queryWinRegistryEntries, resolveWinRegisteredPaths } from "./registry.js";
import type {
WinCleanupResult,
WinInspectResult,
@@ -137,7 +137,7 @@ async function observeWinResidues(config: ToolPackConfig, paths = resolveWinPath
productNamespaceRootExists: await pathExists(resolveWinProductNamespaceRoot(config)),
productUserDataRootExists: await pathExists(resolveWinProductUserDataRoot()),
publicDesktopShortcutExists: await pathExists(paths.publicDesktopShortcutPath),
registryResidues: (await queryWinRegistryEntries(paths)).map((entry) => entry.keyPath),
registryResidues: (await queryWinRegistryEntries(paths, config)).map((entry) => entry.keyPath),
runtimeNamespaceRootExists: await pathExists(config.roots.runtime.namespaceRoot),
startMenuShortcutExists: await pathExists(paths.startMenuShortcutPath),
uninstallerExists: await pathExists(paths.uninstallerPath),
@@ -147,18 +147,19 @@ async function observeWinResidues(config: ToolPackConfig, paths = resolveWinPath
export async function installPackedWinApp(config: ToolPackConfig): Promise<WinInstallResult> {
const paths = resolveWinPaths(config);
const registeredPaths = await resolveWinRegisteredPaths(config, paths);
if (!(await pathExists(paths.setupPath))) throw new Error(`no windows installer found at ${paths.setupPath}; run tools-pack win build first`);
if (await pathExists(paths.uninstallerPath)) {
if (await pathExists(registeredPaths.uninstallerPath)) {
await uninstallPackedWinApp(config);
} else {
await removeTree(paths.installDir);
await removeTree(registeredPaths.installDir);
}
await mkdir(dirname(paths.installDir), { recursive: true });
await runTimed(paths.installTimingPath, "install", async () => {
await invokeNsis(paths, paths.setupPath, installArgs(config, paths), "install");
});
if (!(await pathExists(paths.installedExePath))) throw new Error(`installer completed but executable is missing at ${paths.installedExePath}`);
const registryEntries = await queryWinRegistryEntries(paths);
const registryEntries = await queryWinRegistryEntries(paths, config);
const installPayload = await collectInstallPayloadReport(paths);
await writeJsonMarker(paths.installMarkerPath, {
installedAt: new Date().toISOString(),
@@ -283,14 +284,15 @@ export async function readPackedWinLogs(config: ToolPackConfig) {
export async function uninstallPackedWinApp(config: ToolPackConfig): Promise<WinUninstallResult> {
const paths = resolveWinPaths(config);
const registeredPaths = await resolveWinRegisteredPaths(config, paths);
const stop = await stopPackedWinApp(config);
if (await pathExists(paths.uninstallerPath)) {
if (await pathExists(registeredPaths.uninstallerPath)) {
await runTimed(paths.uninstallTimingPath, "uninstall", async () => {
await invokeNsis(paths, paths.uninstallerPath, config.silent ? ["/S"] : [], "uninstall");
await invokeNsis(paths, registeredPaths.uninstallerPath, config.silent ? ["/S"] : [], "uninstall");
});
}
await removeTree(paths.installDir);
const registryResiduesRemoved = await cleanupWinRegistryResidues(paths);
await removeTree(registeredPaths.installDir);
const registryResiduesRemoved = await cleanupWinRegistryResidues(registeredPaths, config);
const removalPlan = await createWinRemovalPlan(config);
await writeJsonMarker(paths.uninstallMarkerPath, {
namespace: config.namespace,
@@ -315,24 +317,25 @@ export async function uninstallPackedWinApp(config: ToolPackConfig): Promise<Win
removedProductUserDataRoot,
removedSidecarRoot,
removalPlan,
residueObservation: await observeWinResidues(config, paths),
residueObservation: await observeWinResidues(config, registeredPaths),
stop,
timingPath: paths.uninstallTimingPath,
uninstallerPath: paths.uninstallerPath,
uninstallerPath: registeredPaths.uninstallerPath,
};
}
export async function cleanupPackedWinNamespace(config: ToolPackConfig): Promise<WinCleanupResult> {
const paths = resolveWinPaths(config);
const registeredPaths = await resolveWinRegisteredPaths(config, paths);
const removalPlan = await createWinRemovalPlan(config);
if (await pathExists(paths.uninstallerPath)) {
if (await pathExists(registeredPaths.uninstallerPath)) {
await uninstallPackedWinApp(config);
}
const stop = await stopPackedWinApp(config);
const removedOutputRoot = await pathExists(config.roots.output.namespaceRoot);
const removedRuntimeNamespaceRoot = await pathExists(config.roots.runtime.namespaceRoot);
const removedProductUserDataRoot = removalPlan.some((target) => target.scope === "product-user-data" && target.willRemove && target.exists);
await cleanupWinRegistryResidues(paths);
await cleanupWinRegistryResidues(registeredPaths, config);
for (const target of removalPlan) {
if (target.scope === "product-user-data" && target.willRemove) await removeTree(target.path);
}
@@ -344,14 +347,15 @@ export async function cleanupPackedWinNamespace(config: ToolPackConfig): Promise
removedProductUserDataRoot,
removedRuntimeNamespaceRoot,
removalPlan,
residueObservation: await observeWinResidues(config, paths),
residueObservation: await observeWinResidues(config, registeredPaths),
stop,
};
}
export async function listPackedWinNamespaces(config: ToolPackConfig): Promise<WinListResult> {
const paths = resolveWinPaths(config);
const registryEntries = await queryWinRegistryEntries(paths);
const registeredPaths = await resolveWinRegisteredPaths(config, paths);
const registryEntries = await queryWinRegistryEntries(registeredPaths, config);
const productNamespaceRoot = resolveWinProductNamespaceRoot(config);
const productUserDataRoot = resolveWinProductUserDataRoot();
const builtManifest = await readBuiltAppManifest(paths, { requireExecutable: true });
@@ -360,9 +364,9 @@ export async function listPackedWinNamespaces(config: ToolPackConfig): Promise<W
builtExecutableExists: builtManifest != null || await pathExists(paths.unpackedExePath),
builtExecutablePath: builtManifest?.executablePath ?? ((await pathExists(paths.unpackedExePath)) ? paths.unpackedExePath : null),
builtManifestPath: paths.builtManifestPath,
installDir: paths.installDir,
installedExeExists: await pathExists(paths.installedExePath),
installedExePath: paths.installedExePath,
installDir: registeredPaths.installDir,
installedExeExists: await pathExists(registeredPaths.installedExePath),
installedExePath: registeredPaths.installedExePath,
namespace: config.namespace,
publicDesktopShortcutExists: await pathExists(paths.publicDesktopShortcutPath),
publicDesktopShortcutPath: paths.publicDesktopShortcutPath,
@@ -379,8 +383,8 @@ export async function listPackedWinNamespaces(config: ToolPackConfig): Promise<W
setupPath: paths.setupPath,
startMenuShortcutExists: await pathExists(paths.startMenuShortcutPath),
startMenuShortcutPath: paths.startMenuShortcutPath,
uninstallerExists: await pathExists(paths.uninstallerPath),
uninstallerPath: paths.uninstallerPath,
uninstallerExists: await pathExists(registeredPaths.uninstallerPath),
uninstallerPath: registeredPaths.uninstallerPath,
userDesktopShortcutExists: await pathExists(paths.userDesktopShortcutPath),
userDesktopShortcutPath: paths.userDesktopShortcutPath,
},
+78 -6
View File
@@ -1,7 +1,10 @@
import { execFile } from "node:child_process";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import type { ToolPackConfig } from "../config.js";
import { pathExists } from "./fs.js";
import { resolveWinInstallIdentity } from "./identity.js";
import type { WinPaths, WindowsUninstallRegistryEntry } from "./types.js";
const execFileAsync = promisify(execFile);
@@ -10,7 +13,7 @@ function normalizeRegistryPath(value: string | null | undefined): string {
return (value ?? "").replace(/[\\/]+$/, "").toLowerCase();
}
function stripRegistryQuotedValue(value: string | null | undefined): string {
export function stripRegistryQuotedValue(value: string | null | undefined): string {
const trimmed = (value ?? "").trim();
if (trimmed.startsWith('"')) {
const closingQuote = trimmed.indexOf('"', 1);
@@ -32,11 +35,27 @@ function createEmptyRegistryEntry(keyPath: string): WindowsUninstallRegistryEntr
};
}
function normalizeRegistryKeyPath(value: string): string {
return value
.replace(/^HKCU\\/i, "HKEY_CURRENT_USER\\")
.replace(/^HKLM\\/i, "HKEY_LOCAL_MACHINE\\")
.toLowerCase();
}
function namespaceRegistryKeyPath(config: Pick<ToolPackConfig, "namespace">): string {
return normalizeRegistryKeyPath(`HKCU\\${resolveWinInstallIdentity(config).registryKey}`);
}
async function execReg(args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
return await execFileAsync("reg.exe", args, { cwd, env: process.env, windowsHide: true });
}
function registryEntryMatches(paths: WinPaths, entry: WindowsUninstallRegistryEntry): boolean {
function registryEntryMatches(
paths: WinPaths,
entry: WindowsUninstallRegistryEntry,
config?: Pick<ToolPackConfig, "namespace">,
): boolean {
if (config != null && normalizeRegistryKeyPath(entry.keyPath) === namespaceRegistryKeyPath(config)) return true;
const targetInstallDir = normalizeRegistryPath(paths.installDir);
const targetUninstaller = normalizeRegistryPath(paths.uninstallerPath);
const installLocation = normalizeRegistryPath(entry.installLocation);
@@ -51,7 +70,10 @@ function registryEntryMatches(paths: WinPaths, entry: WindowsUninstallRegistryEn
);
}
export async function queryWinRegistryEntries(paths: WinPaths): Promise<WindowsUninstallRegistryEntry[]> {
export async function queryWinRegistryEntries(
paths: WinPaths,
config?: Pick<ToolPackConfig, "namespace">,
): Promise<WindowsUninstallRegistryEntry[]> {
const roots = [
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
@@ -67,7 +89,7 @@ export async function queryWinRegistryEntries(paths: WinPaths): Promise<WindowsU
}
let current: WindowsUninstallRegistryEntry | null = null;
const collect = () => {
if (current != null && registryEntryMatches(paths, current)) entries.push(current);
if (current != null && registryEntryMatches(paths, current, config)) entries.push(current);
};
for (const rawLine of stdout.split(/\r?\n/)) {
const line = rawLine.trimEnd();
@@ -94,8 +116,58 @@ export async function queryWinRegistryEntries(paths: WinPaths): Promise<WindowsU
return entries;
}
export async function cleanupWinRegistryResidues(paths: WinPaths): Promise<string[]> {
const entries = await queryWinRegistryEntries(paths);
export async function queryWinNamespaceRegistryEntry(
config: Pick<ToolPackConfig, "namespace">,
paths: WinPaths,
): Promise<WindowsUninstallRegistryEntry | null> {
const identity = resolveWinInstallIdentity(config);
let stdout = "";
try {
({ stdout } = await execReg(
["query", `HKCU\\${identity.registryKey}`],
await pathExists(paths.appBuilderOutputRoot) ? paths.appBuilderOutputRoot : process.cwd(),
));
} catch {
return null;
}
const entry = createEmptyRegistryEntry(`HKEY_CURRENT_USER\\${identity.registryKey}`);
for (const rawLine of stdout.split(/\r?\n/)) {
const line = rawLine.trimEnd();
if (line.length === 0 || line.startsWith("HKEY_")) continue;
const [name, , ...valueParts] = line.trim().split(/\s{2,}/);
if (name == null || valueParts.length === 0) continue;
const value = valueParts.join(" ");
if (name === "DisplayIcon") entry.displayIcon = value;
else if (name === "DisplayName") entry.displayName = value;
else if (name === "DisplayVersion") entry.displayVersion = value;
else if (name === "InstallLocation") entry.installLocation = value;
else if (name === "Publisher") entry.publisher = value;
else if (name === "QuietUninstallString") entry.quietUninstallString = value;
else if (name === "UninstallString") entry.uninstallString = value;
}
return entry;
}
export async function resolveWinRegisteredPaths(config: ToolPackConfig, paths: WinPaths): Promise<WinPaths> {
const entry = await queryWinNamespaceRegistryEntry(config, paths);
if (entry == null) return paths;
const identity = resolveWinInstallIdentity(config);
const uninstallerFromRegistry = stripRegistryQuotedValue(entry.quietUninstallString) || stripRegistryQuotedValue(entry.uninstallString);
const installDir = stripRegistryQuotedValue(entry.installLocation) || (uninstallerFromRegistry.length > 0 ? dirname(uninstallerFromRegistry) : paths.installDir);
const uninstallerPath = uninstallerFromRegistry.length > 0 ? uninstallerFromRegistry : join(installDir, identity.uninstallerName);
return {
...paths,
installDir,
installedExePath: join(installDir, identity.exeName),
uninstallerPath,
};
}
export async function cleanupWinRegistryResidues(
paths: WinPaths,
config?: Pick<ToolPackConfig, "namespace">,
): Promise<string[]> {
const entries = await queryWinRegistryEntries(paths, config);
const removed: string[] = [];
for (const entry of entries) {
try {