Tuesday, October 04, 2011

NSIS Check wether the $INSTDIR path is valid.



There are some methods that you can used to check whether or not an installation path is valid for your installer.
 
1. .onVerifyInstDir function
This will be called every time the user changes the install directory.
 2.Write a function for MUI_PAGE_CUSTOMFUNCTION_LEAVE and can then check for GetInstDirError withing that function to

check wether it is valid.

Function .onVerifyInstDir
    IfFileExists $INSTDIR\<your exe> PathGood
      Abort ; if $INSTDIR is not a winamp directory, don't let us install there
    PathGood:
FunctionEnd

!define MUI_PAGE_CUSTOMFUNCTION_LEAVE MUIDirectoryPageLeave

Function MUIDirectoryPageLeave
  GetInstDirError $0
  ${Switch} $0
    ${Case} 1
      MessageBox MB_OK "Invalid installation directory!"
      Abort
      ${Break}
    ${Case} 2
      MessageBox MB_OK "Not enough free space!"
      Abort
      ${Break}
  ${EndSwitch}
FunctionEnd

Both the above functions has some issues.
1 will only let you to continue if there is a folfer exists as described in "$INSTDIR" path.

2 will not recognize foledr names which ends with spaces or dots as invalid
eg
 C:\Abc\Your Family \your product ; invalid since it contains space after Your Family
 C:\Abc\Your Family.\your product ; invalid since it contains a dot after Your Family
 C:\Abc\Your Family\your product ; valid

Therefore we have to write our own functions to test wether a path is valid.
Following is sample codes that you can use. (Hope you have bettr understaning of NSIS functions and macros)


!define IndexOfDef "!insertmacro IndexOfMac"
!define IsContainsDotOrSpaceAtEndDef "!insertmacro IsContainsDotOrSpaceAtEndMac"
!define IsContainsInvalidCharactersDef "!insertmacro IsContainsInvalidCharactersMac"

;;;;;   Utility Macros  ;;;;;;;;;;;;
!macro IndexOfMac resVar String Char
Push "${Char}"
Push "${String}"
Call IndexOfFun
Pop "${resVar}"
!macroend

!macro IsContainsDotOrSpaceAtEndMac resVar String
Push "${String}"
Call IsContainsDotOrSpaceAtEnd
Pop "${resVar}"
!macroend

!macro IsContainsInvalidCharactersMac resVar String
Push "${String}"
Call IsContainsInvalidCharacters
Pop "${resVar}"
!macroend

!macro IsValidPathMacro resString String
StrCpy $R1 ${String}
StrCpy ${resString} True
StrCpy $R4 0 ; loop counter
loop:
    IntOp $R4 $R4 + 1    ; increment the count

    ;;;; Split string to two parts ;;;;;;
    ${IndexOfDef} $R0 $R1 "\"
    ${If} $R0 > -1
        StrCpy $R2 $R1 $R0 0        ;first part o fthe string
        IntOp $R0 $R0 + 1            ;length
        StrCpy $R3 $R1 "" $R0       ;second part of the string
        StrCpy $R1 $R3              ;update R1
    ${Else}
        Goto endOfMac
    ${EndIf}
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   
    ${If} $R4 != 1 ; since first part is the disk drive (eg C:, D:) wich is handleed by nsis itself we can omit this

check for  count = 1
   
        ${IsContainsDotOrSpaceAtEndDef} $R0 $R2
        ${If} $R0 == True
            MessageBox MB_OK "Path should not contain dots or spaces at the end"
            Goto errFound
        ${EndIf}
   
        ${IsContainsInvalidCharactersDef} $R0 $R2
        ${If} $R0 == True
            MessageBox MB_OK 'Path should not contain any of these characters:  ?, *, < , >,", \, /, :, |'
            Goto errFound
        ${EndIf}
       
    ${EndIf}
Goto loop
   
    errFound:
    StrCpy ${resString} False
    endOfMac:
!macroend
;;;;;;;Utility Macros End;;;;;;;;;;;;;;

;;;;;  Utility Functions  ;;;;;;;;;;;;;;;;
Function IndexOfFun
    Exch $R0
    Exch
    Exch $R1
    Push $R2
    Push $R3
     
    StrCpy $R3 $R0
    StrCpy $R0 -1
    IntOp $R0 $R0 + 1
    StrCpy $R2 $R3 1 $R0
    StrCmp $R2 "" +2
    StrCmp $R2 $R1 +2 -3
     
    StrCpy $R0 -1
     
    Pop $R3
    Pop $R2
    Pop $R1
    Exch $R0
FunctionEnd

Function IsContainsDotOrSpaceAtEnd
  Exch $R0
  Push $R1
  Push $R2
  Push $R3
  StrCpy $R3 False ; contain the results
 
  StrLen $R1 $R0
  IntOp $R1 $R1 - 1
  StrCpy $R2 $R0 1 $R1  ; last character of the string

  ${If} $R2 == "."
    StrCpy $R3 True
  ${Else}
      ${If} $R2 == " "
        StrCpy $R3 True
      ${EndIf}
  ${EndIf}

  StrCpy $R0 $R3
  Pop $R3
  Pop $R2
  Pop $R1
  Exch $R0
FunctionEnd

Function IsContainsInvalidCharacters
Exch $R0
    Push $R1
    Push $R2
    StrCpy $R2 True ; contain the results
    ${IndexOf}  $R1 $R0 "\"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "/"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "*"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "?"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "<"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 ">"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 ":"
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "$\""
    IntCmp $R1 -1 0 invalidFound invalidFound
    ${IndexOf}  $R1 $R0 "|"
    IntCmp $R1 -1 0 invalidFound invalidFound
    StrCpy $R2 False
    invalidFound:

    StrCpy $R0 $R2
    Pop $R2
    Pop $R1
    Exch $R0
FunctionEnd



Usage

Replace the code based on GetInstDirError on above MUIDirectoryPageLeave function with the macro we have defined.
The example code would looks like


Function MUIDirectoryPageLeave
    !insertmacro IsValidPathMacro $R0 "$INSTDIR"
    ${If} $R0 == False
        Abort
    ${EndIf}
FunctionEnd