From eec3382e40cc6447b468fd51379f3ce4b34490e1 Mon Sep 17 00:00:00 2001 From: Mark Winkel Date: Wed, 8 Jul 2026 11:51:04 -0400 Subject: [PATCH 1/5] Fix: Windows uninstaller now deletes files and adjusts PATH environment var --- deploy/packaging/windows/debug.nsh | 2 + deploy/packaging/windows/is_operator.nsh | 7 +- deploy/packaging/windows/mdsplus.nsi | 206 +++++++++++++++-------- deploy/packaging/windows/recycle.nsh | 4 + 4 files changed, 145 insertions(+), 74 deletions(-) diff --git a/deploy/packaging/windows/debug.nsh b/deploy/packaging/windows/debug.nsh index dbbaab6a1c..e84c7799e8 100644 --- a/deploy/packaging/windows/debug.nsh +++ b/deploy/packaging/windows/debug.nsh @@ -1,3 +1,5 @@ +;; To enable logging, uncomment the lines that hav a single leading ";". +;; It can also be useful to change the path and name of the log file. ### BEGIN DEBUG TOOLS ### ;Var LOG ;; ShowLog show an OK_BOX with $LOG if $LOG is not empty diff --git a/deploy/packaging/windows/is_operator.nsh b/deploy/packaging/windows/is_operator.nsh index 450f7ed186..14d637c9e6 100644 --- a/deploy/packaging/windows/is_operator.nsh +++ b/deploy/packaging/windows/is_operator.nsh @@ -1,5 +1,8 @@ +; Obsolete. PR 3061 eliminated all calls to this operator. However, +; it has been retained just in case it will be useful in the future. +; ;; If operator to check whether section has a flag set -;; ${If} ${setion} is ${SF_SELECTED} +;; ${If} ${section} is ${SF_SELECTED} Var ISVAR !macro _is section flag t f Push `${flag}` ;; In order to process two args without @@ -15,7 +18,7 @@ Var ISVAR IntCmp $ISVAR 0 `${f}` `${t}` `${t}` ; $ISVAR != 0 !macroend ; _is ; this helper function is required to isolate the jump markers -; SectionFlagIsSet does not support relative jumps that may be passes by If +; SectionFlagIsSet does not support relative jumps that may be passed by If Function isfun !insertmacro SectionFlagIsSet $0 $1 true false true: StrCpy $ISVAR 1 diff --git a/deploy/packaging/windows/mdsplus.nsi b/deploy/packaging/windows/mdsplus.nsi index 801824e81f..efa3e71dbd 100644 --- a/deploy/packaging/windows/mdsplus.nsi +++ b/deploy/packaging/windows/mdsplus.nsi @@ -1,3 +1,26 @@ +; Developer tips: +; - Include files not found in the MDSplus repo are part of the Nullsoft framework. +; Refer to the Jenkins Docker image, mdsplus/builder:windows-x86-x64. +; The include files are located in /usr/share/nsis/Include +; - Silent mode is useful for scripted installs and can be done with these commands: +; /S /AllUsers +; /S /CurrentUser +; - Nullsoft (aka NSIS) makes extensive use of macros and a pre-processor, plus the +; syntax is a bit odd. For details, refer to the following documentation: +; https://nsis.sourceforge.io/Main_Page +; https://documentation.help/NSIS/Contents.html +; - Windows Defender can quarantine Nullsoft installers if they contain patterns +; that Defender considers a security breach. Thus, it is essential to test the +; installer on a Windows system before merging a PR. +; - The ${ToLog} macro is useful for troubleshooting installer issues (see debug.nsh). +; Unfortunately, "PrintDetail", cannot be used because it is broken in Nullsoft v3.09. +; - Nullsoft does not automatically create a list of all installed files, thus +; the uninstaller is more complicated than expected. +; - The uninstaller must not use an unguarded "RmDir /r $INSTDIR" because in some +; scenarios that can wipe system files from the Windows computer. For more details, +; refer to the Nullsoft documentation. + + Name "MDSplus${BNAME}-${ARCH} ${MAJOR}.${MINOR}-${RELEASE}" OutFile ${OUTDIR}/MDSplus${BNAME}-${MAJOR}.${MINOR}-${RELEASE}-${ARCH}.exe ;SetCompress off @@ -33,7 +56,7 @@ InstType "Minimal" !define GCC_LIB libgcc_s_dw2-1.dll !endif -!define TEMP_DEL_DIR "$WINDIR\Temp\MDSplus_uninstall_relicts.delete_me" +!define TEMP_DEL_DIR "$WINDIR\Temp\MDSplus_uninstall_relics.delete_me" !define MDSPLUS_DIR "$INSTDIR" !define MDS_PATH "$INSTDIR\tdi" @@ -43,7 +66,7 @@ InstType "Minimal" !define subtree_path "$INSTDIR\trees\subtree" !define BINDIR "$INSTDIR\bin" -;;allows admins to decide if the want to install for current or allusers +;; allows admins to decide if the want to install for current or allusers !define MULTIUSER_EXECUTIONLEVEL Highest !define MULTIUSER_MUI !define MULTIUSER_INSTALLMODE_INSTDIR MDSplus @@ -64,9 +87,11 @@ ${UnStrTrimNewLines} ; implements ${UnStrTrimNewLines} ;; used to get filename in ignore branch of uninstaller !include "WordFunc.nsh" +;; used by the uninstallation script to obtain the filename from a filepath +!include "FileFunc.nsh" + !addincludedir ${INCLUDE} !include debug.nsh ; define debug tools -!include recycle.nsh ; define Recycle, Delete, and TryRecycle !include in_operator.nsh ; define in !include is_operator.nsh ; define is !include registry.nsh ; define AllUsers, registry and env add/remove @@ -86,11 +111,19 @@ Var StartMenuFolder !insertmacro MULTIUSER_PAGE_INSTALLMODE ;; !insertmacro MUI_PAGE_DIRECTORY ;; replace with warning if selected folder already exists +; This function is only called during an interactive install when the user +; changes the path to select an existing directory, such as "C:\Program Files" +; instead of "C:\Program Files\MDSplus". Thus, don't delete the directory; also +; don't install MDSplus over the directory. The Abort stops the callback, but +; does not stop the installer. Function DirectoryLeave ${If} ${FileExists} "$InstDir" - MessageBox MB_YESNO `"$InstDir" already exists.$\r$\nDo you want to continue and DELETE all it's content?` IDYES yes - Abort - yes: + MessageBox MB_OK `"$InstDir" already exists. $\r$\n$\r$\n \ + On the next dialog, do this: $\r$\n \ + - either enter a new directory or $\r$\n \ + - click on the CANCEL button.` IDOK stopit + stopit: + Abort ${EndIf} FunctionEnd Page Directory "" "" DirectoryLeave @@ -112,44 +145,43 @@ Page Directory "" "" DirectoryLeave !define SetSectionFlag '!insertmacro "SetSectionFlag"' - -### BEGIN InstallFiles ### -!macro InstallFiles source dest logfile - !define _id_ ${__LINE__} - Push $0 - Push $1 - FindFirst $0 $1 "${source}\*" - ClearErrors - loop${_id_}: - StrCmp $1 "" done${_id_} - StrCmp $1 "." next${_id_} - StrCmp $1 ".." next${_id_} - retry${_id_}: - Rename "${source}\$1" "${dest}\$1" - IfErrors 0 ignore${_id_} - MessageBox MB_ABORTRETRYIGNORE 'File "$1" cannot be moved to "${dest}". File already exists?!' IDIGNORE ignore${_id_} IDRETRY retry${_id_} - FindClose $0 - FileClose ${logfile} - Abort - ignore${_id_}: - FileWrite ${logfile} "${dest}\$1$\r$\n" - next${_id_}: - FindNext $0 $1 - Goto loop${_id_} - done${_id_}: - FindClose $0 - Pop $1 - Pop $0 - !undef _id_ -!macroend ; InstallFiles -!define InstallFiles '!insertmacro "InstallFiles"' -### END InstallFiles ### +; Obsolete because of the CMake PR 2527, so commented out. +; ### BEGIN InstallFiles ### +; !macro InstallFiles source dest logfile +; !define _id_ ${__LINE__} +; Push $0 +; Push $1 +; FindFirst $0 $1 "${source}\*" +; ClearErrors +; loop${_id_}: +; StrCmp $1 "" done${_id_} +; StrCmp $1 "." next${_id_} +; StrCmp $1 ".." next${_id_} +; retry${_id_}: +; Rename "${source}\$1" "${dest}\$1" +; IfErrors 0 ignore${_id_} +; MessageBox MB_ABORTRETRYIGNORE 'File "$1" cannot be moved to "${dest}". File already exists?!' IDIGNORE ignore${_id_} IDRETRY retry${_id_} +; FindClose $0 +; FileClose ${logfile} +; Abort +; ignore${_id_}: +; FileWrite ${logfile} "${dest}\$1$\r$\n" +; next${_id_}: +; FindNext $0 $1 +; Goto loop${_id_} +; done${_id_}: +; FindClose $0 +; Pop $1 +; Pop $0 +; !undef _id_ +; !macroend ; InstallFiles +; !define InstallFiles '!insertmacro "InstallFiles"' +; ### END InstallFiles ### ### BEGIN SECTIONS ### Function install_core_pre - ${TryRecycle} "$INSTDIR" SetOutPath "$INSTDIR" writeUninstaller "$INSTDIR\uninstall.exe" File icons/mdsplus.ico @@ -473,6 +505,7 @@ SectionEnd ### BEGIN SECTION LOGIC ### +; PR 3061 commented this out ; Function .onSelChange ; ; pydevices depend on python ; ${If} $0 == ${apis} @@ -546,10 +579,10 @@ Function Init Abort ; Run the uninstaller uninst: - ; The uninstaller would create a temp copy before executing - ; This ensures the the original file can be uninstalled - ; however this brease the wait functionality of execwait - ; This is why we create a tempfile manually + ; The uninstaller is copied to a temp dir before executing. + ; That ensures the original uninstaller can be uninstalled. + ; However that also breaks the wait functionality of execwait, which + ; is why we create a tempfile manually. Push $R1 Push $R2 ${ReadkeyStr} $R2 ${UNINSTALL_KEY} "Uninstaller" @@ -647,35 +680,35 @@ Section uninstall Pop $R0 nsExec::ExecToLog '"$R1" /C SC DELETE "MDSplus 8100"' Pop $R0 - FileOpen $R0 "$INSTDIR\uninstall.dat" r - ${DisableX64FSRedirection} - CreateDirectory "${TEMP_DEL_DIR}" - ClearErrors - loop: - FileRead $R0 $R1 - StrCmp $R1 "" done - ${UnStrTrimNewLines} $R2 $R1 - retry: - Delete "$R2" - IfErrors 0 loop - MessageBox MB_ABORTRETRYIGNORE 'File "$R2" could not be deleted. Is it still in use.' IDIGNORE ignore IDRETRY retry - FileClose $R0 - RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" - Abort - ignore: - System::Call 'kernel32::GetTickCount()i .R3' - ${WordFind} "$R2" "\" "-1" $R4 - Rename "$R2" "${TEMP_DEL_DIR}\$R4$R3" - ClearErrors - Goto loop - done: - FileClose $R0 - ${EnableX64FSRedirection} - RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" - Delete uninstall.dat - ${Else} - ${RemoveFromEnv} PATH "${BINDIR}" + ; Obsolete because of CMake PR 2527, so commented out. + ; FileOpen $R0 "$INSTDIR\uninstall.dat" r + ; ${DisableX64FSRedirection} + ; CreateDirectory "${TEMP_DEL_DIR}" + ; ClearErrors + ; loop: + ; FileRead $R0 $R1 + ; StrCmp $R1 "" done + ; ${UnStrTrimNewLines} $R2 $R1 + ; retry: + ; Delete "$R2" + ; IfErrors 0 loop + ; MessageBox MB_ABORTRETRYIGNORE 'File "$R2" could not be deleted. Is it still in use.' IDIGNORE ignore IDRETRY retry + ; FileClose $R0 + ; RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" + ; Abort + ; ignore: + ; System::Call 'kernel32::GetTickCount()i .R3' + ; ${WordFind} "$R2" "\" "-1" $R4 + ; Rename "$R2" "${TEMP_DEL_DIR}\$R4$R3" + ; ClearErrors + ; Goto loop + ; done: + ; FileClose $R0 + ; ${EnableX64FSRedirection} + ; RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" + ; Delete uninstall.dat ${EndIf} + ${RemoveFromEnv} PATH "${BINDIR}" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder RMDir /r "$SMPROGRAMS\$StartMenuFolder" ${DeleteEnv} MDSPLUS_DIR @@ -686,7 +719,36 @@ Section uninstall ${RemoveFromEnv} MDS_PYDEVICE_PATH "${MDS_PYDEVICE_PATH}" ${DeleteKey} "${UNINSTALL_KEY}" SetOutPath "$SYSDIR" ; avoid access to $INSTDIR so it may be deleted - ${TryRecycle} "$INSTDIR" + Var /GLOBAL InstBaseName + ${GetBaseName} $INSTDIR $InstBaseName + ${IfNot} "$InstBaseName" == ${MULTIUSER_INSTALLMODE_INSTDIR} + ${ToLog} "Non-standard dir, so MDPlus was unregistered but not deleted." + ; Windows Defender chokes on the following MessageBox even though the "/SD" makes it silent. So use a more verbose approach. + ; MessageBox MB_OKCANCEL "Non-standard dir, so MDPlus was unregistered but not deleted." /SD IDOK IDOK next + IfSilent next gui ; Note that ${IfSilent} is not available in NSIS v3.09 + gui: + MessageBox MB_OK "Non-standard dir, so MDPlus was unregistered but not deleted." IDOK next + next: ; + + ; Because of the above check, could probably use "RmDir /r $INSTDIR" here. Instead, use a more conservative approach. + ${Else} + RmDir /r /REBOOTOK "$INSTDIR\bin" + RmDir /r /REBOOTOK "$INSTDIR\devtools" + RmDir /r /REBOOTOK "$INSTDIR\epics" + RmDir /r /REBOOTOK "$INSTDIR\idl" + RmDir /r /REBOOTOK "$INSTDIR\include" + RmDir /r /REBOOTOK "$INSTDIR\java" + RmDir /r /REBOOTOK "$INSTDIR\LabView" + RmDir /r /REBOOTOK "$INSTDIR\matlab" + RmDir /r /REBOOTOK "$INSTDIR\pydevices" + RmDir /r /REBOOTOK "$INSTDIR\python" + RmDir /r /REBOOTOK "$INSTDIR\tdi" + RmDir /r /REBOOTOK "$INSTDIR\trees" + RmDir /r /REBOOTOK "$INSTDIR\xml" + Delete /REBOOTOK "$INSTDIR\*" + RmDir "$INSTDIR" + ${EndIf} + Pop $R2 Pop $R1 Pop $R0 diff --git a/deploy/packaging/windows/recycle.nsh b/deploy/packaging/windows/recycle.nsh index d5e42645fb..9f1da162cf 100644 --- a/deploy/packaging/windows/recycle.nsh +++ b/deploy/packaging/windows/recycle.nsh @@ -1,3 +1,7 @@ +; Obsolete. This entire file does not work. Displaying $R0 indicates that +; the $INSTDIR is not found. Fails on both Win11 NTFS and FAT32. +; This is based on a third-party script published in 2012. + ;; https://nsis.sourceforge.io/mediawiki/index.php?title=Send_to_Recycle_Bin !define FO_DELETE 0x3 From 3dd3e28d11fb82682bb38b7782cccd8c8195aa03 Mon Sep 17 00:00:00 2001 From: mwinkel-dev <122583770+mwinkel-dev@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:00:37 -0600 Subject: [PATCH 2/5] Fix: obsolete recycle.nsh removed from the Windows installer --- deploy/packaging/windows/recycle.nsh | 43 ---------------------------- 1 file changed, 43 deletions(-) delete mode 100644 deploy/packaging/windows/recycle.nsh diff --git a/deploy/packaging/windows/recycle.nsh b/deploy/packaging/windows/recycle.nsh deleted file mode 100644 index 9f1da162cf..0000000000 --- a/deploy/packaging/windows/recycle.nsh +++ /dev/null @@ -1,43 +0,0 @@ -; Obsolete. This entire file does not work. Displaying $R0 indicates that -; the $INSTDIR is not found. Fails on both Win11 NTFS and FAT32. -; This is based on a third-party script published in 2012. - -;; https://nsis.sourceforge.io/mediawiki/index.php?title=Send_to_Recycle_Bin - -!define FO_DELETE 0x3 -!define FOF_SILENT 0x4 -!define FOF_NOCONFIRMATION 0x10 -!define FOF_ALLOWUNDO 0x40 -!define FOF_NOERRORUI 0x400 -!define FOF_WANTNUKEWARNING 0x4000 -!define FOF_RECYCLE FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_WANTNUKEWARNING|FOF_ALLOWUNDO -!define FOF_DELETE FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_WANTNUKEWARNING - -!macro SHFileOperation_Delete op outvar filespec -Push `${filespec}` -Exch $0 -Push $1 -Push $2 -StrLen $2 $0 -System::Call `*(&t$2 r0,i0)p.r2` ; terminate with \0\0 -System::Call `*(i$hwndparent,i${FO_DELETE},ir2,i0,i${op},i0,i0,i0)i.r1` ; SHFILEOPSTRUCTA -System::Call `shell32::SHFileOperation(ir1)i.r0` -System::Free $2 -System::Free $1 -Pop $2 -Pop $1 -Exch $0 -Pop ${outvar} -!macroend ; SHFileOperation_Delete -!define Recycle '!insertmacro "SHFileOperation_Delete" ${FOF_RECYCLE}' -!define Delete '!insertmacro "SHFileOperation_Delete" ${FOF_DELETE}' - -!macro TryRecycle filespec -Push $R0 -${Recycle} $R0 `${filespec}` -${If} $R0 == 120 - ${Delete} $R0 `${filespec}` -${EndIf} -Pop $R0 -!macroend ; TryRecycle -!define TryRecycle '!insertmacro "TryRecycle"' From b26c3c7cdfbeaea737ae872dc551ac57593546e7 Mon Sep 17 00:00:00 2001 From: mwinkel-dev <122583770+mwinkel-dev@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:06:01 -0600 Subject: [PATCH 3/5] Fix: the CMake PR 2527 made some code obsolete so remove it from the Windows installer --- deploy/packaging/windows/mdsplus.nsi | 61 ---------------------------- 1 file changed, 61 deletions(-) diff --git a/deploy/packaging/windows/mdsplus.nsi b/deploy/packaging/windows/mdsplus.nsi index efa3e71dbd..75ac236e29 100644 --- a/deploy/packaging/windows/mdsplus.nsi +++ b/deploy/packaging/windows/mdsplus.nsi @@ -145,40 +145,6 @@ Page Directory "" "" DirectoryLeave !define SetSectionFlag '!insertmacro "SetSectionFlag"' -; Obsolete because of the CMake PR 2527, so commented out. -; ### BEGIN InstallFiles ### -; !macro InstallFiles source dest logfile -; !define _id_ ${__LINE__} -; Push $0 -; Push $1 -; FindFirst $0 $1 "${source}\*" -; ClearErrors -; loop${_id_}: -; StrCmp $1 "" done${_id_} -; StrCmp $1 "." next${_id_} -; StrCmp $1 ".." next${_id_} -; retry${_id_}: -; Rename "${source}\$1" "${dest}\$1" -; IfErrors 0 ignore${_id_} -; MessageBox MB_ABORTRETRYIGNORE 'File "$1" cannot be moved to "${dest}". File already exists?!' IDIGNORE ignore${_id_} IDRETRY retry${_id_} -; FindClose $0 -; FileClose ${logfile} -; Abort -; ignore${_id_}: -; FileWrite ${logfile} "${dest}\$1$\r$\n" -; next${_id_}: -; FindNext $0 $1 -; Goto loop${_id_} -; done${_id_}: -; FindClose $0 -; Pop $1 -; Pop $0 -; !undef _id_ -; !macroend ; InstallFiles -; !define InstallFiles '!insertmacro "InstallFiles"' -; ### END InstallFiles ### - - ### BEGIN SECTIONS ### Function install_core_pre @@ -680,33 +646,6 @@ Section uninstall Pop $R0 nsExec::ExecToLog '"$R1" /C SC DELETE "MDSplus 8100"' Pop $R0 - ; Obsolete because of CMake PR 2527, so commented out. - ; FileOpen $R0 "$INSTDIR\uninstall.dat" r - ; ${DisableX64FSRedirection} - ; CreateDirectory "${TEMP_DEL_DIR}" - ; ClearErrors - ; loop: - ; FileRead $R0 $R1 - ; StrCmp $R1 "" done - ; ${UnStrTrimNewLines} $R2 $R1 - ; retry: - ; Delete "$R2" - ; IfErrors 0 loop - ; MessageBox MB_ABORTRETRYIGNORE 'File "$R2" could not be deleted. Is it still in use.' IDIGNORE ignore IDRETRY retry - ; FileClose $R0 - ; RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" - ; Abort - ; ignore: - ; System::Call 'kernel32::GetTickCount()i .R3' - ; ${WordFind} "$R2" "\" "-1" $R4 - ; Rename "$R2" "${TEMP_DEL_DIR}\$R4$R3" - ; ClearErrors - ; Goto loop - ; done: - ; FileClose $R0 - ; ${EnableX64FSRedirection} - ; RmDir /r /REBOOTOK "${TEMP_DEL_DIR}" - ; Delete uninstall.dat ${EndIf} ${RemoveFromEnv} PATH "${BINDIR}" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder From fcce70fc2425e21281e6f88a1ed20dbc40fd5e0d Mon Sep 17 00:00:00 2001 From: mwinkel-dev <122583770+mwinkel-dev@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:57:59 -0600 Subject: [PATCH 4/5] Fix: add tip about the NSIS extension for VS Code --- deploy/packaging/windows/mdsplus.nsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deploy/packaging/windows/mdsplus.nsi b/deploy/packaging/windows/mdsplus.nsi index 75ac236e29..8140f0f212 100644 --- a/deploy/packaging/windows/mdsplus.nsi +++ b/deploy/packaging/windows/mdsplus.nsi @@ -19,6 +19,9 @@ ; - The uninstaller must not use an unguarded "RmDir /r $INSTDIR" because in some ; scenarios that can wipe system files from the Windows computer. For more details, ; refer to the Nullsoft documentation. +; - VS Code has an NSIS extension that makes it much easier to edit the installer source. +; https://marketplace.visualstudio.com/items?itemName=idleberg.nsis + Name "MDSplus${BNAME}-${ARCH} ${MAJOR}.${MINOR}-${RELEASE}" From 7b8ea7bcbe79f1cce100b04e5a6adf881a85a69e Mon Sep 17 00:00:00 2001 From: mwinkel-dev <122583770+mwinkel-dev@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:18:26 -0600 Subject: [PATCH 5/5] Fix: correct typos in the Windows installer --- deploy/packaging/windows/mdsplus.nsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/packaging/windows/mdsplus.nsi b/deploy/packaging/windows/mdsplus.nsi index 8140f0f212..c0e60de0c7 100644 --- a/deploy/packaging/windows/mdsplus.nsi +++ b/deploy/packaging/windows/mdsplus.nsi @@ -664,12 +664,12 @@ Section uninstall Var /GLOBAL InstBaseName ${GetBaseName} $INSTDIR $InstBaseName ${IfNot} "$InstBaseName" == ${MULTIUSER_INSTALLMODE_INSTDIR} - ${ToLog} "Non-standard dir, so MDPlus was unregistered but not deleted." + ${ToLog} "Non-standard dir, so MDSplus was unregistered but not deleted." ; Windows Defender chokes on the following MessageBox even though the "/SD" makes it silent. So use a more verbose approach. - ; MessageBox MB_OKCANCEL "Non-standard dir, so MDPlus was unregistered but not deleted." /SD IDOK IDOK next + ; MessageBox MB_OKCANCEL "Non-standard dir, so MDSplus was unregistered but not deleted." /SD IDOK IDOK next IfSilent next gui ; Note that ${IfSilent} is not available in NSIS v3.09 gui: - MessageBox MB_OK "Non-standard dir, so MDPlus was unregistered but not deleted." IDOK next + MessageBox MB_OK "Non-standard dir, so MDSplus was unregistered but not deleted." IDOK next next: ; ; Because of the above check, could probably use "RmDir /r $INSTDIR" here. Instead, use a more conservative approach.