pure-data/msw/pd.nsi

;
;
; https://stackoverflow.com/questions/36185539/can-i-get-nsis-to-make-a-single-installer-that-handles-local-deployment-and-syst
; ./build-nsi.sh -o "c:/tmp" "G:/gitportable/nsis2/pd-0.54-1" 0.54-1xx


;####################################################
;
; NOTE: registry settings auto change for the 32bit Pd on a 64bit OS:
;
;   SHCTX "Software\${PRODUCT_NAME}"
; turns
;   SHCTX "Software\WOW6432Node\${PRODUCT_NAME}"
;
; all the "Uninstall" keys:
;   SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
;    ...
; turns
;   SHCTX "Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
;    ...
;
; all this is healthier and in concordance with the OS.
; i.e read and write to the registry according to Pd<arch> and not to nsis<arch>
; i.e when a Pd-64bit installer is made with a 32bit nsis avoid using the 32bit registry.
;####################################################


Unicode True
!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "nsDialogs.nsh"

; App name
!if ${PDEXE} == "pd.exe"
    !if ${ARCHI} == "64"
        !define PRODUCT_NAME "Pure Data"
    !else
        !define PRODUCT_NAME "Pure Data (${ARCHI}-bit)"
    !endif
    !define PD_FOLDER "Pd"
!else
    !if ${ARCHI} == "64"
        !define PRODUCT_NAME "Pd64"
    !else
        !define PRODUCT_NAME "Pd64 (${ARCHI}-bit)"
    !endif
    !define PD_FOLDER "Pd64"
!endif
!define PRODUCT_VERSION "${PDVER}"
!define WISH_NAME "${WISHN}"
!define PRODUCT_PUBLISHER "Miller Puckette"
!define PRODUCT_WEB_SITE "http://www.puredata.info"

; MUI Settings / Icons
!define MUI_ICON "pd.ico"
!define MUI_UNICON "pd.ico"
; MUI Settings / Wizard
!define MUI_WELCOMEFINISHPAGE_BITMAP "big.bmp"
; MUI Settings / Header
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "small.bmp"
!define MUI_HEADERIMAGE_UNBITMAP "small.bmp"

Var VInstDirBase
Var VContext
Var Vshortpath
Var PreUninstallerDisplayName
Var PreUninstallerPath
Var PreUninstallerUninstallString
Var ShowPreviousInstallationDetected
Var PdProcess
Var JustForMeShow
Var VProductName
Var Vpdexe
Var Vwishexe
Var Vinstalldir
Var Vusername
Var Showaltinstall
Var Valtinstallbasename
Var Valtinstall
Var Vtempproductname
Var Vtempinstalldir

Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "/tmp/${PD_FOLDER}-${PRODUCT_VERSION}.windows-installer.exe"
InstallDir ""

; Installer
!define MUI_ABORTWARNING
!insertmacro MUI_PAGE_WELCOME
; License page
!include "license_page.nsh"
Page Custom JustForMe JustForMeLeave
Page Custom Troubleshoot TroubleshootLeave
Page Custom PreviousInstallationDetected PreviousInstallationDetectedLeave
Page Custom AltInstall AltInstallLeave
!define MUI_COMPONENTSPAGE_NODESC
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
Page Custom WriteAccess WriteAccessLeave
!insertmacro MUI_PAGE_INSTFILES

; Finish page
!define MUI_FINISHPAGE_RUN "$INSTDIR\bin\${WISH_NAME}"
!define MUI_FINISHPAGE_RUN_PARAMETERS " $Vshortpath\tcl\pd-gui.tcl"
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\doc\1.manual\index.htm"
!insertmacro MUI_PAGE_FINISH

; Uninstaller
;!insertmacro MUI_UNPAGE_CONFIRM
UninstPage Custom un.Troubleshoot un.TroubleshootLeave
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"

; Take the highest execution level available
; This means that if it's possible to, we become an administrator
RequestExecutionLevel highest

!macro ONINIT un
    Function ${un}.onInit
        ReadRegStr $Vusername HKCU "Volatile Environment" "USERNAME"
        UserInfo::GetAccountType
        Pop $0
        ${If} $0 == "Admin"
            ; If we're an admin, default to installing to C:\Program Files
            ; The value of SetShellVarContext detetmines whether SHCTX is HKLM or HKCU
            SetShellVarContext all
            StrCpy $VContext "all"
            StrCpy $VInstDirBase "$PROGRAMFILES${ARCHI}"
            StrCpy $VProductName "${PRODUCT_NAME}"
            StrCpy $JustForMeShow "yes"
        ${Else}
            ; If we're just a user, default to installing to ~\AppData\Local
            SetShellVarContext current
            StrCpy $VContext "current"
            StrCpy $VInstDirBase "$LOCALAPPDATA"
            StrCpy $VProductName "${PRODUCT_NAME} ($Vusername)"
        ${EndIf}
        ; the installer .exe might be a 32bit app and Windows is treating it
        ; "as is" but we might need to access the 64bit part of the registry.
        ${If} ${ARCHI} == "64"
            SetRegView 64
        ${Else}
            ; read "NOTE: registry settings" above
            SetRegView 32
        ${Endif}
        ; This only happens in the installer, because the uninstaller already knows INSTDIR
        ${If} $INSTDIR == ""
            Call ${un}.SetupStuff
        ${Endif}
    FunctionEnd

    Function ${un}.SetupStuff
        ReadRegStr $PreUninstallerPath SHCTX "Software\$VProductName" "${ARCHI}"
        ReadRegStr $PreUninstallerDisplayName SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "DisplayName"
        ReadRegStr $PreUninstallerUninstallString SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "UninstallString"
        ${If} $PreUninstallerPath != ""
            ; If we're already installed show the optional uninstall page:
            StrCpy $ShowPreviousInstallationDetected "yes"
        ${Endif}

        ${If} ${ARCHI} != "64"
            ${AndIf} $VInstDirBase == "$LOCALAPPDATA"
                ; use special folder if we are 32bit and on $LOCALAPPDATA
                StrCpy $INSTDIR "$VInstDirBase\${PD_FOLDER}${ARCHI}"
        ${Else}
            StrCpy $INSTDIR "$VInstDirBase\${PD_FOLDER}"
        ${Endif}
    FunctionEnd
!macroend

; Define the function twice, once for the installer and again for the uninstaller
!insertmacro ONINIT ""
!insertmacro ONINIT "un"

!macro macroA un
    Function ${un}RefreshShellIcons
        ; By jerome tremblay - april 2003
        System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v \
        (0x08000000, 0, 0, 0)'
    FunctionEnd

    Function ${un}DoCheckIfRunning
        ; perhaps all this can be done with an nsis plugin but we just use nsis as it comes from package managers.

        ReadEnvStr $0 COMSPEC
        nsExec::ExecToStack '"$0" /c tasklist | findstr /R /I /M /C:"\<$PdProcess\>"'
        Pop $1
        Pop $2
        ${If} $2 == ""
            goto good
        ${Else}
            IfSilent default
            MessageBox MB_ICONEXCLAMATION|MB_YESNO "Refusing to continue. Save your work and quit \
            any running ${PD_FOLDER} app before doing an (un)installation.$\r$\n$\r$\n\
            We found the following app open:$\r$\n$\r$\n\
            $2 $\r$\n$\r$\n\
            Force installation anyway?" IDYES good IDNO default
            default:
            quit
        ${EndIf}
        good:
    FunctionEnd

    Function ${un}CheckIfRunning
        StrCpy $PdProcess "${PD_FOLDER}.com"
        Call ${un}DoCheckIfRunning
        StrCpy $PdProcess "${PD_FOLDER}.exe"
        Call ${un}DoCheckIfRunning
    FunctionEnd

    Function ${un}AssociatePdFiles
        ; Set file ext associations
        WriteRegStr SHCTX "Software\Classes\.pd" "" "PureData"
        WriteRegStr SHCTX "Software\Classes\PureData" "" ""
        WriteRegStr SHCTX "Software\Classes\PureData\DefaultIcon" "" "$Vinstalldir\bin\$Vpdexe"
        WriteRegStr SHCTX "Software\Classes\PureData\shell" "" ""
        WriteRegStr SHCTX "Software\Classes\PureData\shell\open" "" ""
        WriteRegStr SHCTX "Software\Classes\PureData\shell\open\command" "" '"$Vinstalldir\bin\$Vwishexe" "$Vinstalldir\tcl\pd-gui.tcl" "%1"'
        ; Set file ext icon
        WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd" "" ""
        WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd\OpenWithList" "a" "$Vpdexe"
        WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd\OpenWithList" "MRUList" ""
        WriteRegBin SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd\OpenWithProgids" "PureData" "0"
        WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd\UserChoice" "Progid" "PureData"
        ; Refresh Shell Icons
        Call ${un}RefreshShellIcons
    FunctionEnd

    Function ${un}Wipeprefs
        DeleteRegKey HKCU "Software\Pure-Data"
        HideWindow
        MessageBox MB_ICONINFORMATION|MB_OK "Pd preferences had been cleared"
        quit
    FunctionEnd
!macroend

!insertmacro macroA ""
!insertmacro macroA "un."

Function JustForMe
    ${If} $JustForMeShow == "yes"
        nsDialogs::Create 1018
        Pop $0
        !insertmacro MUI_HEADER_TEXT \
        "User selection" "This section is only aviable for admin users."
        ${NSD_CreateLabel} 0 0 100% 40% "Since you are admin you can \
        select to install for all users or just for you.$\r$\n$\r$\n\
        If you select 'Just me' your start menu folder will be named \
        '${PRODUCT_NAME} ($Vusername)'.$\r$\n$\r$\nAlso in the next \
        section there's a tool to set which app opens .pd files. \
        Select here the scope for it."
        ${NSD_CreateRadioButton} 0 40% 100% 10% "All users"
        Pop $1
        SendMessage $1 ${BM_CLICK} "" "" ; Must select a default
        ${NSD_CreateRadioButton} 0 50% 100% 15% "Just me"
        Pop $2
        nsDialogs::Show
    ${EndIf}
FunctionEnd

Function JustForMeLeave
    ${NSD_GetState} $2 $3
    ${If} $3 == ${BST_CHECKED}
        StrCpy $ShowPreviousInstallationDetected " "
        SetShellVarContext current
        StrCpy $VContext "current"
        StrCpy $VInstDirBase "$LOCALAPPDATA"
        StrCpy $VProductName "${PRODUCT_NAME} ($Vusername)"
        Call .SetupStuff
    ${Else}
    ; we have to do all this again if we change our minds later and came back to here.
        StrCpy $ShowPreviousInstallationDetected " "
        SetShellVarContext all
        StrCpy $VContext "all"
        StrCpy $VInstDirBase "$PROGRAMFILES${ARCHI}"
        StrCpy $VProductName "${PRODUCT_NAME}"
        Call .SetupStuff
    ${Endif}
FunctionEnd

Function CheckValidAppFolder
    StrCpy $Vpdexe " "
    StrCpy $Vwishexe " "
    nsDialogs::SelectFolderDialog "Select the Pd(64) app folder to open .pd \
    files.$\r$\nNormally is 'C:\Program files\Pd'" "c:\"
    Pop $0
    StrCpy $Vinstalldir "$0"
    IfFileExists $0\bin\pd.exe 0 +2
    StrCpy $Vpdexe "pd.exe"
    IfFileExists $0\bin\pd64.exe 0 +2
    StrCpy $Vpdexe "pd64.exe"
    IfFileExists $0\bin\wish85.exe 0 +2
    StrCpy $Vwishexe "wish85.exe"
    IfFileExists $0\bin\wish86.exe 0 +2
    StrCpy $Vwishexe "wish86.exe"
FunctionEnd

Function DefaultToOpen
    loop:
    Call CheckValidAppFolder
    ${If} $Vpdexe != " "
        ${AndIf} $Vwishexe != " "
            HideWindow
            Call AssociatePdFiles
            MessageBox MB_ICONINFORMATION|MB_OK "Successfully set Pd(64) app \
            at '$Vinstalldir' to open .pd files."
            quit
        ${Else}
            MessageBox MB_ICONINFORMATION|MB_RETRYCANCEL "Not a valid Pd \
            or Pd64 app folder. Select a valid one." IDRETRY loop IDCANCEL bye
            goto loop
    ${Endif}
    bye:
    quit
FunctionEnd

Function Troubleshoot
    nsDialogs::Create 1018
    Pop $0
    !insertmacro MUI_HEADER_TEXT \
    "Troubleshoot existing installation" "Preferences repair tools."
    ${NSD_CreateLabel} 0 0 100% 40% "Sometimes your pre-existing Pd settings \
    (if there are any) can go wrong and the app might not be able to load. You \
    don't have to use this tool unless the app is failing to start. \
    $\r$\n$\r$\nAlso there's a tool to set which app is the default to \
    open .pd files."

    ${NSD_CreateRadioButton} 0 40% 100% 10% "Continue (normal installation)"
    Pop $1
    SendMessage $1 ${BM_CLICK} "" "" ; Must select a default
    ${NSD_CreateRadioButton} 0 55% 100% 10% "Clear Pd preferences and exit."
    Pop $2
    ${NSD_CreateRadioButton} 0 70% 100% 10% "Repair which Pd(64) defaults to \
    open .pd files and exit"
    Pop $3
    nsDialogs::Show
FunctionEnd

Function TroubleshootLeave
    ${NSD_GetState} $2 $4
    ${If} $4 == ${BST_CHECKED}
        Call Wipeprefs
    ${EndIf}
    ${NSD_GetState} $3 $4
    ${If} $4 == ${BST_CHECKED}
        Call DefaultToOpen
    ${EndIf}
FunctionEnd

Function PreviousInstallationDetected
    ; do an early warning if we find Pd is running
    Call CheckIfRunning
    ${If} $ShowPreviousInstallationDetected == "yes"
        nsDialogs::Create 1018
        Pop $0
        !insertmacro MUI_HEADER_TEXT \
        "Previous Installation" "Select if you are going to uninstall \
        previous installation"
        ${NSD_CreateLabel} 0 0 100% 40% "System tells there is already a \
        Pd installation:$\r$\n$\r$\n \
        '$PreUninstallerDisplayName' on '$PreUninstallerPath' $\r$\n$\r$\n\
        If you are going to upgrade or downgrade it is recommended that you run \
        the uninstaller. If not, install as an alternate app or proceed the \
        installation to a different location"
        ${NSD_CreateRadioButton} 0 40% 50% 10% "Run the uninstaller (recommended)"
        Pop $1
        SendMessage $1 ${BM_CLICK} "" "" ; Must select a default
        ${NSD_CreateRadioButton} 0 55% 50% 10% "Install as an alternate app"
        Pop $2
        ${NSD_CreateRadioButton} 0 70% 50% 10% "Continue"
        Pop $3
        nsDialogs::Show
    ${EndIf}
FunctionEnd

Function DoUninstall
    ExecWait '"$PreUninstallerUninstallString" /S _?=$PreUninstallerPath'
    Delete "$PreUninstallerUninstallString"
    RMDir "$PreUninstallerPath"
    ; don't show the uninstall window if you hit "back"
    StrCpy $ShowPreviousInstallationDetected "no"
FunctionEnd

Function AltInstall
    ${If} $Showaltinstall == "yes"
        nsDialogs::Create 1018
        Pop $0
        !insertmacro MUI_HEADER_TEXT \
        "Alternate installation" "Make an 'alternate' installation."
        ${NSD_CreateLabel} 0 0 100% 40% "This installation will be an \
        alternate install.$\r$\n$\r$\nThe start menu folder, desktop \
        icon and uninstaller will be named with the following string. \
        You can change it but is recommnded that you keep it short."
        StrCpy $1 "${PD_FOLDER}-${PDVER}-(alt)"
        ${NSD_CreateText} 0 40% 50% 10% "$1"
        Pop $Valtinstallbasename
        nsDialogs::Show
    ${EndIf}
FunctionEnd

Function AltInstallLeave
    ${If} $Showaltinstall == "yes"
        ${NSD_GetText} $Valtinstallbasename $0
        StrCpy $Vtempproductname "$VProductName"
        StrCpy $Vtempinstalldir "$INSTDIR"
        StrCpy $VProductName "$0"
        StrCpy $INSTDIR "$VInstDirBase\$VProductName"
        StrCpy $Valtinstall "yes"
    ${EndIf}
FunctionEnd

Function UndoAltInstall
    ${If} $Valtinstall == "yes"
        StrCpy $VProductName "$Vtempproductname"
        StrCpy $INSTDIR "$Vtempinstalldir"
        StrCpy $Valtinstall "no"
    ${EndIf}
    StrCpy $Showaltinstall "no"
FunctionEnd

Function PreviousInstallationDetectedLeave
    ${NSD_GetState} $1 $4
    ${If} $4 == ${BST_CHECKED}
        Call UndoAltInstall
        Call DoUninstall
    ${EndIf}
    ${NSD_GetState} $2 $4
    ${If} $4 == ${BST_CHECKED}
        StrCpy $Showaltinstall "yes"
    ${EndIf}
    ${NSD_GetState} $3 $4
    ${If} $4 == ${BST_CHECKED}
        Call UndoAltInstall
    ${EndIf}
FunctionEnd

;https://nsis.sourceforge.io/Go_to_a_NSIS_page
Function RelGotoPage
    IntCmp $R9 0 0 Move Move
    StrCmp $R9 "X" 0 Move
    StrCpy $R9 "120"

    Move:
    SendMessage $HWNDPARENT "0x408" "$R9" ""
FunctionEnd

Function WriteAccess
    IfSilent skip
    StrCpy $0 " "
    CreateDirectory "$INSTDIR\chk"
    WriteINIStr "$INSTDIR\chk\writecheck.ini" section1 check "yes"
    IfFileExists "$INSTDIR\chk\writecheck.ini" 0 +2
    StrCpy $0 "yes"
    ${If} $0 == "yes"
        Delete "$INSTDIR\chk\writecheck.ini"
        RmDir "$INSTDIR\chk\"
        return
    ${Else}
        MessageBox MB_ICONINFORMATION|MB_RETRYCANCEL "We don't have \
        permissions to write to folder: '$INSTDIR'. Please select a writtable \
        folder." IDRETRY loop IDCANCEL bye
        goto loop
    ${Endif}
    bye:
    quit
    loop:
    StrCpy $R9 -1 ;Relative page number.
    Call RelGotoPage
    skip:
FunctionEnd

Function WriteAccessLeave
FunctionEnd

SectionGroup /e "${PRODUCT_NAME}"
    Section "Application" PureData
        ; When on silent installs custom pages are skipped but we need to \
        ; ckeck if Pd is running and uninstall previous installation.
        IfSilent x_silent x_nosilent
        x_silent:
        Call CheckIfRunning
        ${If} $ShowPreviousInstallationDetected == "yes"
            Call DoUninstall
        ${EndIf}
        x_nosilent:
        SectionIn RO
        !include "install_files_list.nsh"
        ; write ini file
        WriteINIStr "$INSTDIR\uninst.ini" section1 vname "$VProductName"
        WriteINIStr "$INSTDIR\uninst.ini" section1 shellcontext "$VContext"
    SectionEnd

    Section "Create Startmenu entry" StartMenu
        WriteINIStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
        WriteINIStr "$INSTDIR\uninst.ini" section1 uninstallstartmenu "yes"
        ; ugly hack so the app shows up in "recently added"
        ; should have been one "SetShellVarContext $VContext" but it refuses to compile
        ${If} $VContext == "all"
            SetShellVarContext all
        ${Else}
            SetShellVarContext current
        ${EndIf}
        CreateDirectory "$SMPROGRAMS\$VProductName\"
        CreateShortCut "$SMPROGRAMS\$VProductName\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url" "" "%SYSTEMROOT%\system32\shell32.dll" 14
        CreateShortCut "$SMPROGRAMS\$VProductName\$VProductName.lnk" "$INSTDIR\bin\${WISH_NAME}" '"$INSTDIR\tcl\pd-gui.tcl"' "$INSTDIR\bin\${PDEXE}" 0
    SectionEnd

    Section "Create Desktop Shortcut" DesktopShortcut
        CreateShortCut "$Desktop\$VProductName.lnk" "$INSTDIR\bin\${WISH_NAME}" '"$INSTDIR\tcl\pd-gui.tcl"' "$INSTDIR\bin\${PDEXE}" 0
        WriteINIStr "$INSTDIR\uninst.ini" section1 uninstalldesktopshortcut "yes"
    SectionEnd

    Section "Open .pd-files with Pd" SetFileAssociations
        StrCpy $Vpdexe "${PDEXE}"
        StrCpy $Vwishexe "${WISH_NAME}"
        StrCpy $Vinstalldir "$INSTDIR"
        Call AssociatePdFiles
        WriteINIStr "$INSTDIR\uninst.ini" section1 uninstallfileassociation "yes"
    SectionEnd

    Section
        ; make variable $Vshortpath
        GetFullPathName /SHORT $Vshortpath $INSTDIR
    SectionEnd
SectionGroupEnd


Section -Post
    WriteUninstaller "$INSTDIR\uninst.exe"
    ${If} $Valtinstall != "yes"
        WriteRegStr SHCTX "Software\$VProductName" "${ARCHI}" $INSTDIR
    ${EndIf}
    ; These registry entries are necessary for the program to show up in the Add/Remove programs dialog
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "DisplayName" "$VProductName ${PRODUCT_VERSION}"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "UninstallString" "$INSTDIR\uninst.exe"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "ModifyPath" "$INSTDIR\uninst.exe"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "DisplayIcon" "$INSTDIR\bin\${PDEXE}"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "DisplayVersion" "${PRODUCT_VERSION}"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "Publisher" "${PRODUCT_PUBLISHER}"

    ;WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "NoModify" 1
    ;WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "NoRepair" 1

    ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
    IntFmt $0 "0x%08X" $0
    WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName" "EstimatedSize" "$0"
SectionEnd


Function un.onUninstSuccess
    IfSilent skip
    HideWindow
    MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed \
    from your computer."
    skip:
    quit
FunctionEnd

Function un.sure
    IfSilent skip
    MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you \
    want to completely remove $(^Name) and all of its components?" IDYES +2
    Abort
    skip:
FunctionEnd

Function un.dirty
    IfSilent skip
    HideWindow
    MessageBox MB_ICONINFORMATION|MB_OK "We could not find '$INSTDIR\uninst.ini' \
    so we could not remove the startup program folder and other Windows registry \
    stuff. We could only delete '$INSTDIR'."
    skip:
FunctionEnd

Function un.deletefiles
    !include "uninstall_files_list.nsh"
    Delete "$INSTDIR\${PRODUCT_NAME}.url"
    Delete "$INSTDIR\uninst.ini"
    Delete "$INSTDIR\uninst.exe"
    RmDir "$INSTDIR"
FunctionEnd

Function un.Troubleshoot
    nsDialogs::Create 1018
    Pop $0
    !insertmacro MUI_HEADER_TEXT \
    "Troubleshoot existing installation" "Preferences repair tools."
    ${NSD_CreateLabel} 0 0 100% 40% "Sometimes your ${PD_FOLDER} \
    preferences can go wrong and the app might not be able to load. \
    $\r$\n$\r$\nAlso there's a tool to repair .pd files associations \
    to open with this ${PD_FOLDER} installation."

    ${NSD_CreateRadioButton} 0 40% 100% 10% "Uninstall the app"
    Pop $1
    SendMessage $1 ${BM_CLICK} "" "" ; Must select a default
    ${NSD_CreateRadioButton} 0 55% 100% 10% "Clear Pd preferences and exit."
    Pop $2
    ${NSD_CreateRadioButton} 0 70% 100% 10% "Repair .pd files associations \
    and exit"
    Pop $3
    GetDlgItem $R0 $HWNDPARENT 1
    SendMessage $R0 ${WM_SETTEXT} 0 "STR:Next"
    nsDialogs::Show
FunctionEnd

Function un.TroubleshootLeave
    ${NSD_GetState} $2 $4
    ${If} $4 == ${BST_CHECKED}
        Call un.Wipeprefs
    ${EndIf}
    ${NSD_GetState} $3 $4
    ${If} $4 == ${BST_CHECKED}
        ReadINIStr $0 "$INSTDIR\uninst.ini" section1 shellcontext
        ${If} "$0" != "all"
            SetShellVarContext current
        ${EndIf}
        StrCpy $Vpdexe "${PDEXE}"
        StrCpy $Vwishexe "${WISH_NAME}"
        StrCpy $Vinstalldir "$INSTDIR"
        Call un.AssociatePdFiles
        MessageBox MB_ICONINFORMATION|MB_OK "Successfully set the app \
            at '$Vinstalldir' to open .pd files."
        quit
    ${EndIf}
FunctionEnd

Section Uninstall

    ; let the uninstaller quit if Pd is running
    Call un.CheckIfRunning
    IfSilent skip
    Call un.sure
    skip:
    StrCpy $0 " "
    IfFileExists "$INSTDIR\uninst.ini" 0 +2
    StrCpy $0 "yes"
    ${If} $0 == "yes"
        ; read ini file
        ReadINIStr $VProductName "$INSTDIR\uninst.ini" section1 vname
        ReadINIStr $0 "$INSTDIR\uninst.ini" section1 shellcontext
        ${If} "$0" != "all"
            SetShellVarContext current
        ${EndIf}
        ReadINIStr $0 "$INSTDIR\uninst.ini" section1 uninstallstartmenu
        ${If} "$0" == "yes"
            Delete "$SMPROGRAMS\$VProductName\Website.lnk"
            Delete "$SMPROGRAMS\$VProductName\$VProductName.lnk"
            RMDir "$SMPROGRAMS\$VProductName"
        ${EndIf}
        ReadINIStr $0 "$INSTDIR\uninst.ini" section1 uninstalldesktopshortcut
        ${If} "$0" == "yes"
            Delete "$DESKTOP\$VProductName.lnk"
        ${EndIf}
        ReadINIStr $0 "$INSTDIR\uninst.ini" section1 uninstallfileassociation
        ${If} "$0" == "yes"
            DeleteRegKey SHCTX "Software\Classes\.pd"
            DeleteRegKey SHCTX "Software\Classes\PureData"
            ; file ext icon
            DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pd"
        ${EndIf}
        Call un.deletefiles
        Call un.RefreshShellIcons
        DeleteRegKey SHCTX "Software\$VProductName"
        DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\$VProductName"
        SetAutoClose true
    ${Else}
        ; no ini file. dirty uninstall
        Call un.deletefiles
        Call un.dirty
        Call un.RefreshShellIcons
        SetAutoClose true
    ${EndIf}
SectionEnd