feat: add LICENSE file and custom NSIS installer script for NIANXX
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ValueCell Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -102,6 +102,8 @@ nsis:
|
||||
createStartMenuShortcut: true
|
||||
shortcutName: NIANXX
|
||||
uninstallDisplayName: NIANXX
|
||||
license: LICENSE
|
||||
include: scripts/installer.nsh
|
||||
installerIcon: resources/icons/icon.ico
|
||||
uninstallerIcon: resources/icons/icon.ico
|
||||
|
||||
|
||||
367
scripts/installer.nsh
Normal file
367
scripts/installer.nsh
Normal file
@@ -0,0 +1,367 @@
|
||||
; NIANXX Custom NSIS Installer/Uninstaller Script
|
||||
;
|
||||
; Install: enables long paths, adds resources\cli to user PATH for openclaw CLI.
|
||||
; Uninstall: removes the PATH entry and optionally deletes user data.
|
||||
|
||||
!ifndef nsProcess::FindProcess
|
||||
!include "nsProcess.nsh"
|
||||
!endif
|
||||
|
||||
!macro customHeader
|
||||
; Show install details by default so users can see what stage is running.
|
||||
ShowInstDetails show
|
||||
ShowUninstDetails show
|
||||
!macroend
|
||||
|
||||
!macro customCheckAppRunning
|
||||
; Make stage logs visible on assisted installers (defaults to hidden).
|
||||
SetDetailsPrint both
|
||||
DetailPrint "Preparing installation..."
|
||||
DetailPrint "Extracting NIANXX runtime files. This can take a few minutes on slower disks or while antivirus scanning is active."
|
||||
|
||||
${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
|
||||
|
||||
${if} $R0 == 0
|
||||
${if} ${isUpdated}
|
||||
# Auto-update: the app is already shutting down (quitAndInstall was called).
|
||||
# The before-quit handler needs up to 8s to gracefully stop the Gateway
|
||||
# process tree (5s timeout + force-terminate + re-quit). Wait for the
|
||||
# app to exit on its own before resorting to force-kill.
|
||||
DetailPrint `Waiting for "${PRODUCT_NAME}" to finish shutting down...`
|
||||
Sleep 8000
|
||||
${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
|
||||
${if} $R0 != 0
|
||||
# App exited cleanly. Still kill long-lived child processes (gateway,
|
||||
# uv, python) which may not have followed the app's graceful exit.
|
||||
nsExec::ExecToStack 'taskkill /F /IM openclaw-gateway.exe'
|
||||
Pop $0
|
||||
Pop $1
|
||||
Goto done_killing
|
||||
${endIf}
|
||||
# App didn't exit in time; fall through to force-kill
|
||||
${endIf}
|
||||
${if} ${isUpdated}
|
||||
${else}
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(appRunning)" /SD IDOK IDOK doStopProcess
|
||||
Quit
|
||||
${endIf}
|
||||
|
||||
doStopProcess:
|
||||
DetailPrint `Closing running "${PRODUCT_NAME}"...`
|
||||
|
||||
# Kill ALL processes whose executable lives inside $INSTDIR.
|
||||
# This covers NIANXX.exe (multiple Electron processes), openclaw-gateway.exe,
|
||||
# python.exe (skills runtime), uv.exe (package manager), and any other
|
||||
# child process that might hold file locks in the installation directory.
|
||||
#
|
||||
# Use PowerShell Get-CimInstance for path-based matching (most reliable),
|
||||
# with taskkill name-based fallback for restricted environments.
|
||||
# Note: Using backticks for the NSIS string allows us to use single quotes inside.
|
||||
nsExec::ExecToStack `"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "Get-CimInstance -ClassName Win32_Process | Where-Object { $$_.ExecutablePath -and $$_.ExecutablePath.StartsWith('$INSTDIR', [System.StringComparison]::OrdinalIgnoreCase) } | ForEach-Object { Stop-Process -Id $$_.ProcessId -Force -ErrorAction SilentlyContinue }"`
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
${if} $0 != 0
|
||||
# PowerShell failed (policy restriction, etc.) - fall back to name-based kill
|
||||
nsExec::ExecToStack 'taskkill /F /T /IM "${APP_EXECUTABLE_FILENAME}"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
${endIf}
|
||||
|
||||
# Also kill well-known child processes that may have detached from the
|
||||
# Electron process tree or run from outside $INSTDIR (e.g. system python).
|
||||
nsExec::ExecToStack 'taskkill /F /IM openclaw-gateway.exe'
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
# Wait for Windows to fully release file handles after process termination.
|
||||
# 5 seconds accommodates slow antivirus scanners and filesystem flush delays.
|
||||
Sleep 5000
|
||||
DetailPrint "Processes terminated. Continuing installation..."
|
||||
|
||||
done_killing:
|
||||
${nsProcess::Unload}
|
||||
${endIf}
|
||||
|
||||
; Even if NIANXX.exe was not detected as running, orphan child processes
|
||||
; (python.exe, openclaw-gateway.exe, uv.exe, etc.) from a previous crash
|
||||
; or unclean shutdown may still hold file locks inside $INSTDIR.
|
||||
; Unconditionally kill any process whose executable lives in the install dir.
|
||||
nsExec::ExecToStack `"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "Get-CimInstance -ClassName Win32_Process | Where-Object { $$_.ExecutablePath -and $$_.ExecutablePath.StartsWith('$INSTDIR', [System.StringComparison]::OrdinalIgnoreCase) } | ForEach-Object { Stop-Process -Id $$_.ProcessId -Force -ErrorAction SilentlyContinue }"`
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
; Always kill known process names as a belt-and-suspenders approach.
|
||||
; PowerShell path-based kill may miss processes if the old NIANXX was installed
|
||||
; in a different directory than $INSTDIR (e.g., per-machine -> per-user migration).
|
||||
; taskkill is name-based and catches processes regardless of their install location.
|
||||
nsExec::ExecToStack 'taskkill /F /T /IM "${APP_EXECUTABLE_FILENAME}"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
nsExec::ExecToStack 'taskkill /F /IM openclaw-gateway.exe'
|
||||
Pop $0
|
||||
Pop $1
|
||||
; Note: we intentionally do NOT kill uv.exe globally - it is a popular
|
||||
; Python package manager and other users/CI jobs may have uv running.
|
||||
; The PowerShell path-based kill above already handles uv inside $INSTDIR.
|
||||
|
||||
; Brief wait for handle release (main wait was already done above if app was running)
|
||||
Sleep 2000
|
||||
|
||||
; Release NSIS's CWD on $INSTDIR BEFORE the rename check.
|
||||
; NSIS sets CWD to $INSTDIR in .onInit; Windows refuses to rename a directory
|
||||
; that any process (including NSIS itself) has as its CWD.
|
||||
SetOutPath $TEMP
|
||||
|
||||
; Pre-emptively clear the old installation directory so that the 7z
|
||||
; extraction CopyFiles step in extractAppPackage.nsh won't fail on
|
||||
; locked files. electron-builder's extractUsing7za macro extracts to a
|
||||
; temp folder first, then uses CopyFiles /SILENT to copy into $INSTDIR.
|
||||
; If ANY file in $INSTDIR is still locked, CopyFiles fails and triggers a
|
||||
; "Can't modify NIANXX's files" retry loop -> "NIANXX cannot be closed" dialog.
|
||||
;
|
||||
; Strategy: rename (move) the old $INSTDIR out of the way. Rename works
|
||||
; even when AV/indexer have files open for reading (they use
|
||||
; FILE_SHARE_DELETE sharing mode), whereas CopyFiles fails because it
|
||||
; needs write/overwrite access which some AV products deny.
|
||||
; Check if a previous installation exists ($INSTDIR is a directory).
|
||||
; Use trailing backslash - the correct NSIS idiom for directory existence.
|
||||
; (IfFileExists "$INSTDIR\*.*" only matches files containing a dot and
|
||||
; would fail for extensionless files or pure-subdirectory layouts.)
|
||||
IfFileExists "$INSTDIR\" 0 _instdir_clean
|
||||
; Find the first available stale directory name (e.g. $INSTDIR._stale_0)
|
||||
; This ensures we NEVER have to synchronously delete old leftovers before
|
||||
; renaming the current $INSTDIR. We just move it out of the way instantly.
|
||||
StrCpy $R8 0
|
||||
_find_free_stale:
|
||||
IfFileExists "$INSTDIR._stale_$R8\" 0 _found_free_stale
|
||||
IntOp $R8 $R8 + 1
|
||||
Goto _find_free_stale
|
||||
|
||||
_found_free_stale:
|
||||
ClearErrors
|
||||
Rename "$INSTDIR" "$INSTDIR._stale_$R8"
|
||||
IfErrors 0 _stale_moved
|
||||
; Rename still failed - a process reopened a file or holds CWD in $INSTDIR.
|
||||
; We must delete forcibly and synchronously to make room for CopyFiles.
|
||||
; This can be slow (~1-3 minutes) if there are 10,000+ files and AV is active.
|
||||
nsExec::ExecToStack 'cmd.exe /c rd /s /q "$INSTDIR"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
Sleep 2000
|
||||
CreateDirectory "$INSTDIR"
|
||||
Goto _instdir_clean
|
||||
_stale_moved:
|
||||
CreateDirectory "$INSTDIR"
|
||||
_instdir_clean:
|
||||
|
||||
; Pre-emptively remove the old uninstall registry entry so that
|
||||
; electron-builder's uninstallOldVersion skips the old uninstaller entirely.
|
||||
;
|
||||
; Why: uninstallOldVersion has a hardcoded 5-retry loop that runs the old
|
||||
; uninstaller repeatedly. The old uninstaller's atomicRMDir fails on locked
|
||||
; files (antivirus, indexing) causing a blocking "NIANXX cannot be closed" dialog.
|
||||
; Deleting UninstallString makes uninstallOldVersion return immediately.
|
||||
; The new installer will overwrite / extract all files on top of the old dir.
|
||||
; registryAddInstallInfo will write the correct new entries afterwards.
|
||||
; Clean both SHELL_CONTEXT and HKCU to cover cross-hive upgrades
|
||||
; (e.g. old install was per-user, new install is per-machine or vice versa).
|
||||
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY}" UninstallString
|
||||
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY}" QuietUninstallString
|
||||
DeleteRegValue HKCU "${UNINSTALL_REGISTRY_KEY}" UninstallString
|
||||
DeleteRegValue HKCU "${UNINSTALL_REGISTRY_KEY}" QuietUninstallString
|
||||
!ifdef UNINSTALL_REGISTRY_KEY_2
|
||||
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY_2}" UninstallString
|
||||
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY_2}" QuietUninstallString
|
||||
DeleteRegValue HKCU "${UNINSTALL_REGISTRY_KEY_2}" UninstallString
|
||||
DeleteRegValue HKCU "${UNINSTALL_REGISTRY_KEY_2}" QuietUninstallString
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
; Override electron-builder's handleUninstallResult to prevent the
|
||||
; "NIANXX cannot be closed" retry dialog when the old uninstaller fails.
|
||||
;
|
||||
; During upgrades, electron-builder copies the old uninstaller to a temp dir
|
||||
; and runs it silently. The old uninstaller uses atomicRMDir to rename every
|
||||
; file out of $INSTDIR. If ANY file is still locked (antivirus scanner,
|
||||
; Windows Search indexer, delayed kernel handle release after taskkill), it
|
||||
; aborts with a non-zero exit code. The default handler retries 5x then shows
|
||||
; a blocking MessageBox.
|
||||
;
|
||||
; This macro clears the error and lets the new installer proceed - it will
|
||||
; simply overwrite / extract new files on top of the (partially cleaned) old
|
||||
; installation directory. This is safe because:
|
||||
; 1. Processes have already been force-killed in customCheckAppRunning.
|
||||
; 2. The new installer extracts a complete, self-contained file tree.
|
||||
; 3. Any leftover old files that weren't removed are harmless.
|
||||
!macro customUnInstallCheck
|
||||
${if} $R0 != 0
|
||||
DetailPrint "Old uninstaller exited with code $R0. Continuing with overwrite install..."
|
||||
${endIf}
|
||||
ClearErrors
|
||||
!macroend
|
||||
|
||||
; Same safety net for the HKEY_CURRENT_USER uninstall path.
|
||||
; Without this, handleUninstallResult would show a fatal error and Quit.
|
||||
!macro customUnInstallCheckCurrentUser
|
||||
${if} $R0 != 0
|
||||
DetailPrint "Old uninstaller (current user) exited with code $R0. Continuing..."
|
||||
${endIf}
|
||||
ClearErrors
|
||||
!macroend
|
||||
|
||||
!macro customInstall
|
||||
; Async cleanup of old dirs left by the rename loop in customCheckAppRunning.
|
||||
; Wait 60s before starting deletion to avoid I/O contention with NIANXX's
|
||||
; first launch (Windows Defender scan, ASAR mapping, etc.).
|
||||
; ExecShell SW_HIDE is completely detached from NSIS and avoids pipe blocking.
|
||||
IfFileExists "$INSTDIR._stale_0\" 0 _ci_stale_cleaned
|
||||
; Use PowerShell to extract the basename of $INSTDIR so the glob works
|
||||
; even when the user picked a custom install folder name.
|
||||
; E.g. $INSTDIR = D:\Apps\MyNianXX -> glob = MyNianXX._stale_*
|
||||
ExecShell "" "cmd.exe" `/c ping -n 61 127.0.0.1 >nul & cd /d "$INSTDIR\.." & for /d %D in ("$INSTDIR._stale_*") do rd /s /q "%D"` SW_HIDE
|
||||
_ci_stale_cleaned:
|
||||
DetailPrint "Core files extracted. Finalizing system integration..."
|
||||
|
||||
; Enable Windows long path support (Windows 10 1607+ / Windows 11).
|
||||
; pnpm virtual store paths can exceed the default MAX_PATH limit of 260 chars.
|
||||
; Writing to HKLM requires admin privileges; on per-user installs without
|
||||
; elevation this call silently fails - no crash, just no key written.
|
||||
DetailPrint "Enabling long-path support (if permissions allow)..."
|
||||
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1
|
||||
|
||||
; Add $INSTDIR to Windows Defender exclusion list so that real-time scanning
|
||||
; doesn't block the first app launch (Defender scans every newly-created file,
|
||||
; causing 10-30s startup delay on a fresh install). Requires elevation;
|
||||
; silently fails on non-admin per-user installs (no harm done).
|
||||
DetailPrint "Configuring Windows Defender exclusion..."
|
||||
nsExec::ExecToStack `"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "Add-MpPreference -ExclusionPath '$INSTDIR' -ErrorAction SilentlyContinue"`
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
; Use PowerShell to update the current user's PATH.
|
||||
; This avoids NSIS string-buffer limits and preserves long PATH values.
|
||||
DetailPrint "Updating user PATH for the OpenClaw CLI..."
|
||||
InitPluginsDir
|
||||
ClearErrors
|
||||
File "/oname=$PLUGINSDIR\update-user-path.ps1" "${PROJECT_DIR}\resources\cli\win32\update-user-path.ps1"
|
||||
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\update-user-path.ps1" -Action add -CliDir "$INSTDIR\resources\cli"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
StrCmp $0 "error" 0 +2
|
||||
DetailPrint "Warning: Failed to launch PowerShell while updating PATH."
|
||||
StrCmp $0 "timeout" 0 +2
|
||||
DetailPrint "Warning: PowerShell PATH update timed out."
|
||||
StrCmp $0 "0" 0 +2
|
||||
Goto _ci_done
|
||||
DetailPrint "Warning: PowerShell PATH update exited with code $0."
|
||||
|
||||
_ci_done:
|
||||
DetailPrint "Installation steps complete."
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
; Remove Windows Defender exclusion added during install
|
||||
nsExec::ExecToStack `"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "Remove-MpPreference -ExclusionPath '$INSTDIR' -ErrorAction SilentlyContinue"`
|
||||
Pop $0
|
||||
Pop $1
|
||||
|
||||
; Remove resources\cli from user PATH via PowerShell so long PATH values are handled safely
|
||||
InitPluginsDir
|
||||
ClearErrors
|
||||
File "/oname=$PLUGINSDIR\update-user-path.ps1" "${PROJECT_DIR}\resources\cli\win32\update-user-path.ps1"
|
||||
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\update-user-path.ps1" -Action remove -CliDir "$INSTDIR\resources\cli"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
StrCmp $0 "error" 0 +2
|
||||
DetailPrint "Warning: Failed to launch PowerShell while removing PATH entry."
|
||||
StrCmp $0 "timeout" 0 +2
|
||||
DetailPrint "Warning: PowerShell PATH removal timed out."
|
||||
StrCmp $0 "0" 0 +2
|
||||
Goto _cu_pathDone
|
||||
DetailPrint "Warning: PowerShell PATH removal exited with code $0."
|
||||
|
||||
_cu_pathDone:
|
||||
|
||||
; Ask user if they want to remove AppData (preserves .openclaw)
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
||||
"Do you want to remove NIANXX application data?$\r$\n$\r$\nThis will delete:$\r$\n - AppData\Local\nianxx (local app data)$\r$\n - AppData\Roaming\nianxx (roaming app data)$\r$\n$\r$\nYour .openclaw folder (configuration & skills) will be preserved.$\r$\nSelect 'No' to keep all data for future reinstallation." \
|
||||
/SD IDNO IDYES _cu_removeData IDNO _cu_skipRemove
|
||||
|
||||
_cu_removeData:
|
||||
; Kill any lingering NIANXX processes (and their child process trees) to
|
||||
; release file locks on electron-store JSON files, Gateway sockets, etc.
|
||||
${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
|
||||
${if} $R0 == 0
|
||||
nsExec::ExecToStack 'taskkill /F /T /IM "${APP_EXECUTABLE_FILENAME}"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
${endIf}
|
||||
${nsProcess::Unload}
|
||||
|
||||
; Wait for processes to fully exit and release file handles
|
||||
Sleep 2000
|
||||
|
||||
; --- Always remove current user's AppData first ---
|
||||
; NOTE: .openclaw directory is intentionally preserved (user configuration & skills)
|
||||
RMDir /r "$LOCALAPPDATA\nianxx"
|
||||
RMDir /r "$APPDATA\nianxx"
|
||||
|
||||
; --- Retry: if directories still exist (locked files), wait and try again ---
|
||||
|
||||
; Check AppData\Local\nianxx
|
||||
IfFileExists "$LOCALAPPDATA\nianxx\*.*" 0 _cu_localDone
|
||||
Sleep 3000
|
||||
RMDir /r "$LOCALAPPDATA\nianxx"
|
||||
IfFileExists "$LOCALAPPDATA\nianxx\*.*" 0 _cu_localDone
|
||||
nsExec::ExecToStack 'cmd.exe /c rd /s /q "$LOCALAPPDATA\nianxx"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
_cu_localDone:
|
||||
|
||||
; Check AppData\Roaming\nianxx
|
||||
IfFileExists "$APPDATA\nianxx\*.*" 0 _cu_roamingDone
|
||||
Sleep 3000
|
||||
RMDir /r "$APPDATA\nianxx"
|
||||
IfFileExists "$APPDATA\nianxx\*.*" 0 _cu_roamingDone
|
||||
nsExec::ExecToStack 'cmd.exe /c rd /s /q "$APPDATA\nianxx"'
|
||||
Pop $0
|
||||
Pop $1
|
||||
_cu_roamingDone:
|
||||
|
||||
; --- Final check: warn user if any directories could not be removed ---
|
||||
StrCpy $R3 ""
|
||||
IfFileExists "$LOCALAPPDATA\nianxx\*.*" 0 +2
|
||||
StrCpy $R3 "$R3$\r$\n - $LOCALAPPDATA\nianxx"
|
||||
IfFileExists "$APPDATA\nianxx\*.*" 0 +2
|
||||
StrCpy $R3 "$R3$\r$\n - $APPDATA\nianxx"
|
||||
StrCmp $R3 "" _cu_cleanupOk
|
||||
MessageBox MB_OK|MB_ICONEXCLAMATION \
|
||||
"Some data directories could not be removed (files may be in use):$\r$\n$R3$\r$\n$\r$\nPlease delete them manually after restarting your computer."
|
||||
_cu_cleanupOk:
|
||||
|
||||
; --- For per-machine (all users) installs, enumerate all user profiles ---
|
||||
StrCpy $R0 0
|
||||
|
||||
_cu_enumLoop:
|
||||
EnumRegKey $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $R0
|
||||
StrCmp $R1 "" _cu_enumDone
|
||||
|
||||
ReadRegStr $R2 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$R1" "ProfileImagePath"
|
||||
StrCmp $R2 "" _cu_enumNext
|
||||
|
||||
; ExpandEnvStrings requires distinct src and dest registers
|
||||
ExpandEnvStrings $R3 $R2
|
||||
StrCmp $R3 $PROFILE _cu_enumNext
|
||||
|
||||
; NOTE: .openclaw directory is intentionally preserved for all users
|
||||
RMDir /r "$R3\AppData\Local\nianxx"
|
||||
RMDir /r "$R3\AppData\Roaming\nianxx"
|
||||
|
||||
_cu_enumNext:
|
||||
IntOp $R0 $R0 + 1
|
||||
Goto _cu_enumLoop
|
||||
|
||||
_cu_enumDone:
|
||||
_cu_skipRemove:
|
||||
!macroend
|
||||
Reference in New Issue
Block a user