A sample script that uses several cool functions (replace txt, mutually exclusive functions, MUI, patch install, etc.)
From NSIS Wiki
Jump to navigationJump to search
Author: frenchbenj (talk, contrib) |
Description
This installer is a real life installer so i blanked out all the file names, etc. It is provided as an example of a few things you can do in NSIS.
The problems I was trying to solve:
- Two different installation modes, a "patch" one and a "full" one.
- The full install removes any previous install and copies all the files to the host system.
- When doing a full install, a bunch of config files need to be updated (e.g. IP addresses, etc). The files i have in the executable directory (that gets compressed into the NSIS executable) include those config files. I replaced whatever needed to be updated in those files by "variables", indicated by two @ symbols (e.g. @IPAddress@). I then have the installer, after it has copied all the files over into the host's install directory, search the config files for the @xxxx@ variables and replace them with string passed by the user through an InstallOptions screen. I have only kept two variables in this "cleaned up" version.
- When doing a full install, i check if there is already an install out there. We know that because we copy a version.txt file on the host's system when installing (we can't mess with their registry for security reasons). If the version they have is the same as the version that we're installing, i give them a warning.
- The patch install copies a few configuration files from the host's install directory into a temporary directory on the host's machine. It then copies all the files into the host's install directory. Finally, it copies back the configuration files from the temporary directory back into the install directory. This allows keeping configuration options when installing a patch
- When doing a patch install, i abort them if they don't have a prior version, and give them the warning if they are installing the same version
The Script
;====================================================== ; Include !include "MUI.nsh" ;====================================================== ; Installer Information Name "Installer version 1.0.3" OutFile "Installer_1.0.3.exe" InstallDir C:\App ;====================================================== ; Modern Interface Configuration !define MUI_HEADERIMAGE !define MUI_ABORTWARNING !define MUI_COMPONENTSPAGE_SMALLDESC !define MUI_HEADERIMAGE_BITMAP_NOSTRETCH !define MUI_FINISHPAGE !define MUI_FINISHPAGE_TEXT "Thank you for installing the appname. \r\n\n\nYou can check the install by going to the Test Page at: http://appname/testpage.jsp" !define MUI_WELCOMEFINISHPAGE_BITMAP "..\somepic.bmp" !define MUI_ICON "..\someicon.ico" ;====================================================== ; Defines for the Mutually Exclusive functions !define SF_SELECTED 1 !define SF_SUBSEC 2 !define SF_SUBSECEND 4 !define SF_BOLD 8 !define SF_RO 16 !define SF_EXPAND 32 !define SECTION_OFF 0xFFFFFFFE ;====================================================== ; Pages !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_COMPONENTS Page custom customerConfig Page custom priorApp !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH ;====================================================== ; Languages !insertmacro MUI_LANGUAGE "English" ;====================================================== ; Reserve Files ReserveFile "..\customerConfig.ini" ReserveFile "..\priorApp.ini" !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS ;====================================================== ; Variables var varCustomerName var varCustomerLicense var varPriorApp var varPreviousVersion ;====================================================== ; Sections Section "Install a full application" fullInstallSection ; Version checking logic ; This will send out a warning if the same version is already installed ; Open the file and assign it the handle $R8 FileOpen $R8 "C:\app\docs\version.txt" r ; Go to position 11 on the first line of the file FileSeek $R8 11 ; Read from there until the end of the line and copy to the variable FileRead $R8 $varPreviousVersion ; If the variable is empty (which will mean the file is not there in this case ; go to lbl_noprev. Otherwise go to lbl_prev StrCmp $varPreviousVersion "" lbl_noprev lbl_prev lbl_noprev: ; Close the file (otherwise you'll have some problems during the copying of ; files that override the version.txt file FileClose $R8 GoTo lbl_prevdone lbl_prev: ;MessageBox MB_OK "Previous version: $varPreviousVersion" ; Close the file (otherwise you'll have some problems during the copying of ; files that override the version.txt file FileClose $R8 ; If the variable is equal to 1.0.3, which is the version of this installer, ; go to lbl_warn, otherwise for to lbl_del StrCmp $varPreviousVersion "1.0.3" lbl_warn lbl_del lbl_warn: ; Ask confirmation to user that they want to install 1.0.3 even though they ; already have it installed. If they click OK, go to lbl_prevdone, if they click ; Cancel, go to lbl_ABORT MessageBox MB_OKCANCEL|MB_ICONQUESTION "Existing install is the same version as this installer. The existing install will be removed. Do you want to continue?" IDOK lbl_prevdone IDCANCEL lbl_ABORT lbl_ABORT: ; Abort the install ABORT GoTo lbl_del lbl_del: ; Delete the previous install by deleting a file and a directory Delete "$varPriorapp\bin\app.bat" ; The /r ensures the directory will be deleted even though it's not empty RMDir /r "$varPriorapp\webapps" GoTo lbl_prevdone lbl_prevdone: SetOutPath $INSTDIR File /r "app\*.*" ; Replace Customer Name in config.properties Push @CustomerName@ #text to be replaced Push $varCustomerName #replace with Push all #replace all occurrences Push all #replace all occurrences Push $INSTDIR\config\config.properties #file to replace in Call AdvReplaceInFile #call find and replace function ; Replace Customer License in config.properties Push @CustomerLicense@ #text to be replaced Push $varCustomerLicense #replace with Push all #replace all occurrences Push all #replace all occurrences Push $INSTDIR\config\config.properties #file to replace in Call AdvReplaceInFile #call find and replace function SectionEnd Section "Install a Patch to an existing app" installPatchSection ; Version checking logic ; This will send out a warning if the same version is already installed ; and abort if there is no previous version ; Open the file and assign it the handle $R8 FileOpen $R8 "C:\app\docs\version.txt" r ; Go to position 11 on the first line of the file FileSeek $R8 11 ; Read from there until the end of the line and copy to the variable FileRead $R8 $varPreviousVersion ; If the variable is empty (which will mean the file is not there in this case ; go to lbl_noprev. Otherwise go to lbl_prev StrCmp $varPreviousVersion "" lbl_noprev lbl_prev lbl_noprev: ; The variable is empty, meaning either the previous install was corrupt or the file wasn't ; there, which means they need to do a full install, not just a patch install MessageBox MB_OK "No previous version detected. You need to perform a full install" ; Close the file (otherwise you'll have some problems during the copying of ; files that override the version.txt file FileClose $R8 ; Abort the install ABORT GoTo lbl_prevdone lbl_prev: ;MessageBox MB_OK "Previous version of app: $varPreviousVersion" ; Close the file (otherwise you'll have some problems during the copying of ; files that override the version.txt file FileClose $R8 ; If the variable is equal to 1.0.3, which is the version of this installer, ; go to next command (the message box), otherwise for to two commands down (or the CreateDirectory) StrCmp $varPreviousVersion "1.0.3" "+1" "+2" ; Ask confirmation to user that they want to install 1.0.3 even though they ; already have it installed. If they click OK, go to lbl_prevdone, if they click ; Cancel, go to lbl_ABORT MessageBox MB_OKCANCEL|MB_ICONQUESTION "Existing install is the same version as this installer. Do you want to continue?" IDOK lbl_prevdone IDCANCEL lbl_ABORT lbl_ABORT: ; Abort the install ABORT GoTo lbl_prevdone lbl_prevdone: ; Save the config files into a temp directory CreateDirectory "C:\Temp3141592" CopyFiles "C:\app\config\config.properties" "C:\Temp3141592\config.properties" ; Perform the actual install SetOutPath $INSTDIR File /r "app\*.*" ; Put the config files back in their places and delete the temp directory CopyFiles "C:\Temp3141592\config.properties" "C:\app\config\config.properties" RMDir /r "C:\Temp3141592" SectionEnd ;====================================================== ;Descriptions ;Language strings LangString DESC_InstallPatch ${LANG_ENGLISH} "This will install a patch to an existing app." LangString DESC_InstallFull ${LANG_ENGLISH} "This will install a full app. PLEASE NOTE: This will delete any existing install." ;Assign language strings to sections !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${fullInstallSection} $(DESC_InstallFull) !insertmacro MUI_DESCRIPTION_TEXT ${installPatchSection} $(DESC_InstallPatch) !insertmacro MUI_FUNCTION_DESCRIPTION_END ;====================================================== ; Functions Function .onInit !insertmacro MUI_INSTALLOPTIONS_EXTRACT "..\customerConfig.ini" !insertmacro MUI_INSTALLOPTIONS_EXTRACT "..\priorApp.ini" ;============================================================== ; Mutually exclusive functions Push $0 StrCpy $R9 ${fullInstallSection} SectionGetFlags ${fullInstallSection} $0 IntOp $0 $0 | ${SF_SELECTED} SectionSetFlags ${fullInstallSection} $0 SectionGetFlags ${installPatchSection} $0 IntOp $0 $0 & ${SECTION_OFF} SectionSetFlags ${installPatchSection} $0 Pop $0 ; END FunctionEnd ;============================================================== ; Mutually exclusive functions Function .onSelChange Push $0 StrCmp $R9 ${fullInstallSection} check_sec1 SectionGetFlags ${fullInstallSection} $0 IntOp $0 $0 & ${SF_SELECTED} IntCmp $0 ${SF_SELECTED} 0 done done StrCpy $R9 ${fullInstallSection} SectionGetFlags ${installPatchSection} $0 IntOp $0 $0 & ${SECTION_OFF} SectionSetFlags ${installPatchSection} $0 Goto done check_sec1: SectionGetFlags ${installPatchSection} $0 IntOp $0 $0 & ${SF_SELECTED} IntCmp $0 ${SF_SELECTED} 0 done done StrCpy $R9 ${installPatchSection} SectionGetFlags ${fullInstallSection} $0 IntOp $0 $0 & ${SECTION_OFF} SectionSetFlags ${fullInstallSection} $0 done: Pop $0 FunctionEnd ;============================================================== ; Custom screen functions LangString TEXT_IO_TITLE ${LANG_ENGLISH} "Configuration page" LangString TEXT_IO_SUBTITLE ${LANG_ENGLISH} "This page will update application files based on your system configuration." Function priorapp ; read in section flag Push $0 SectionGetFlags ${installPatchSection} $0 ; skip complete function if not selected. that way this InstallOptions screen doesn't show ; if it's not needed (if they do a patch install, we don't need to ask them this question) StrCmp $0 ${SF_SELECTED} configdone 0 !insertmacro MUI_HEADER_TEXT "$(TEXT_IO_TITLE)" "$(TEXT_IO_SUBTITLE)" !insertmacro MUI_INSTALLOPTIONS_DISPLAY "..\priorApp.ini" !insertmacro MUI_INSTALLOPTIONS_READ $varPriorapp "..\priorApp.ini" "Field 2" "State" configdone: Pop $0 FunctionEnd Function customerConfig ; read in section flag Push $0 SectionGetFlags ${installPatchSection} $0 ; skip complete function if not selected StrCmp $0 ${SF_SELECTED} configdone 0 !insertmacro MUI_HEADER_TEXT "$(TEXT_IO_TITLE)" "$(TEXT_IO_SUBTITLE)" !insertmacro MUI_INSTALLOPTIONS_DISPLAY "..\customerConfig.ini" !insertmacro MUI_INSTALLOPTIONS_READ $varCustomerName "..\customerConfig.ini" "Field 1" "State" !insertmacro MUI_INSTALLOPTIONS_READ $varCustomerLicense "..\customerConfig.ini" "Field 2" "State" configdone: Pop $0 FunctionEnd Function AdvReplaceInFile ; call stack frame: ; 0 (Top Of Stack) file to replace in ; 1 number to replace after (all is valid) ; 2 replace and onwards (all is valid) ; 3 replace with ; 4 to replace ; save work registers and retrieve function parameters Exch $0 ;file to replace in Exch 4 Exch $4 ;to replace Exch Exch $1 ;number to replace after Exch 3 Exch $3 ;replace with Exch 2 Exch $2 ;replace and onwards Exch 2 Exch Push $5 ;minus count Push $6 ;universal Push $7 ;end string Push $8 ;left string Push $9 ;right string Push $R0 ;file1 Push $R1 ;file2 Push $R2 ;read Push $R3 ;universal Push $R4 ;count (onwards) Push $R5 ;count (after) Push $R6 ;temp file name GetTempFileName $R6 FileOpen $R1 $0 r ;file to search in FileOpen $R0 $R6 w ;temp file StrLen $R3 $4 StrCpy $R4 -1 StrCpy $R5 -1 loop_read: ClearErrors FileRead $R1 $R2 ;read line IfErrors exit StrCpy $5 0 StrCpy $7 $R2 loop_filter: IntOp $5 $5 - 1 StrCpy $6 $7 $R3 $5 ;search StrCmp $6 "" file_write2 StrCmp $6 $4 0 loop_filter StrCpy $8 $7 $5 ;left part IntOp $6 $5 + $R3 StrCpy $9 $7 "" $6 ;right part StrCpy $7 $8$3$9 ;re-join IntOp $R4 $R4 + 1 StrCmp $2 all file_write1 StrCmp $R4 $2 0 file_write2 IntOp $R4 $R4 - 1 IntOp $R5 $R5 + 1 StrCmp $1 all file_write1 StrCmp $R5 $1 0 file_write1 IntOp $R5 $R5 - 1 Goto file_write2 file_write1: FileWrite $R0 $7 ;write modified line Goto loop_read file_write2: FileWrite $R0 $R2 ;write unmodified line Goto loop_read exit: FileClose $R0 FileClose $R1 SetDetailsPrint none Delete $0 Rename $R6 $0 Delete $R6 SetDetailsPrint both Pop $R6 Pop $R5 Pop $R4 Pop $R3 Pop $R2 Pop $R1 Pop $R0 Pop $9 Pop $8 Pop $7 Pop $6 Pop $5 Pop $4 Pop $3 Pop $2 Pop $1 Pop $0 FunctionEnd
Credits
This is not all my code. Some parts come from the NSIS Wiki (the mutually exclusive sections and the replace text functions). They come from:
I created the ini files with HM NIS Edit.