NSIS script not installing in correct directory - nsis

I am trying to make an install script:
on 32bits pc: tapi_32bits.tsp in C:\windows\system32
on 64bits pc : tapi_64bits.tsp in C:\Windows\System32 and tapi_32bits.tsp in C:\Windows\SysWOW64
This is the script I wrote:
; The name of the installer
Name "TAPI Installer"
; The file to write
OutFile "TAPI Installer"
; The default installation directory
InstallDir $DESKTOP
;--------------------------------
; Install to the correct directory on 32 bit or 64 bit machines
Section
IfFileExists $WINDIR\SYSWOW64\*.* Is64bit Is32bit
Is32bit:
; Set output path to the installation directory.
SetOutPath $SYSDIR
; Put file there
File tapi_32bits.tsp
; SectionEnd MessageBox MB_OK "32 bit"
SetRegView 32
StrCpy $INSTDIR "$PROGRAMFILES32\LANDesk\Health Check"
GOTO End32Bitvs64BitCheck
Is64bit:
; install in C:\Windows\System32
SetOutPath $WINDIR\System32\
; file to put there
File tapi_64bits.tsp
; install in C:\Windows\SysWOW64
SetOutPath $WINDIR\SysWOW64
; file to put there
File tapi_32bits.tsp
;SectionEnd MessageBox MB_OK "32 bit"
SetRegView 32
StrCpy $INSTDIR "$PROGRAMFILES32\LANDesk\Health Check"
GOTO End32Bitvs64BitCheck
MessageBox MB_OK "64 bit"
SetRegView 64
StrCpy $INSTDIR "$PROGRAMFILES64\LANDesk\Health Check"
End32Bitvs64BitCheck:
SectionEnd
;--------------------------------
But on 64bits pc it places both files (tapi_64bits.tsp and tapi_32bits.tsp) in the Syswow64 folder. The installer does say that it is installed in the correct folder but both files are in the Syswow64 folder. What am I doing wrong?

NSIS is a 32 bit application so it is affected by file redirection.
You must use x64.nsh, it has code to detect WOW64 and disable redirection (Turn it back on as soon as possible). The other alternative is to extract to $windir\sysnative but that is more of a hack and does not work on XP 64.

The following code should work.
!include x64.nsh
; Install to the correct directory on 32 bit or 64 bit machines
Section
${If} ${RunningX64}
; install in C:\Windows\System32
SetOutPath $WINDIR\System32\
; file to put there
File tapi_64bits.tsp
; install in C:\Windows\SysWOW64
SetOutPath $WINDIR\SysWOW64
; file to put there
File tapi_32bits.tsp
${Else}
; Set output path to the installation directory.
SetOutPath $SYSDIR
; Put file there
File tapi_32bits.tsp
${EndIf}
SectionEnd

Related

Signing NSIS Uninstaller from Linux or Mac

I am porting our NSI installer to Linux and Mac instead of Windows to better integrate with our Maven build system.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed.
Obviously this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
Does anyone has a better solution. I'm no expert at NSIS and wondering if there is a clever way to get the uninstall.exe so that it can be signed?
I don't think there is a real solution to this.
The installer and uninstaller uses the same exe code and only checks a flag (FH_FLAGS_UNINSTALL in firstheader) on startup to see if it is a uninstaller. Just flipping this bit is not enough though, the program would fail the CRC check and even if you bypass that the uninstaller data is compressed so you would have to decompress that to the correct location in the file. To actually accomplish this you would have to write a custom tool. You can see this operation in the NSIS source in exec.c if you search for EW_WRITEUNINSTALLER.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed. [...] this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
If you exploit a stub installer for uninstall operations (no payload), this appears to be possible.
It will spawn an uninstall.exe process from the $TEMP folder, which is then capable of deleting $INSTDIR.
This script will create a stub (un)installer which can then be Authenticode Signed. It will compile on Windows, MacOS and Linux.
Caveats:
You'll have to manually bundle this into the installer (trivial)
You'll have to manage your own uninstall registry entries (trivial)
The look and feel may not match NSIS's default for uninstallers
You'll see the installer open twice (first from $INSTDIR, second from $TEMP). This is a child process which allows uninstall.exe to delete itself, similar to how NSIS does it in the Section "Uninstall".
You'll need a secondary .nsi script dedicated to uninstall operations, cumbersome if you have a lot of shared logic between your install/uninstall sections.
Worse, you'll have to AVOID the "Uninstall" section title, as you'll be placed into the same problem as the OP when that bytecode is generated.
When explicitly running from $TEMP some relative file logic will be incorrect. The example passes these back in as a $DELETE_DIR, $DELETE_EXE respectively.
The code:
!include MUI2.nsh
!include x64.nsh
!include LogicLib.nsh
!include FileFunc.nsh
!include WinMessages.nsh
!define MUI_PRODUCT "My App"
!define MUI_VERSION "1.0.0"
; Masquerade the title
!define MUI_PAGE_HEADER_TEXT "Uninstall My App"
!define MUI_PAGE_HEADER_SUBTEXT "Remove My App from your computer"
!define MUI_INSTFILESPAGE_FINISHHEADER_TEXT "Uninstallation Complete"
!define MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT "Uninstall was completed successfully."
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!insertmacro GetParameters
RequestExecutionLevel admin
CRCCheck On
OutFile "uninstall.exe"
Name "Uninstall"
Var /GLOBAL RESPAWN
Var /GLOBAL DELETE_DIR
Var /GLOBAL DELETE_EXE
Section
; Masquerade as uninstall
SendMessage $HWNDPARENT ${WM_SETTEXT} 0 "STR:Uninstall"
${GetParameters} $0
${GetOptions} "$0" "/RESPAWN=" $RESPAWN
${GetOptions} "$0" "/DELETE_DIR=" $DELETE_DIR
${GetOptions} "$0" "/DELETE_EXE=" $DELETE_EXE
${If} $RESPAWN != ""
; We're running from $TEMP; Perform the uninstall
!define yay "We're running from $EXEPATH, yay, we can remove the install directory!$\n$\n"
!define myvars "$\tRESPAWN$\t$RESPAWN$\n$\tDELETE_EXE$\t$DELETE_EXE$\n$\tDELETE_DIR$\t$DELETE_DIR"
MessageBox MB_OK "${yay}${myvars}"
; Your uninstall code goes here
; RMDir /r $DELETE_DIR\*.*
; Delete "$DESKTOP\${MUI_PRODUCT}.lnk"
; Delete "$SMPROGRAMS\${MUI_PRODUCT}\*.*"
; RmDir "$SMPROGRAMS\${MUI_PRODUCT}"
; Delete Uninstaller And Unistall Registry Entries
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${MUI_PRODUCT}"
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}"
; Remove the old version of ourself
ClearErrors
Delete $DELETE_EXE
IfErrors 0 +3
MessageBox MB_OK "File could NOT be deleted: $DELETE_EXE"
Goto +2
MessageBox MB_OK "File was successfully deleted: $DELETE_EXE"
; Remove ourself from $TEMP after reboot
Delete /REBOOTOK $EXEPATH
; ${If} ${RunningX64}
; ${EnableX64FSRedirection}
; ${EndIf}
SetDetailsPrint textonly
DetailPrint "Completed"
${Else}
; We're NOT running from $TEMP, copy to temp and respawn ourself
GetTempFileName $0
CopyFiles "$EXEPATH" "$0"
Exec '"$0" /RESPAWN=1 /DELETE_DIR="$EXEDIR" /DELETE_EXE="$EXEPATH"'
Quit
${EndIf}
SectionEnd
Function .onInit
; ${If} ${RunningX64}
; SetRegView 64
; ${DisableX64FSRedirection}
; ${EndIf}
FunctionEnd

Can i detect 32 or 64 bit OS without plugin somehow?

now i use the x64.nsh for this, but i can detect it without this plugin?
${If} ${RunningX64}
MessageBox MB_OK "running on 64 bit"
File /r ${64BIT_OPENVPN_INSTALL}
Execwait ${64BIT_OPENVPN_INSTALL}
${Else}
MessageBox MB_OK "running on 32 bit"
File /r ${32BIT_OPENVPN_INSTALL}
Execwait ${32BIT_OPENVPN_INSTALL}
${EndIf}
x64.nsh does not implies specific external plugin usage (apart the system plugin): it is just an included file that defines 3 macros based on kernel calls (i.e kernel32::GetCurrentProcess() and kernel32::IsWow64Process()) through the system plugin, that can be conveniently used with LogicLib.nsh
There are probably many ways to detect the native bitness by just looking at files and registry keys but there is always the risk that some 32-bit systems have somehow ended up with a SysWOW64 folder in %WinDir% etc.
The SetRegView test should be pretty safe but there is a small window where some other app could change the registry at just the wrong time giving you the wrong result.
The correct way to detect this is of course to call the IsWow64Process function and the x64.nsh header already does that for you.
!include LogicLib.nsh
Section
!if "${NSIS_PTR_SIZE}" > 4
DetailPrint "64-bit NSIS, this must be a 64-bit system"
!endif
${If} ${FileExists} "$WinDir\SysWOW64\kernel32.dll"
DetailPrint "Probably not a native 32-bit system"
${EndIf}
${If} ${FileExists} "$WinDir\SysNative\kernel32.dll"
DetailPrint "Probably a 32-bit app on a native 64-bit system (Vista+ only)"
${EndIf}
SetRegView 64
ReadRegStr $6 HKLM "Software\Microsoft\Windows\CurrentVersion" "ProgramFilesDir"
SetRegView lastused
SetRegView 32
ReadRegStr $3 HKLM "Software\Microsoft\Windows\CurrentVersion" "ProgramFilesDir"
SetRegView lastused
${If} $3 != $6
DetailPrint "Probably a 32-bit app on a native 64-bit system"
${EndIf}
; ReadEnvStr on ProgramW6432 or PROCESSOR_ARCHITEW6432 etc
SectionEnd

NSIS uninstaller doesn't delete files/folders

I'm writing a NSIS installer for one of the apps that the company I work for uses internally, the install process works fine, with no problems all the REG keys are created, and so are the files folders and services, that the App uses. For some reason I can't understand, the uninstall process doesn't work's.
The services created by the app are deleted and so are the Registry keys, the most simple part, the files themselves, I can't delete them through the uninstaller!
#Includes
!include "x64.nsh"
#Defines and Installer Properties
Outfile "ESTvnc Installer.exe"
Name ESTvnc
Icon "${NSISDIR}\contrib\graphics\icons\VNCON.ico"
#Detect OS Version
Function .onInit
StrCpy $instdir $PROGRAMFILES
${If} ${RunningX64}
StrCpy $instdir $PROGRAMFILES32
${EndIf}
FunctionEnd
section
SetShellVarContext all
CreateDirectory $instdir\EST\ESTvnc
setOutPath $instdir\EST\ESTvnc
File /r installfiles\*
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ESTvnc\" \
"DisplayName" "ESTvnc"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ESTvnc"\
"UninstallString" "$instdir\EST\ESTvnc\uninstaller.exe"
writeUninstaller $instdir\EST\ESTvnc\uninstaller.exe
ExecWait '"$instdir\EST\estvnc\estvnc.exe" -install'
sectionEnd
section "Uninstall"
SetShellVarContext all
SimpleSC::StopService "ESTVNC" 1 30
pop $0
SimpleSC::StopService "ESTVNCSR" 1 30
pop $0
SimpleSC::RemoveService "ESTVNC"
SimpleSC::RemoveService "ESTVNCSR"
RMDir /r "$instdir\EST\ESTvnc"
Delete $instdir\EST\ESTvnc\uninstaller.exe
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ESTvnc"
sectionEnd
In the uninstaller, $instdir is the directory the uninstaller is in!
Either place the uninstaller in $instdir and delete $instdir\EST\ESTvnc or if you want to keep it in $instdir\EST\ESTvnc, delete $instdir...

Is it possible to conditionally add a file/folder to NSIS installer

Is it possible to conditionally add a file/folder and installation option to a NSIS installer?
My idea is that if the folder Foo exists at a given location it should be added to the installer and the option to install Foo should be added to the installer as well. But if the folder Foo does not exist, the NSIS script should just create the installer but leave Foo and the option to select Foo out of it.
You can try to include a file with /NONFATAL. If it exists, it will be included by the compiler. In runtime, you can check if installer was able to extract it.
File /NONFATAL "file.zip"
${If} ${FileExists} "$OUTDIR\file.zip"
...
${EndIf}
In NSIS 2 File /NONFATAL /R "c:\foo" is the best you can do without external tools and you need a little hack to hide the section when there are no files:
!include LogicLib.nsh
Page Components
Page InstFiles
Section "Main"
SetOutPath $InstDir
# File "C:\myfiles\myapp.exe"
SectionEnd
Section "Install Foo" SID_FOO
SetOutPath $InstDir
File /NONFATAL /r "C:\myfiles\foo\*.*"
SectionEnd
Function .onInit
SectionGetSize ${SID_FOO} $0
StrCmp $0 0 "" +3
SectionSetFlags ${SID_FOO} 0 ; Force all flags off including the checkmark
SectionSetText ${SID_FOO} "" ; Hide the section because its size is 0
FunctionEnd
If this is unacceptable you can use !system and get a little help from cmd.exe to check if something exists:
!tempfile INCEXIST
!system 'if exist "C:\myfiles\foo\*.*" echo !define HAVE_FOO > "${INCEXIST}"'
!include "${INCEXIST}"
!delfile "${INCEXIST}"
!ifdef HAVE_FOO
Section "Install Foo"
SetOutPath $InstDir
File /r "C:\myfiles\foo\*.*"
SectionEnd
!endif
In NSIS 3 !if supports a /FileExists switch:
!if /FileExists "C:\myfiles\foo\*.*"
Section "Install Foo"
SetOutPath $InstDir
File /r "C:\myfiles\foo\*.*"
SectionEnd
!endif
Example to replace file what depends on running service and exists or not at targget location
IfFileExists "$SYSDIR\my_file.dll" exist notexist
exist:
ExecWait 'net stop desired_service'
SetOutPath $SYSDIR
SetOverwrite on
File "/oname=$SYSDIR\my_file.dll" "Path to my file\my_file.dll"
ExecWait 'net start desired_service'
notexist:
.....what you want to do if doesn't exists

Having InstallDir within IF ELSE block

I try to have the following code from
; The default installation directory
InstallDir $PROGRAMFILES\${PRODUCT_NAME}
to
!include x64.nsh
${If} ${RunningX64}
; The default installation directory
InstallDir $PROGRAMFILES\${PRODUCT_NAME}
${Else}
; The default installation directory
InstallDir $PROGRAMFILES64\${PRODUCT_NAME}
${EndIf}
I get the following error :-
!insertmacro: _If
Error: Can't add entry, no section or function is open!
Error in macro _RunningX64 on macroline 2
Error in macro _If on macroline 9
Error in script "C:\Users\yccheok\Desktop\mysoftware.nsi" on line 17 -- aborting creation process
Is there way I can set the value for InstallDir, within if else block?
If you need a dynamic $InstDir you should not use InstallDir at all but set $InstDir in .onInit:
Installdir ""
!include LogicLib.nsh
!include x64.nsh
Function .onInit
${If} $InstDir == "" ; /D= was not used on the command line
${If} ${RunningX64}
StrCpy $InstDir "c:\foo"
${Else}
StrCpy $InstDir "c:\bar"
${EndIf}
${EndIf}
FunctionEnd
Your current if else block does not make any sense because you are selecting the 32 bit program files on x64 and the 64 bit program files on x86! It is OK to use $PROGRAMFILES64 on x86 so if you always want the "real" program files you can use $PROGRAMFILES64 for all platforms...

Resources