NSIS install path validation - nsis

I want to validate the installation path selected by the user. I can't figure out how to check that so it will look like this:
You can't select a path with spaces (except Program Files)
When you click "Install" then it will prompt the error saying that you have to change the installation directory
For now I have this:
Function StrStr
Exch $1 ; st=haystack,old$1, $1=needle
Exch ; st=old$1,haystack
Exch $2 ; st=old$1,old$2, $2=haystack
Push $3
Push $4
Push $5
StrLen $3 $1
StrCpy $4 0
; $1=needle
; $2=haystack
; $3=len(needle)
; $4=cnt
; $5=tmp
loop:
StrCpy $5 $2 $3 $4
StrCmp $5 $1 done
StrCmp $5 "" done
IntOp $4 $4 + 1
Goto loop
done:
StrCpy $1 $2 "" $4
Pop $5
Pop $4
Pop $3
Pop $2
Exch $1
FunctionEnd
Function .onVerifyInstDir
Push "$INSTDIR"
Push " "
Call StrStr
Pop $0
StrCpy $0 $0 1
StrCmp $0 " " 0 +2
Abort
FunctionEnd
It refuses to install while there is any space in the path. I need to modify this so Program Files will be the only exception for that rule. Also, printing error message would be helpful

This restriction makes no sense to me. Some legacy applications can't handle spaces in the path but that of course also includes the Program Files folder (although the progra~1 hack can be used as a workaround if short name generation is active).
NSIS does not have a specific way to display a error/warning message directly on the page but you can change existing text in the UI and/or display a balloon.
!include WinMessages.nsh
!define /IfNDef EM_SHOWBALLOONTIP 0x1503
!define /IfNDef EM_HIDEBALLOONTIP 0x1504
!define DIRPAGE_CHANGETEXT ; Remove this line to disable the text change
!define DIRPAGE_BALLOON ; Remove this line to disable the balloon
Function .onVerifyInstDir
FindWindow $9 "#32770" "" $HWNDPARENT
!ifdef DIRPAGE_CHANGETEXT
GetDlgItem $3 $9 1006 ; IDC_INTROTEXT
LockWindow on
!endif
StrCpy $1 0
loop:
StrCpy $2 $InstDir 1 $1
StrCmp $2 '' valid ; End of string
StrCmp $2 ' ' found_space
IntOp $1 $1 + 1
Goto loop
valid:
!ifdef DIRPAGE_CHANGETEXT
SetCtlColors $3 SYSCLR:18 SYSCLR:15
SendMessage $3 ${WM_SETTEXT} "" "STR:$(^DirText)"
LockWindow off
!endif
!ifdef DIRPAGE_BALLOON
GetDlgItem $3 $9 1019
SendMessage $3 ${EM_HIDEBALLOONTIP} "" "" ; Not required?
!endif
Return
found_space:
StrLen $1 "$ProgramFiles\"
StrCpy $2 "$InstDir\" $1
StrCmp $2 "$ProgramFiles\" valid
!ifdef DIRPAGE_CHANGETEXT
SetCtlColors $3 ff0000 transparent
SendMessage $3 ${WM_SETTEXT} "" "STR:Paths with spaces are not allowed, except for $ProgramFiles for some reason!"
LockWindow off
!endif
!ifdef DIRPAGE_BALLOON
GetDlgItem $3 $9 1019
System::Call '*(&l${NSIS_PTR_SIZE},w "Bad path!", w "Spaced not allowed in path!",p 3)p.r2'
SendMessage $3 ${EM_SHOWBALLOONTIP} "" $2 ; This will only work on XP and later (and you must use "XPStyle on")
System::Free $2
!endif
Abort
FunctionEnd
XPStyle on
Page Directory
The Next button is disabled when Abort is called inside .onVerifyInstDir. If you want to display a MessageBox when the user clicks next then you can't call Abort in .onVerifyInstDir, you will have to use the page leave function callback (where you have to verify the path again and maybe call MessageBox+Abort).

Related

Disable empty SectionGroup (NSIS)

In my script I have the conditional header inside a SectionGroup where sections are created at compile time.
If sections are not created SectionGroup exists and not checkable. How can I disable it if sections are not exist?
Set the name to empty to hide a section and/or group.
!include Sections.nsh
!include LogicLib.nsh
Page Components
Page InstFiles
Function HideEmptyGroup
Exch $3
Push $0
Push $1
Push $2
StrCpy $0 $3
StrCpy $2 ""
ClearErrors
loop:
SectionGetFlags $0 $1
IfErrors done
${If} $1 & ${SF_SECGRPEND}
${If} $2 = 1
SectionSetText $3 ""
${EndIf}
IntOp $2 $2 - 1
${ElseIf} $1 & ${SF_SECGRP}
IntOp $2 $2 + 1
${Else}
Goto done
${EndIf}
IntOp $0 $0 + 1
Goto loop
done:
Pop $2
Pop $1
Pop $0
Pop $3
FunctionEnd
SectionGroup /e "My Group" SID_MYGROUP
!if 0 ; Set to 1 to show section and group
Section "Thing"
SectionEnd
!endif
SectionGroupEnd
Function .onInit ; Note: This must come after the sections!
Push ${SID_MYGROUP}
Call HideEmptyGroup
FunctionEnd

How to save log during uninstallation using NSIS

I'm trying to log the uninstallation process, especially the files and folders deleted by rmdir for debug. However, LogSet On and DumpLog function seems not working. Does anyone knows if there is a way to do it?
Edit:
I'm using DumpLog like:
Section "Uninstall"
;ADD YOUR OWN FILES HERE...
LogSet On
DetailPrint "this is an uninstall test"
Delete "$INSTDIR\Uninstall.exe"
DeleteRegKey /ifempty HKCU "Software\Test"
DetailPrint "dumplog section"
StrCpy $0 "$INSTDIR\install.log"
Push $0
Call un.DumpLog
SectionEnd
Function un.DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 exit
FileOpen $5 $5 a
StrCmp $5 "" exit
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Alloc ${NSIS_MAX_STRLEN}
Pop $3
StrCpy $2 0
System::Call "*(i, i, i, i, i, i, i, i, i) i \
(0, 0, 0, 0, 0, r3, ${NSIS_MAX_STRLEN}) .r1"
loop: StrCmp $2 $6 done
System::Call "User32::SendMessageA(i, i, i, i) i \
($0, ${LVM_GETITEMTEXT}, $2, r1)"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWrite $5 "$4$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
exit:
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
More Edit:
Things are getting more complex.
I put the structure like:
-InstDir
-UnInstDir
-Uninstaller.exe
When I run uninstaller or use execwait $InstDir\UnInstDir\Uninstall.exe, LogSet On works and a new install.log was created in UnInstDir.
However, when I use execwait $InstDir\UnInstDir\Uninstall.exe _?=$InstDir, LogSet On doesn't work.
Anyone has any clue on it?
LogSet requires that you use the logging build of NSIS. DumpLog only writes the same info as you see on the InstFiles page.
Another alternative is to track the files as you install them.
The final option is to use !system to execute a script (batch file etc.) that generates two .nsh files (one for the installer, one for the uninstaller) with File and Delete instructions based on the output of dir /s.

How do I redirect the output of commands to file in silent mode in NSIS?

In GUI mode, commands like CopyFiles , Delete , etc. output their data to GUI (may be using DetailPrint) and their is a function available on NSIS forums to copy that data (at the end of section) to a file.
Queries:
If the installer is being run in silent mode, how do I get the same data (which was being directed to GUI in non-silent mode) to the file?
In GUI mode, since I am directing custom logs through DetailPrint to the log file with the help of function so that all the logs are received in order. Here the issue is that line breaks are removed from the custom logs. May be DetailPrint removes it. How shall I avoid this?
Example:
DetailPrint "This is a custom log1"
DetailPrint "$\r$\nThis is a custom log2"
/*
Dumped these logs using function mentioned above
Output in logs(with no line breaks):
This is a custom log1
This is a custom log2
Required output:
This is a custom log1
This is a custom log2
*/
Your Second query is Solved.
!include "MUI2.nsh"
section
StrCpy $0 "$EXEDIR\install.log"
Push $0
DetailPrint "This is a custom log1"
DetailPrint "This is a custom log2"
Call DumpLog
sectionend
;!define LVM_GETITEMCOUNT 0x1004
!define LVM_GETITEMTEXT 0x102D
Function DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 exit
FileOpen $5 $5 "w"
StrCmp $5 "" exit
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Alloc ${NSIS_MAX_STRLEN}
Pop $3
StrCpy $2 0
System::Call "*(i, i, i, i, i, i, i, i, i) i \
(0, 0, 0, 0, 0, r3, ${NSIS_MAX_STRLEN}) .r1"
loop: StrCmp $2 $6 done
System::Call "User32::SendMessageA(i, i, i, i) i \
($0, ${LVM_GETITEMTEXT}, $2, r1)"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWrite $5 "$4$\r$\n"
FileWrite $5 "$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
exit:
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
Your sample code is works for me. Its gives me output as your requirement.
Dont put "$\r$\n" in Detailprint. Add FileWrite $5 "$\r$\n" in DumpLog function as i did. By this you will not have to put $\r$\n in each detail print.

NSIS: How to validate IP address box

I have added a ${NSD_CreateIPaddress} box in my nsi script, but while validating each field of IP is come up with by default to 0. I am getting the IP address value by using ${GetText}.
Is there any way to remove default value 0 and validate each field of IP address. Please help me out.
I'm not really sure what you are asking about. If you want to tell the difference between a blank address and the valid "0.0.0.0" address you can use the IPM_ISBLANK message. There should be no need to validate the individual fields because they are already limited to 0..255 and you can even set a custom range with IPM_SETRANGE.
If you still feel the need to look at the individual fields you can
A) Manually parse the string you get from ${NSD_GetText} yourself.
or
B) Unpack the 32-bit address you get from IPM_GETADDRESS:
Page Custom myPage myPageLeave
Page InstFiles
!include nsDialogs.nsh
; Using custom version of http://nsis.sourceforge.net/NsDialogs_CreateIPaddress
!ifndef NSD_CreateIPaddress
!define NSD_CreateIPaddress "!insertmacro NSD_CreateIPaddress "
!include LogicLib.nsh
!include WinMessages.nsh
!ifndef ICC_INTERNET_CLASSES
!define ICC_INTERNET_CLASSES 0x00000800
!endif
Function CCInitIP
!insertmacro _LOGICLIB_TEMP
System::Call '*(i8,i${ICC_INTERNET_CLASSES})p.s' ; NSIS 2.50+
System::Call 'COMCTL32::InitCommonControlsEx(pss)'
Pop $_LOGICLIB_TEMP
System::Free $_LOGICLIB_TEMP
FunctionEnd
!macro NSD_CreateIPaddress x y w h t
!insertmacro _LOGICLIB_TEMP
Call CCInitIP
nsDialogs::CreateControl "SysIPAddress32" ${DEFAULT_STYLES}|${WS_TABSTOP} 0 ${x} ${y} ${w} ${h} "${t}"
Exch $0
CreateFont $_LOGICLIB_TEMP "$(^Font)" "$(^FontSize)"
SendMessage $0 ${WM_SETFONT} $_LOGICLIB_TEMP 1
Exch $0
!macroend
!define /math IPM_GETADDRESS ${WM_USER} + 102
!define /math IPM_ISBLANK ${WM_USER} + 105
!endif
Function OnIPNotify ; This function displays some information about the IP
Pop $0 ; Not used
${NSD_GetText} $1 $3
StrCpy $4 "NSD_GetText: $3"
SendMessage $1 ${IPM_ISBLANK} 0 0 $0
StrCpy $4 "$4$\nIPM_ISBLANK: $0"
System::Call 'USER32::SendMessage(pr1, i ${IPM_GETADDRESS}, p 0, *i0 r3)p.r0' ; NSIS 2.50+
IntFmt $5 "0x%.8x" $3
StrCpy $4 "$4$\nIPM_GETADDRESS: ValidFields=$0 PackedIP=$5"
IntOp $0 $3 >> 24
IntOp $0 $0 & 0xff
StrCpy $4 "$4$\n$\t Field1=$0"
IntOp $0 $3 >> 16
IntOp $0 $0 & 0xff
StrCpy $4 "$4$\n$\t Field2=$0"
IntOp $0 $3 >> 8
IntOp $0 $0 & 0xff
StrCpy $4 "$4$\n$\t Field3=$0"
IntOp $0 $3 & 0xff
StrCpy $4 "$4$\n$\t Field4=$0"
${NSD_SetText} $2 $4
FunctionEnd
Function myPage
nsDialogs::Create 1018
Pop $0
${NSD_CreateIPaddress} 1% 0 50% 12u ""
Pop $1
${NSD_CreateLabel} 1% 20u 98% -20u "Enter an IP address to see information here..."
Pop $2
${NSD_OnNotify} $1 OnIPNotify ; Display some information when the control is changed
nsDialogs::Show
FunctionEnd
Function myPageLeave
Push 0
Call OnIPNotify
MessageBox mb_ok $4
FunctionEnd

NSIS - Reading multiple command line arguments

When passing a single argument to my windows installer,
MyApplication.exe CLIENT="Your Mom"
I can correctly read it using GetParameters and GetOptions, like so:
${GetParameters} $0
${GetOptions} "$0" "CLIENT=" $CLIENT
But as soon as I attempt to pass multiple parameters
MyApplication.exe CLIENT="Your Mom" NAME="Jill" LOCATION="Da Yard" TEL="0221456789"
and try to read it like so
${GetParameters} $0
${GetOptions} "$0" "CLIENT=" $CLIENT
${GetOptions} "$0" "NAME=" $NAME
${GetOptions} "$0" "LOCATION=" $LOCATION
${GetOptions} "$0" "TEL=" $TEL
The values of $CLIENT, $NAME and $TEL would be correct, but $LOCATION will contain
Da Yard TEL=0221456789
The same happens if I add more parameters, the first two and last one is always correct, but the parameters in between always contains some substring of the string passed to the installer.
Am I using GetOptions correctly?
If you change all the parameters so they start with / then it works for some reason! (MyApplication.exe /CLIENT="Your Mom" /NAME="Jill" /LOCATION="Da Yard" /TEL="0221456789" and ${GetOptions} "$0" "/LOCATION=" $LOCATION)
If you cannot live with the / switch prefix then you would have to rewrite ${GetOptions} yourself or wait until the next NSIS release where it will hopefully be fixed.
If you want to take a stab at fixing it yourself then it would look something like this:
!include FileFunc.nsh
!macroundef GetOptionsBody
!macro GetOptionsBody _FILEFUNC_S
Exch $1 ; Prefix
Exch
Exch $0 ; String
Exch
ClearErrors
; Parse $0 here and look for $1 and store the suffix in $0...
FileFunc_GetOptions${_FILEFUNC_S}_notfound:
SetErrors
StrCpy $0 ''
FileFunc_GetOptions${_FILEFUNC_S}_end:
Pop $1
Exch $0
!macroend
Edit:
It seems like this might be by design, the help file contains this little broken English tidbit:
First option symbol it is delimiter
Edit2:
I made a new GetOptions from scratch, it only supports double quotes and is probably different in all sorts of ways. I did not test much but it seems to work OK:
Outfile "Test.exe"
RequestExecutionLevel user
ShowInstDetails show
!include FileFunc.nsh
!if '${NSIS_PACKEDVERSION}' <= 0x0300003f ; Older versions don't support !macroundef
!define GetOptionsBody_Alt GetOptionsBody_Alt
!undef GetOptions
!define GetOptions `!insertmacro GetOptionsCall_Alt`
!macro GetOptionsCall_Alt _PARAMETERS _OPTION _RESULT
!verbose push
!verbose ${_FILEFUNC_VERBOSE}
Push `${_PARAMETERS}`
Push `${_OPTION}`
${CallArtificialFunction} GetOptions_Alt_
Pop ${_RESULT}
!verbose pop
!macroend
!macro GetOptions_Alt_
!verbose push
!verbose ${_FILEFUNC_VERBOSE}
!insertmacro ${GetOptionsBody_Alt} ''
!verbose pop
!macroend
!else
!define GetOptionsBody_Alt GetOptionsBody
!macroundef ${GetOptionsBody_Alt}
!endif
!macro ${GetOptionsBody_Alt} _FILEFUNC_S ; This alternative version only knows about " quotes and assumes there is nothing or a space/tab before the prefix
Exch $1 ; Prefix
Exch
Exch $0 ; String
Exch
Push $2 ; The quote type we are in if any (Currently only supports ")
Push $3 ; Position in $0
Push $4 ; Temp
Push $5 ; Temp
Push $6 ; Start of data
ClearErrors
StrCpy $2 ''
StrCpy $3 "-1"
StrCpy $6 "-1"
FileFunc_GetOptions${_FILEFUNC_S}_loop:
StrCpy $5 $0 1 $3
IntOp $3 $3 + 1
StrCpy $4 $0 1 $3
StrCmp $4 "" FileFunc_GetOptions${_FILEFUNC_S}_eos
StrCmp $4 '"' FileFunc_GetOptions${_FILEFUNC_S}_foundquote
StrCmp $2 '' 0 FileFunc_GetOptions${_FILEFUNC_S}_loop ; We are inside a quote, just keep looking for the end of it
StrCmp -1 $6 0 FileFunc_GetOptions${_FILEFUNC_S}_dataisunquoted ; Have we already found the prefix and start of data?
IntCmpU $3 0 +2 ; $3 starts as -1 so $5 might contain the last character so we force it to a space
StrCmp $5 '$\t' 0 +2
StrCpy $5 " "
StrCmp $5 " " 0 FileFunc_GetOptions${_FILEFUNC_S}_loop ; The prefix must be at the start of the string or be prefixed by space or tab
StrLen $4 $1
StrCpy $5 $0 $4 $3
StrCmp${_FILEFUNC_S} "$5" "$1" "" FileFunc_GetOptions${_FILEFUNC_S}_loop
IntOp $6 $4 + $3 ; Data starts here
IntOp $3 $6 - 1 ; This is just to ignore the + 1 at the top of the loop
Goto FileFunc_GetOptions${_FILEFUNC_S}_loop
FileFunc_GetOptions${_FILEFUNC_S}_dataisunquoted:
StrCmp $4 ' ' FileFunc_GetOptions${_FILEFUNC_S}_extractdata
StrCmp $4 '$\t' FileFunc_GetOptions${_FILEFUNC_S}_extractdata FileFunc_GetOptions${_FILEFUNC_S}_loop
FileFunc_GetOptions${_FILEFUNC_S}_extractdata:
IntOp $5 $3 - $6
StrCpy $0 $0 $5 $6
Goto FileFunc_GetOptions${_FILEFUNC_S}_return
FileFunc_GetOptions${_FILEFUNC_S}_foundquote:
StrCmp $2 $4 FileFunc_GetOptions${_FILEFUNC_S}_endquote
StrCpy $2 $4 ; Starting a quoted part
Goto FileFunc_GetOptions${_FILEFUNC_S}_loop
FileFunc_GetOptions${_FILEFUNC_S}_endquote:
StrCpy $2 ''
StrCmp -1 $6 FileFunc_GetOptions${_FILEFUNC_S}_loop FileFunc_GetOptions${_FILEFUNC_S}_extractquoteddata
FileFunc_GetOptions${_FILEFUNC_S}_eos: ; End Of String
StrCmp $2 '' +2
FileFunc_GetOptions${_FILEFUNC_S}_extractquoteddata:
IntOp $6 $6 + 1 ; Skip starting quote when extracting the data
StrCmp -1 $6 0 FileFunc_GetOptions${_FILEFUNC_S}_extractdata
SetErrors
StrCpy $0 ''
FileFunc_GetOptions${_FILEFUNC_S}_return:
Pop $6
Pop $5
Pop $4
Pop $3
Pop $2
Pop $1
Exch $0
!macroend
Var CLIENT
Var NAME
Var LOCATION
Var TEL
Function Test
${GetParameters} $0
DetailPrint "Calling GetOptions on |$0|"
${GetOptions} $0 "/CLIENT=" $CLIENT
${GetOptions} $0 "/NAME=" $NAME
${GetOptions} $0 "/LOCATION=" $LOCATION
${GetOptions} $0 "/TEL=" $TEL
DetailPrint "/CLIENT=|$CLIENT|"
DetailPrint "/NAME=|$NAME|"
DetailPrint "/LOCATION=|$LOCATION|"
DetailPrint "/TEL=|$TEL|"
${GetOptions} $0 "CLIENT=" $CLIENT
${GetOptions} $0 "NAME=" $NAME
${GetOptions} $0 "LOCATION=" $LOCATION
${GetOptions} $0 "TEL=" $TEL
DetailPrint "CLIENT=|$CLIENT|"
DetailPrint "NAME=|$NAME|"
DetailPrint "LOCATION=|$LOCATION|"
DetailPrint "TEL=|$TEL|"
FunctionEnd
Section
; Normally I would detect this automation with a command line parameter but
; because we are debugging those I'm using this hack instead
ExpandEnvStrings $0 "%NSIS_Test_GetOptions%"
StrCmp $0 "1337" 0 launchselfwithparams
Call Test
Goto done
launchselfwithparams:
System::Call 'KERNEL32::SetEnvironmentVariable(t "NSIS_Test_GetOptions", t "1337")'
DetailPrint "NSIS ${NSIS_VERSION}"
ExecWait '"$ExePath" /CLIENT="Your Mom"'
ExecWait '"$ExePath" /CLIENT="Your Mom" /NAME="Jill" /LOCATION="Da Yard" /TEL="0221456789"'
ExecWait '"$ExePath" CLIENT="Your Mom"'
ExecWait '"$ExePath" CLIENT="Your Mom" NAME="Jill" LOCATION="Da Yard" TEL="0221456789"'
${GetOptions} 'foo=bar bar=baz baz=biz' 'bar=' $R0
DetailPrint |$R0|
${GetOptions} 'foo=bar bar=baz baz=biz' 'failthis=' $R0
DetailPrint |$R0|
${GetOptions} '/SILENT=yes/INSTDIR=bug /INSTDIR="C:/Program Files/Common Files" /ADMIN=password' "/INSTDIR=" $R0
DetailPrint |$R0|
${GetOptions} 'SILENT=yesINSTDIR=bug INSTDIR="C:/Program Files/Common Files" ADMIN=password' "INSTDIR=" $R0
DetailPrint |$R0|
${GetOptions} '/SILENT="yes/INSTDIR=bug" /INSTDIR="C:/Program Files/Common Files" /ADMIN="password"' "/INSTDIR=" $R0
DetailPrint |$R0|
${GetOptions} 'SILENT="yesINSTDIR=bug" INSTDIR="C:/Program Files/Common Files" ADMIN="password"' "INSTDIR=" $R0
DetailPrint |$R0|
done:
SectionEnd

Resources