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.