From 49a24da23d3cdcb4fcc91e220604fa96ae2875ab Mon Sep 17 00:00:00 2001 From: Miguel Angel Mulero Martinez Date: Sat, 13 Jan 2018 08:58:16 +0100 Subject: [PATCH] Windows installer: uninstall only what was installed --- assets/windows/UnInst.nsh | 377 +++++++++++++++++++++++++++++++++++ assets/windows/installer.nsi | 35 +++- 2 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 assets/windows/UnInst.nsh diff --git a/assets/windows/UnInst.nsh b/assets/windows/UnInst.nsh new file mode 100644 index 00000000..125e6e72 --- /dev/null +++ b/assets/windows/UnInst.nsh @@ -0,0 +1,377 @@ +/* + +NSIS Uninstaller Data +Copyright 2014 Aleksandr Ivankiv + +Modified by the Betaflight Team project to let it be used out of the uninstall section +*/ + +;-------------------------------- + +!ifndef UNINST_INCLUDED +!define UNINST_INCLUDED + +!verbose push +!verbose 3 + +!ifndef UNINST_VERBOSE + !define UNINST_VERBOSE 3 +!endif + +!verbose ${UNINST_VERBOSE} + +;-------------------------------- +;Header files required by Uninstaller Data + +!include "FileFunc.nsh" +!include "TextFunc.nsh" + +;-------------------------------- +;Variables + +Var List +Var Log +Var Tmp +Var UNINST_DAT +Var UNINST_EXE +Var UNINST_DEL_FILE + +;-------------------------------- +;Default language strings + +!define UNINST_EXCLUDE_ERROR_DEFAULT "Error creating an exclusion list." +!define UNINST_DATA_ERROR_DEFAULT "Error creating the uninstaller data: $\r$\nCannot find an exclusion list." +!define UNINST_DAT_NOT_FOUND_DEFAULT "$UNINST_DAT not found, unable to perform uninstall. Manually delete files." +!define UNINST_DAT_MISSING_DEFAULT "$UNINST_DAT is missing, some elements could not be removed. These can be removed manually." +!define UNINST_DEL_FILE_DEFAULT "Delete File" + +;-------------------------------- +;Language strings macro + +!macro SETSTRING NAME + + !ifndef "${NAME}" + !ifdef UNINST_LOCALIZE + !define "${NAME}" "$(${NAME})" + !else + !define "${NAME}" "${${NAME}_DEFAULT}" + !endif + !endif + +!macroend + +;-------------------------------- +;Initialization macro + +!macro UNINST_INIT + + ;Default settings + !ifndef UninstName + !define UninstName "Uninstall" + !endif + !ifndef UninstHeader + !define UninstHeader "=========== Uninstaller Data please do not edit this file ===========" + !endif + !insertmacro SETSTRING "UNINST_EXCLUDE_ERROR" + !insertmacro SETSTRING "UNINST_DATA_ERROR" + !insertmacro SETSTRING "UNINST_DAT_NOT_FOUND" + !insertmacro SETSTRING "UNINST_DAT_MISSING" + !insertmacro SETSTRING "UNINST_DEL_FILE" + StrCpy $UNINST_DEL_FILE "${UNINST_DEL_FILE}" + StrCpy $UNINST_DAT "$OUTDIR\${UninstName}.dat" + StrCpy $UNINST_EXE "$OUTDIR\${UninstName}.exe" + +!macroend + +;-------------------------------- +;Change name of file + +!macro UNINST_NAME Name + + !ifdef UninstName + !undef UninstName + !define UninstName "${Name}" + !else + !define UninstName "${Name}" + !endif + !insertmacro UNINST_INIT + +!macroend + +;-------------------------------- +;Create an exclusion list + +!macro UNINSTALLER_DATA_BEGIN + + !insertmacro UNINST_EXCLUDE + +!macroend + +!macro UNINST_EXCLUDE + + !verbose push + !verbose ${UNINST_VERBOSE} + + !insertmacro UNINST_INIT + + StrCmp "$PLUGINSDIR" "" 0 +2 + InitPluginsDir + + GetTempFileName $Tmp $PLUGINSDIR + + IfErrors 0 UninstExclude + !ifndef UNINST_ERROR + !define UNINST_ERROR + MessageBox MB_OK|MB_ICONEXCLAMATION "${UNINST_EXCLUDE_ERROR}" /SD IDOK + Goto +3 + !endif + + UninstExclude: + FileOpen $List "$Tmp" w + ${Locate} "$OUTDIR" "/L=FD" "${ExcludeList_Func_CallBack}" + FileClose $List + + !verbose pop + +!macroend + +!macro UNINST_FUNCTION_EXCLUDELIST + + Function ExcludeList + + FileWrite $List "$R9$\r$\n" + Push $0 + + FunctionEnd + +!macroend + +!ifndef ExcludeList_Func_CallBack + !insertmacro UNINST_FUNCTION_EXCLUDELIST + !define ExcludeList_Func_CallBack "ExcludeList" +!endif + +;---------------------------------------------------------------- +;Write Uninstaller Data + +!macro UNINSTALLER_DATA_END + + !insertmacro UNINST_DATA + +!macroend + +!macro UNINST_DATA + + !verbose push + !verbose ${UNINST_VERBOSE} + + !insertmacro UNINST_INIT + + IfFileExists "$Tmp" UninstData + !ifndef UNINST_ERROR + !define UNINST_ERROR + ${GetFileName} "$UNINST_DAT" $R0 + MessageBox MB_OK|MB_ICONEXCLAMATION "${UNINST_DATA_ERROR}" /SD IDOK + !endif + Goto DoneUninstData + + UninstData: + FileOpen $Log "$UNINST_DAT" a + FileOpen $List "$Tmp" r + + FileRead $Log $1 + IfErrors 0 +2 + FileWrite $Log "${UninstHeader}$\r$\n" + + ${Locate} "$OUTDIR" "/L=FD" "${UninstallData_Func_CallBack}" + + FileClose $List + FileClose $Log + + DoneUninstData: + StrCpy $Tmp "" + + !verbose pop + +!macroend + +!macro UNINST_FUNCTION_UNINSTDATA + + Function UninstallData + + StrCmp $R9 $UNINST_DAT Done + + FileSeek $List 0 SET + + LoopReadList: + FileRead $List $1 ${NSIS_MAX_STRLEN} + IfErrors DoneReadList + + ${TrimNewLines} $1 $R0 + StrCmp $R0 $R9 Done + + Goto LoopReadList + + DoneReadList: + FileSeek $Log 0 SET + + LoopReadLog: + FileRead $Log $1 ${NSIS_MAX_STRLEN} + IfErrors DoneReadLog + + ${TrimNewLines} $1 $R0 + StrCmp $R0 $R9 Done + + Goto LoopReadLog + + DoneReadLog: + FileSeek $Log 0 END + FileWrite $Log "$R9$\r$\n" + + Done: + Push $0 + + FunctionEnd + +!macroend + +!ifndef UninstallData_Func_CallBack + !insertmacro UNINST_FUNCTION_UNINSTDATA + !define UninstallData_Func_CallBack "UninstallData" +!endif + +;---------------------------------------------------------------- +;Uninstall Files + +!macro INST_DELETE Path Name + !insertmacro UNINST_DELETE_MULTIPLE ${Path} ${Name} "" +!macroend + +!macro UNINST_DELETE Path Name + !insertmacro UNINST_DELETE_MULTIPLE ${Path} ${Name} "un." +!macroend + +!macro UNINST_DELETE_MULTIPLE Path Name un + + !verbose push + !verbose ${UNINST_VERBOSE} + + !if "${Path}" == "" + StrCpy $OUTDIR "$INSTDIR" + !else + StrCpy $OUTDIR "${Path}" + !endif + !if "${Name}" == "" + !insertmacro UNINST_NAME "Uninstall" + !else + !insertmacro UNINST_NAME "${Name}" + !endif + + !insertmacro UNINST_INIT + + IfFileExists "$UNINST_DAT" +3 + !ifdef UNINST_TERMINATE + MessageBox MB_OK|MB_ICONSTOP "${UNINST_DAT_NOT_FOUND}" /SD IDOK + Quit + !else + MessageBox MB_OK|MB_ICONEXCLAMATION "${UNINST_DAT_MISSING}" /SD IDOK + StrCpy $0 "error" + !endif + + ${If} $0 != "error" + + FileOpen $Log "$UNINST_DAT" r + Call ${un}DeleteList + FileClose $Log + + Delete "$UNINST_DAT" + + !ifdef UNINST_INTERACTIVE + ${Locate} "$OUTDIR" "/L=F" "${un.InterActive_Func_CallBack}" + ${Locate} "$OUTDIR" "/L=DE" "${un.InterActive_Func_CallBack}" + !else + Call ${un}InterActive + !endif + + ${Else} + StrCpy $0 "" + ${EndIf} + + Delete "$UNINST_EXE" + RMDir $OUTDIR + ClearErrors + + !verbose pop + +!macroend + +!macro UNINST_FUNCTION_DELETE un + + Function ${un}DeleteList + + LoopReadFile: + FileRead $Log $1 ${NSIS_MAX_STRLEN} + IfErrors DoneReadFile + + ${TrimNewLines} $1 $R0 + + IfFileExists $R0\*.* +3 + IfFileExists $R0 0 +2 + Delete $R0 + + Goto LoopReadFile + + DoneReadFile: + FileSeek $Log 0 SET + + LoopReadDIR: + FileRead $Log $1 ${NSIS_MAX_STRLEN} + IfErrors DoneReadDIR + + ${TrimNewLines} $1 $R0 + + IfFileExists $R0\*.* 0 +3 + RMDir $R0 + ClearErrors + + Goto LoopReadDIR + + DoneReadDIR: + + FunctionEnd + +!macroend + +!insertmacro UNINST_FUNCTION_DELETE "" +!insertmacro UNINST_FUNCTION_DELETE "un." + +!macro UNINST_FUNCTION_INTERACTIVE un + + Function ${un}InterActive + + StrCmp $R9 "" +8 + IfFileExists $R9\*.* 0 +3 + RMDir $R9 + Goto +4 + StrCmp $R9 $UNINST_EXE +3 + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "$UNINST_DEL_FILE '$R9'?" /SD IDNO IDNO +2 + Delete $R9 + Push $0 + + FunctionEnd + +!macroend + +!ifndef un.InterActive_Func_CallBack + !insertmacro UNINST_FUNCTION_INTERACTIVE "un." + !define un.InterActive_Func_CallBack "un.InterActive" +!endif + +!ifndef InterActive_Func_CallBack + !insertmacro UNINST_FUNCTION_INTERACTIVE "" + !define InterActive_Func_CallBack "InterActive" +!endif + +!verbose pop + +!endif diff --git a/assets/windows/installer.nsi b/assets/windows/installer.nsi index a3cc4c4c..8317c5d5 100644 --- a/assets/windows/installer.nsi +++ b/assets/windows/installer.nsi @@ -1,6 +1,7 @@ !include "MUI2.nsh" !include "FileFunc.nsh" !include "LogicLib.nsh" +!include "UnInst.nsh" # Receives variables from the command line # ${VERSION} - Version to generate (x.y.z) @@ -26,6 +27,9 @@ BrandingText "${COMPANY_NAME}" !define MUI_ICON ".\bf_installer_icon.ico" !define MUI_UNICON ".\bf_uninstaller_icon.ico" +#Define uninstall list name +!define UninstName "UninstallBF" + # Request rights user level RequestExecutionLevel highest @@ -48,6 +52,7 @@ OutFile "..\..\${DEST_FOLDER}\${FILE_NAME_INSTALLER}" !insertmacro MUI_LANGUAGE "Korean" !insertmacro MUI_LANGUAGE "Spanish" +# detect default install folder Function .onInit # Check if older version @@ -102,19 +107,35 @@ Section ${If} $R4 != "" # delete the installed files of the older version - RMDir /r $R4 + !insertmacro INST_DELETE $R4 "${UninstName}" + + # remove installation folder if empty + RMDir "$INSTDIR" + ${EndIf} ${EndIf} + # if the registry entries did not exist, we ignore the errors + ClearErrors + # define the path to which the installer should install SetOutPath $INSTDIR + # create an exclusion list for the uninstaller + !insertmacro UNINSTALLER_DATA_BEGIN + # specify the files to go in the output path File /r ${SOURCE_FILES} # create the uninstaller WriteUninstaller "$INSTDIR\${FILE_NAME_UNINSTALLER}" + # change uninstall list name + !insertmacro UNINST_NAME "unins000BF" + + # store uninstaller data + !insertmacro UNINSTALLER_DATA_END + # create shortcuts in the start menu and on the desktop CreateDirectory "$SMPROGRAMS\${GROUP_NAME}\${FOLDER_NAME}" CreateShortCut "$SMPROGRAMS\${GROUP_NAME}\${FOLDER_NAME}\${APP_NAME}.lnk" "$INSTDIR\${FILE_NAME_EXECUTABLE}" @@ -152,8 +173,14 @@ SectionEnd # create a section to define what the uninstaller does Section "Uninstall" - # delete the installed files - RMDir /r $INSTDIR + # terminate uninstaller if the .dat file does not exist + !define UNINST_TERMINATE + + # delete files + !insertmacro UNINST_DELETE "$INSTDIR" "${UninstName}" + + # remove installation folder if it is empty + RMDir "$INSTDIR" # delete the shortcuts Delete "$SMPROGRAMS\${GROUP_NAME}\${FOLDER_NAME}\${APP_NAME}.lnk" @@ -163,7 +190,7 @@ Section "Uninstall" RMDir "$SMPROGRAMS\${GROUP_NAME}" Delete "$DESKTOP\${APP_NAME}.lnk" - # remove from add/remove programs + # remove from registry DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" DeleteRegKey HKCU "Software\${GROUP_NAME}\${APP_NAME}" DeleteRegKey /ifempty HKCU "Software\${GROUP_NAME}"