NSIS !finalize catch error - nsis

I'm trying to catch an error if !finalize doesn't successfully sign the OUTFILE.
In my research, I found that it is possible by reading this NSIS closed/fixed SourceForge bug #1148 that I could check for a return error code; basically anything other than 0 for an unsuccessful return.
So what I'm actually trying to accomplish is I want to be able to check if !finalize was unsuccessful; which in this particular case will most likely be because the user doesn't have access to the internet because I'm having it use a timestamp service first and if not than sign the OUTFILE without using a timestamp.
Here's my current logic using the !finalize instruction:
!define TimestampSHA1
!define TimestampSHA256
!define CERT `Contrib\crt\${DEVELOPER}.${CERT_EXT}`
!define SIGN `Contrib\bin\signtool.exe`
!define CMD `"${SIGN}" sign /f "${CERT}" /p ""`
!define SHA1 `${CMD} /t "${TimestampSHA1}" /v "${PACKAGE}\${OUTFILE}"`
!define SHA256 `${CMD} /fd sha256 /tr "${TimestampSHA256}" /td sha256 /as /v "${PACKAGE}\${OUTFILE}"`
#
# There's a lot more Timestamp services for this next
# !if/!else conditional check but I kept it short
# and sweet as I'm only sharing what's necessary.
#
!if "${TIMESTAMP_SVC}" == "Comodo"
!define /REDEF TimestampSHA1 "http://timestamp.comodoca.com"
!define /REDEF TimestampSHA256 "http://timestamp.comodoca.com/?td=sha256"
!else if "${TIMESTAMP_SVC}" == "Verisign"
!define /REDEF TimestampSHA1 "http://timestamp.verisign.com/scripts/timstamp.dll"
!define /REDEF TimestampSHA256 "http://sha256timestamp.ws.symantec.com/sha256/timestamp"
!endif
#
# Sign only if we can locate the certificate..
#
!if /FileExists "${CERT}"
!ifdef TIMESTAMP_SVC
#
# Sign using dual signature hashing
# algorithm standards (SHA256 and SHA1)
# along with a timestamping service.
#
!finalize `${SHA1}`
!finalize `${SHA256}`
#
# TODO: Figure out how to catch an error or
# an unsuccessful signing in order to sign
# OUTFILE without using a timestamp.
#
# The following is just me theoretically
# coding as I cannot figure out how to check
# the return of 'signtool.exe'
#
!if ! "${ERRORLEVEL}" == 0
!finalize `${CMD} /v "%1"`
!finalize `${CMD} /fd sha256 /td sha256 /as /v "%1"`
!endif
!else
#
# If we're here than the end-user didn't
# declare a timestamping service so we'll
# sign it without giving it a timestamp.
#
!finalize `${CMD} /v "%1"`
!finalize `${CMD} /fd sha256 /td sha256 /as /v "%1"`
!endif
!else # Cannot find the certificate..
!warning "Cannot find a certificate to code sign with. Please \
check the spelling and/or the path to your certificate \
are correct and recompile to sign ${OUTFILE}!"
!endif
I'm well aware about the documentation on !finalize which shows an example using a .bat script but I'd like to avoid having to do that as I've already got the logic in place using NSIS.
Can someone give me a helping hand please?

!finalize happens after the executable generation is done. So that !if ! "${ERRORLEVEL}" == 0 will actually be evaluated before your command is even executed. You can have the entire script fail with:
!finalize `${CMD} /v "%1"` = 0
!finalize `${CMD} /fd sha256 /td sha256 /as /v "%1"` = 0
If you want to also try to resign without a timestamp, it might be easier to just create a batch file that handles all this logic and call that file with !finalize.

Related

Generate installation logs

I am using special logging build to generate installation logs.
I have observed the logs are not generating when called another installer from installer script.
For ex -
ExecWait '"$INSTDIR\installer1.exe" /S _?=$INSTDIR'
The log is generating for main installer but not for installer1.exe
The installer1.exe contains lots of components and I need to print the logs for the same. I have tried enabling logset on in the installer1 script but no luck.
Tried using dumplog but it doesn't work with silent installation.
Any help would be appreciated!
Sample code from Main Installer script --
InstallDir "C:\MyFolder"
Name "${PRODUCT_NAME_VERSION}"
OutFile "${OUT_FILE}"
Section "Test"
SetOutPath $INSTDIR
LogSet on
ExecWait '"$EXEDIR\Packages\installer1.exe" /S /INST=$INSTDIR' $0
SectionEnd
Sample code from sub-installer script ---
InstallDir "C:\MyFolder"
Section "-Demo"
SetOutPath $INSTDIR
LogSet on
LogText "Print something"
SetOutPath $INSTDIR\ExternalFolder\Demo
File /nonfatal /a /r $INSTDIR\ExternalFolder\Demo\Test
ExecWait '"$INSTDIR\ExternalFolder\Demo\Test\TestSetup.exe" /silent '
SectionEnd
The sub-installer (installer1.exe) is pre-compiled and kept the exe in $EXEDIR\Packages\installer1.exe The patch is valid.
_?= is special syntax that is only supported by NSIS uninstallers, installers use /D=.
ExecWait '"$InstDir\installer.exe" /S /D=$InstDir'
Logging of course has to be enabled in this sub-installer as well.
/D= overrides the InstallDir attributes, forcing $InstDir to the specified path before .onInit is executed.
InstallDir $INSTDIR does not make sense, use something like InstallDir "$ProgramFiles\MyApp"

How do I execute the app right after installation ... with arguments?

My installation EXE should unzip itself in a temp folder, then execute an app therein (with passing args), return and delete the temp folder again.
How do I write the nsi script?
It sounds to me like you are trying to create a portable application. Portable applications are always better when the original author adds support for it because it can handle registry and other configuration files correctly.
If you still want to create a launcher application you can do something like this:
OutFile "MyLauncher.exe"
RequestExecutionLevel User
SilentInstall Silent
SetCompressor LZMA
!include FileFunc.nsh
!insertmacro GetParameters
Section
${GetParameters} $1
InitPluginsDir
SetOutPath $PluginsDir
File "c:\myfiles\MyApp.exe"
File /r "c:\myfiles\otherfiles\*.*" ; If you need to include other files required by the application
ExecWait '"$PluginsDir\MyApp.exe" /param1 "pa ra m2" /param3 $1' $0 ; $1 contains the parameters passed to your launcher, remove it if you don't want to pass those arguments
SetErrorLevel $0
SetOutPath $Temp ; Don't lock $PluginsDir so it can be deleted automatically by the installer
SectionEnd
Answering my own question.
The required nsi script skeleton should look like this:
# The name of the installer (arbitrary)
Name "hello"
# The name of the installation file
OutFile "hello.exe"
# where put the installation - other options would be $TEMP, etc.
InstallDir $DESKTOP
RequestExecutionLevel user # no Windows UAC popup please!
SilentInstall silent # completely silent install
SetCompressor /SOLID /FINAL lzma # max compression for inst. file
# The stuff to install
Section ""
SetOutPath $INSTDIR # where to install (overwritable by user!)
File /r D:\...\... # where the install material lives
SectionEnd
# this function auto-runs after installation is fine
Function .onInstSuccess
# parameter are passed through via $CMDLINE
ExecWait '"$OUTDIR\hello.dist\hello.exe" $CMDLINE'
RMDir /r "$OUTDIR\hello.dist" # remove install folder again
FunctionEnd

Copying files from a folder with a pattern

As part of my installer I'm extracting a JRE which is in a folder that varies name (i.e. jre1.8.0_74). I'm trying to do that with a pattern, but it can't find it. I tried something like this:
SetOutPath "$INSTDIR\${APPDIR}\jre"
${If} ${RunningX64}
File /nonfatal /a /r "${SrcDir}\jre\jre_64\jre*\*.*"
${Else}
File /nonfatal /a /r "${SrcDir}\jre\jre_32\jre*\*.*"
${EndIf}
But found no files. I basically want to skip this "unknown" folder name to make it easier starting my application, etc.
I know it's a NSIS newbie basic mistake somewhere :P
Any idea?
When something is not known at compile-time you must use !system to execute a batch file or something else that is able to create a text file with the desired NSIS instruction that you later can !include.
For filesystem wildcards you can probably get away with just using dir or for:
!macro InvokeNSISCommandOnWildcardFolder parentdir wildcard commandprefix commandsuffix
!tempfile _InvokeNSISCommandOnWildcardFolder
!system 'for /D %A in ("${parentdir}\${wildcard}") do #(^>^> "${_InvokeNSISCommandOnWildcardFolder}" echo ${commandprefix}${parentdir}\%~nxA${commandsuffix})'
!include "${_InvokeNSISCommandOnWildcardFolder}"
!delfile "${_InvokeNSISCommandOnWildcardFolder}"
!undef"${_InvokeNSISCommandOnWildcardFolder}"
!macroend
Section
SetOutPath "$INSTDIR\${APPDIR}\jre"
!insertmacro InvokeNSISCommandOnWildcardFolder "${SrcDir}\jre\jre_32" "jre*" 'File /nonfatal /a /r "' '\*.*"'
SectionEnd
Because my example here just uses !system to invoke "DOS" commands directly there are some restrictions on the strings passed to this macro and <, >, (, and ) might have to be escaped.

Nsis doesn't see environment variables

Here is the part os nsis script (.nsi):
!ifndef QTDIR
!error "Please define QT installation directory via /DQTDIR=C:\qt\4.8.4"
!endif
But after executing this command:
set QTDIR=C:\path\to\qt
the erorr still occurs. The same result on two computers, both windows 7. Nsis version is 2.46 .
!ifdef and !ifndef operate on defines internally in the compiler process. You can set one in your script with !define or use the -D MakeNSIS command line argument.
MakeNSIS can also read Windows environment variables: !echo "The value of QTDIR is $%QTDIR%".
You can also support both:
!ifndef QTDIR
!define QTDIR "$%QTDIR%"
!endif
!if ! /fileexists "${QTDIR}"
!error "QTDIR not valid"
!endif

Use Section Index Before Section Definition or Install Last Section First in NSIS

I'm writing an NSIS installer script to install some network printers. I want each printer to be installed as an optional Section, but I don't want to restart the print spooler more than once. So, I created a hidden section that checks if each printer section is selected, and if it is then it includes the registry settings to create the port for that printer. Then it restarts the print spooler. The problem is I need this hidden section to be done first, but I also need to use the section indexes of the printer sections.
Does anyone know how I can reference the section indexes before the sections are defined? Another approach I thought of was to move the hidden section to the end, but then I would need a way to make sure it's installed first. Any help or ideas would be appreciated.
Section "-"
${IfThen} ${SectionIsSelected} ${Sec01} ${|} !include "10.0.0.8.nsh" ${|}
${IfThen} ${SectionIsSelected} ${Sec02} ${|} !include "10.0.0.11.nsh" ${|}
nsExec::Exec 'net stop spooler'
nsExec::Exec 'net start spooler'
SectionEnd
Section "My Printer" Sec01
ExecWait 'rundll32 printui.dll,PrintUIEntry /if /b "My Printer" /f "$EXEDIR\oj8000\hpoj800z.inf" /r "10.0.0.8" /m "HP Officejet Pro 8000 A809 Series" /z'
SectionEnd
Section "Copier" Sec02
ExecWait 'rundll32 printui.dll,PrintUIEntry /if /b "Copier" /f "$EXEDIR\copier\oemsetup.inf" /r "10.0.0.11" /m "RICOH Aficio MP C4000 PCL 6" /z'
SectionEnd
Section -
...
call doSectionChecks
SectionEnd
Section "My Printer" Sec01
...
SectionEnd
Function doSectionChecks
... ${Sec01}
FunctionEnd

Resources