Ok, I have a simplified version of my nsi script (see below). In the A2 section, I copy a single executable to the specified installation path, create environment variables, then do a SendMessage which is supposed to make all currently running processes aware of the env change. However, it seems that the NSIS installer process itself doesn't get updated because the shortcut I create in the Links section doesn't work
installer.nsi:
SetCompressor /FINAL zlib
!include LogicLib.nsh
!include WinMessages.nsh
!include x64.nsh
!define ENGINE "TEST"
!define DERIV "A2"
!define LIB_VER "v43"
!define RELEASE "v3dev2"
Name "${ENGINE}${DERIV} DTE ${RELEASE}"
OutFile "${ENGINE}${DERIV}-DTE.exe"
; display the installation directory page
; note that NSIS places the selected directory in $INSTDIR
; DirText ""
Page directory setDefaultInstallDirectory
Function setDefaultInstallDirectory
;check for an existing sim root and set it as
the default installation directory if it exists
ReadEnvStr $1 SIM_ROOT
${If} $1 != ""
StrCpy $INSTDIR $1
${EndIf}
FunctionEnd
; display the installation page and show a message
; when the installation is complete
Page instfiles "" "" finished
Function finished
MessageBox MB_OK "Installation Complete."
FunctionEnd
section "A2"
SetOutPath $INSTDIR\${ENGINE}\${DERIV}
File alt_control.exe
; Environment Variables
WriteRegStr HKCU "Environment" "SIM_ROOT" "$INSTDIR"
WriteRegStr HKCU "Environment" "ENGINE" "${ENGINE}"
WriteRegStr HKCU "Environment" "DERIV" "${DERIV}"
WriteRegExpandStr HKCU "Environment" "RUN_DIR" "%SIM_ROOT%\%ENGINE%\%DERIV%"
; Broadcast to all processes that they need to update their environment
; http://forums.winamp.com/showthread.php?t=118501
SendMessage ${HWND_BROADCAST}
${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
sectionEnd
section "Links"
; create the start menu directory & shortcuts
CreateDirectory $SMPROGRAMS\DTE
SetOutPath "$INSTDIR"
CreateShortCut "$SMPROGRAMS\DTE\AltControl.lnk"
"$INSTDIR\%ENGINE%\%DERIV%\alt_control.exe"
; Broadcast to all processes that they need to update their environment
; http://forums.winamp.com/showthread.php?t=118501
SendMessage ${HWND_BROADCAST}
${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
sectionEnd
After you run the installer, then attempt to run the start menu shortcut AltControl.lnk, you get a Windows missing shortcut error. Specifically: "Windows is searching for alt_control.exe. To locate the file yourself, click Browse."
If the environment variables already exist when you perform the installation, then the link works. Even weirder, if you click on the start menu shortcut properties and make some trivial change (like add and remove a space in the comment field) then click apply, Windows seems to regenerate the AltControl.lnk file (I know because the .lnk file increases in size even though no functional change was made through the dialog!?) and it works! So it seems like NSIS or some background Windows process in charge of generating/resolving .lnk files is not picking up on the newly created environment variables during the installation process. I've scoured the web and everything seems to indicate that the SendMessage I'm using should force everything to be aware of the newly created env variables. Thanks in advance to whoever can solve this mystery. You can use the included installer.nsi script to replicate my situation. Also note that it exhibits this behavior in multiple environments (XP, Vista, 7, with/without admin, etc.).
Most applications do not handle that message broadcast, it is mostly just for explorer.exe.
You can update the variables directly in your installer process and that will also be inherited by child processes:
System::Call 'Kernel32::SetEnvironmentVariable(t "ENGINE", t "v8")'
Related
I am trying to recognized the previous installation directory in nsis scripting language using registry.
I found a entry for uninstollation in this registry
HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PaperTrlQBDConnector" "UninstallString"
but the registry value is \xxx\xxxx\PaperTrlQBDConnector\unistall.exe
i want to extract the path without uninstall.exe to a variable.
Function PreDirCheck
# discover if QBD Connector is already installed
ClearErrors
SetRegView 64
ClearErrors
;ReadRegStr $previous_install_dir HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PaperTrlQBDConnector" "PreDir"
;${if} $previous_install_dir == 0
ReadRegStr $previous_install_dir HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PaperTrlQBDConnector" "UninstallString"
;${Endif}
IfErrors done
;sectionsetflags ${sec1} 0
SetAutoClose false
strcpy $INSTDIR $previous_install_dir
MessageBox MB_ICONQUESTION|MB_YesNO|MB_DEFBUTTON2 "Detected previous version in Directory $previous_install_dir $\n Click Yes To update in the existing directory $\n Click No to completely remove PaperTrl SyncManager and install to a new location?" IDYes lbl_abort IDNo lbl_un
lbl_abort:
sectionsetflags ${sec2} 0
Abort ;skip page
lbl_un:
!insertmacro UninstallExisting
sectionsetflags ${sec2} 1
done:
SetAutoClose false
;!insertmacro MUI_PAGE_DIRECTORY
sectionsetflags ${sec2} 1
FunctionEnd
i tried this code but it gives the variable value with the unistall.exe for the path
InstallDirRegKey knows how to remove the .exe if you point it to your UninstallString value. Controlling its reg-view however is not possible.
You can also remove the last path component yourself:
!include "FileFunc.nsh"
Function .onInit
ReadRegStr $0 HK.. ...
${GetParent} "$0" $1
MessageBox MB_OK $1
FunctionEnd
I have the following CMakeLists.txt (the only thing in the directory):
cmake_minimum_required(VERSION 3.0.0)
project(CPackUninstallerTest)
set(CPACK_GENERATOR NSIS)
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
"DetailPrint \\\"Sleeping...\\\"
Sleep 3000"
)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test.txt" "Some output\n")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/test.txt" TYPE DATA)
include(CPack)
Running cmake and cpack works fine, and .../build/CPackUninstallerTest-0.1.1-win64.exe is generated.
Running the installer works as expected:
And running the uninstaller (Uninstall.exe in the install directory) also works, where the sleep takes three seconds:
However, this uninstall window does not show up if I try to install on top of an existing installation. After clicking Yes here:
That window goes away for three seconds (as it uninstalls) before the new installer runs.
This is a terrible user experience and results in them re-running the installer while waiting for the hidden uninstaller, causing confusing results.
How do I configure NSIS or CMake/CPack to show the uninstaller progress bar when using CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL and re-installing?
Your issue could not be solved with NSIS interfaces of CMake.
I had a similar problem with CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL. The user could not install it again when uninstallation run manually.
The problem here is that manually running the uninstaller does not delete the install flag from windows registry keys.
My recommendation is that you can create your NSIS script by using NSIS template script of CMake. And you can add remove command easily to uninstaller section of new script.
And then you can give the new script as template to CMake.
As sad #hrn
For "normal" launch Uninstaller from installer (if apllication was installed)
You can create own NSIS.template.in (for example get cmake standart template and modify it)
Set new template as source
CMake checks if files NSIS.template.in, NSIS.InstallOptions.ini.in are in the module path
(module path in sample project was set with this command:
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/resources/nsis ${CMAKE_MODULE_PATH}))
If those files are found, then they are used for generating the installer.
There is an issue with sequential launch installer and uninstaller (not parallel)
NSIS ExecWait always waits for the child process but it does not wait for grandchildren.
So you need to use something more powerful,
for example nsis plugin StdUtils with functions StdUtils.ExecShellWaitEx/StdUtils.WaitForProcEx
There an issue with launching uninstaller:
firstly it copied itself to some temprorary directory and launches from this space,
and you dont know what you need to wait.
To avoid it launch uninstaller with flag:
${StdUtils.ExecShellWaitEx} $0 $1 "$uninstallerFullPath" "open" "_?=$uninstallerDirectoryPath"
If you cannot add plugin to NSIS directory (default C:\Program Files (x86)\NSIS),
you need to set path to this plugin from CMake,
add own variable CPACK_NSIS_INCLUDE_PLUGINS as script
and you can use it in some place in NSIS.template.in
CMakeLists:
set(CPACK_NSIS_INCLUDE_PLUGINS "
!addincludedir \\\"${PROJECT_SOURCE_DIR}\\\\resources\\\\nsis\\\\Include\\\"
!addplugindir /x86-unicode \\\"${PROJECT_SOURCE_DIR}\\\\resources\\\\nsis\\\\Plugins\\\\x86-unicode\\\"
!include 'StdUtils.nsh'
")
NSIS.template.in:
; on the begining of file
#CPACK_NSIS_OWN_INCLUDE_PLUGINS#
Function .onInit
...
Var /GLOBAL uninstallerDirectoryPath
Var /GLOBAL uninstallerFullPath
; Get the address of uninstaller
ReadRegStr $uninstallerFullPath HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\#CPACK_PACKAGE_INSTALL_REGISTRY_KEY#" "UninstallString"
; uninstallerFullPath has symbol " at the begin and symbol " at the end, we need to remove it
StrCpy $uninstallerFullPath $uninstallerFullPath "" 1 ; Remove first symbol
StrCpy $uninstallerFullPath $uninstallerFullPath -1 ; Remove last symbol
StrLen $2 "\#CPACK_NSIS_UNINSTALL_NAME#.exe"
StrCpy $uninstallerDirectoryPath $uninstallerFullPath -$2 # remove "\#CPACK_NSIS_UNINSTALL_NAME#.exe" from UninstallString to get path
StrCmp $uninstallerFullPath "" inst
MessageBox MB_YESNO|MB_ICONQUESTION \
"#CPACK_NSIS_PACKAGE_NAME# is already installed and must be removed before installation.$\n$\nDo you want to continue?" \
IDYES uninst IDNO 0
Quit
uninst:
${StdUtils.ExecShellWaitEx} $0 $1 "$uninstallerFullPath" "open" "_?=$uninstallerDirectoryPath"
; MessageBox MB_OK|MB_ICONSTOP "Result: $0 -> $1" ;returns "ok", "no_wait" or "error".
StrCmp $0 "error" ExecFailed ;check if process failed to create
StrCmp $0 "no_wait" WaitNotPossible ;check if process can be waited for - always check this!
StrCmp $0 "ok" WaitForProc ;make sure process was created successfully
MessageBox MB_OK|MB_ICONSTOP "error with StdUtils.ExecShellWaitEx"
Abort
ExecFailed:
MessageBox MB_OK|MB_ICONSTOP "Failed to create Uninstall process (error code: $1)"
Abort
WaitNotPossible:
MessageBox MB_OK|MB_ICONSTOP "Can not wait for Uninstall process."
Abort
WaitForProc:
${StdUtils.WaitForProcEx} $2 $1
IntCmp $2 0 uninst_successful
IntCmp $2 1 uninst_canceled
MessageBox MB_OK|MB_ICONSTOP "Uninstall process ends with error. (exit code: $2)"
Abort
uninst_canceled:
MessageBox MB_OK|MB_ICONINFORMATION "Uninstall canceled (exit code: $2)"
Quit
uninst_successful:
Delete "$uninstallerFullPath"
RMDir "$uninstallerDirectoryPath"
inst:
We have an installer system based on a WIX built MSI. The boot strapper is NSIS. It is just the way things went. And it all works fine now but for one little glitch.
There are two NSIS installers. One for new users. That runs the MSI conventionally so they contract screen can be agreed to. The the app checks for updates and the user can do just that. This is the second NSIS package for that:
Section -RA_unzip
InitPluginsDir
ReadRegStr $R1 HKCU "Software\Company\AppFolder\Property" "User Directory"
SetOutPath $R1
File "f:\cpp\AppName\deployment\AppName.msi"
ExecWait 'msiexec /i "$R1\AppName.msi" /L* msi.log /passive /norestart' $0
SectionEnd
Section -r_name_dlls
ReadRegStr $R0 HKCU "Software\Company\AppFolder\Property" "Program Directory"
Rename $R0\libssl_3.dll $R0\libssl-3.dll
Rename $R0\libcrypto_3.dll $R0\libcrypto-3.dll
SectionEnd
Section -Finishing Up
Sleep 1000
SectionEnd
Section -restartRA
ReadRegStr $R0 HKCU "Software\Company\AppFolder\Property" "Program Directory"
Exec $R0\ars.exe
Quit
SectionEnd
Less than half of the time the application comes up to the top of the Z-order. Sometimes it ends up at the bottom! This happens on my Windows7 and my wife's Windows10. If I run this without bumping the MSI version, (It just quits without an install), then the app window will always make it to the top.
I've even added BringWindowToTop(*GetMainWnd()); at the end of initialization when the main window is well established and running. And I did think it had something to do with the windows installer being slow to leave, that is why the Sleep 1000. It made no difference.
The only thing that is for sure is it happens when the windows installer actually does an install.
Calling Quit directly after Exec is not a good idea because if the installer quits before the child process has displayed its window the right to set the foreground window is lost.
You could try
ExecShell /WAITFORINPUTIDLE "" "$R0\ars.exe"
Sleep 1000
Quit
As a part of deployment script I need to add flag "run as admin" for some app. I found where it is configured in registry, but I see that is not enough. For example, I have procexp64.exe in C:\; I'm adding string value C:\procexp64.exe with data ~ RUNASADMIN in registry in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers. After this I see the checkbox "Run as admin" in exe properties, but actually the app isn't running as admin!
Ok, I removed my registry modification and configured it manually as show on the pic. The registry value appears back with the same data at the same place. I traced with procmon the modification and found that dllhost does it - it adds only one registry modification and doesn't modify anything on file system. dllhosts's modification works, but my modification - not. What I'm doing wrong?
Seems like it is not enough to add reg value...
Registry virtualization was disabled, user has admin rights. Win Srv 2012 R2.
Possible duplicate of: How to set "Run this program as an administrator" programmatically
Make sure you choose HKLM or HKCU correcly
You can try
reg.exe Add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\procexp64.exe" /d "RUNASADMIN" /f
or
reg.exe Add "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\procexp64.exe" /d "RUNASADMIN" /f
I want to kill RunDll32 process which is started from my install directory.
So if I use
${nsProcess::KillProcess} "rundll32.exe" $R0
It kills all the rundll32 processes on the system which I don't want to happen.
IMO, I have two options to fix this,
1. Identify interested process from commandline parameters
2. Identify from process startup directory (current directory).
I see there are few plugins to find the process but what they do is they just return found or not found. Instead I want IDs of the processes or list of these processes and then I'll check each process for command line or startup directory information and will act on the the required process.
BTW, I checked following plugins
http://nsis.sourceforge.net/FindProcDLL_plug-in
http://nsis.sourceforge.net/Processes_plug-in
http://forums.winamp.com/showthread.php?t=230998
Thanks
I fixed by using wmic query as follows:
StrCpy $1 "wmic Path win32_process where $\"name like 'Rundll32.exe' and CommandLine like '%$0%'$\" Call Terminate"
nsExec::Exec $1
If you control the .dll then the best option is to provide some sort of way to shutdown the app in a clean way. Perhaps you could find a window based on its class name and send it a WM_CLOSE message.
If you just need to shutdown the application during upgrade/uninstall then the LockedList plug-in is much better than just killing processes...