Check/Prevent Windows Shutdown (NSIS) - nsis

To summarize, I need to check and/or prevent a user if he/she decides to shutdown the computer while the installer is running. Now I've researched for a while and came up with the following:
${If} ${AtMostWinXP}
System::Call `kernel32::GetModuleHandle(i0)i.r3`
System::Call `user32::CreateWindowEx(i0,t"STATIC",t"MyApp",i0,i0,i0,i0,i0,i$HWNDPARENT,i0,ir3,i0)i.r1`
${ElseIf} ${AtLeastVista}
System::Call `user32::ShutdownBlockReasonCreate(ir1,w"MyApp is running and still needs to clean up before shutting down!")i.r0`
${EndIf}
However the above snippet isn't working. Am I missing something? I've tried using just:
System::Call `kernel32::GetModuleHandle(i0)i.r3`
System::Call `user32::CreateWindowEx(i0,t"STATIC",t"MyApp",i0,i0,i0,i0,i0,i$HWNDPARENT,i0,ir3,i0)i.r1`
System::Call `user32::ShutdownBlockReasonCreate(ir1,w"MyApp is running and still needs to clean up before shutting down!")i.r0`
As the first two calls are meant for Windows XP and earlier and the third call is meant for Windows Vista or later but is ignored by Windows XP and earlier I believe (I have no evidence to support this theory). This too isn't working.
Also, I can use user32::ShutdownBlockReasonCreate(ir1,w"$(PreventShutdown)")i.r0 instead of using the entire string in the above snippet for different language support, right?

Your code for Windows XP makes no sense, the window needs to handle WM_QUERYENDSESSION to block the shutdown. Luckily NSIS already handles WM_QUERYENDSESSION for you.
Use something like this for Vista and later:
LoadLanguageFile "${NSISDIR}\Contrib\Language Files\English.nlf"
LangString BlockReason ${LANG_ENGLISH} "Installer blah blah"
LoadLanguageFile "${NSISDIR}\Contrib\Language Files\Swedish.nlf"
LangString BlockReason ${LANG_SWEDISH} "Installer bork bork"
!include nsDialogs.nsh ; For WS_CHILD
!define /ifndef WS_POPUP 0x80000000
!include LogicLib.nsh
Function CreateShutdownBlockReason
StrCpy $1 $hwndParent
${If} $1 Z= 0 ; $hwndParent is 0, create a new window for silent installers
System::Call 'USER32::CreateWindowEx(i0, t "STATIC", t "$(^Name)", i ${WS_CHILD}|${WS_POPUP}, i0, i0, i0, i0, p r1, i0, i0, i0)p.r1'
${EndIf}
System::Call 'USER32::ShutdownBlockReasonCreate(p r1, w "$(BlockReason)")'
FunctionEnd
Function .onInit
IfSilent 0 +2
Call CreateShutdownBlockReason ; .onGuiInit is not executed in silent installers
FunctionEnd
Function .onGuiInit
Call CreateShutdownBlockReason
FunctionEnd
I'm not sure if a silent installer will be able to block the shutdown but this is the best you can do without a plug-in.

Related

How to stop the uninstallation and pop up a message box when uninstalling in Safe mode using NSIS?

How to stop the uninstallation and pop up a message box (something like as shown below) when uninstalling the installed software using NSIS if the operating system is in Safe mode.
!include LogicLib.nsh
Function un.onInit
!define /IfNDef SM_CLEANBOOT 67
System::Call 'USER32::GetSystemMetrics(i${SM_CLEANBOOT})i.r0'
${If} $0 <> 0
MessageBox mb_IconStop "Safe mode blah blah"
Quit
${EndIf}
FunctionEnd

nsis - remove pinned icon from taskbar on uninstall

I'm working on an app for a company and they requested that if the app is pinned to the taskbar, when uninstalling the app should be unpinned. If I just delete the icon from quicklaunch\user pinned\taskbar then it leaves a blank icon on the taskbar.
I need to somehow actually unpin it. The only thing I've come across is installing winshell plugin (http://nsis.sourceforge.net/WinShell_plug-in) and then calling IStartMenuPinnedList::RemoveFromList (https://msdn.microsoft.com/en-us/library/windows/desktop/bb774817(v=vs.85).aspx)
I'd rather not install a plugin if I don't need to. Does anyone have any suggestions?
NSIS does not have native support for this interface so you have to use a plug-in. If you want to avoid that 3rd-party plug-in (written by me) then you can use the System plug-in instead:
!include "LogicLib.nsh"
!include "Win\COM.nsh" ; NSIS v3+
!macro UnpinShortcut lnkpath
Push $0
Push $1
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 ""
${If} $0 P<> 0
System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g "${IID_IShellItem}", *p0r1)' "${lnkpath}"
${If} $1 P<> 0
${IStartMenuPinnedList::RemoveFromList} $0 '(r1)'
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
Pop $1
Pop $0
!macroend
Section Uninstall
!insertmacro UnpinShortcut "$SMPrograms\MyApp.lnk"
Delete "$SMPrograms\MyApp.lnk"
SectionEnd

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

How do you request administrator permissions using NSIS?

I am quite new with NSIS.
I am trying to request administrator permissions in order to run the installer, as it messes around a bit with registries.
My problem with "RequestExecutionLevel" and "MULTIUSER_EXECUTIONLEVEL" is that they both absolutely block any non-Admin user from opening the installer, even when selecting "Run as Administrator" in the context menu.
I have tried using the RunAs DLL, but I have not found a single thread as to what to put in the $command variable passed to "RunAsW" function.
Here is my (pretty hacked-up) code:
StrCpy $0 0
StrCpy $1 ""
System::Call 'RunAs::GetAdministrators(w r1, *i .r0) i .r2 ? u'
System::Alloc 64
Pop $4
StrCpy $4 $2
StrCpy $5 ""
loop:
IntCmp $0 0 endloop
System::Call '*$4(w .r3)'
StrCpy $5 "$5|$3"
endloop:
System::Free $4 ; we free the memory used by the array
StrCpy $5 "$5" "" 1
!insertmacro MUI_INSTALLOPTIONS_WRITE "Settings.ini" "Field 1" "ListItems" $5
!insertmacro MUI_INSTALLOPTIONS_DISPLAY "Settings.ini"
!insertmacro MUI_INSTALLOPTIONS_READ $1 "UserPass" "Field 1" "State"
!insertmacro MUI_INSTALLOPTIONS_READ $2 "Settings.ini" "Field 2" "State"
StrCpy $3 "%%LOGONSERVER%%"
StrCpy $3 0
StrCpy $4 0
System::Call 'RunAs::RunAsW(w r1, w r2, w r3, *w .r4) i .r0 ? u'
MessageBox MB_OK $0
IntCmp $0 1 success
Quit
success:
!insertmacro MUI_LANGDLL_DISPLAY
A lot of it is just guess work and trial and error. (btw - I also tried running through a loop to get all Administrators, but it seems the DLL was intended only for 32-bit machines, so...).
Anyway, my question is:
Does anybody know of a way (using "RunAs" or otherwise) to open a dialog requesting Username and password, check the credentials and continue with the installation only if they check out?
Also, I know there is a way to set up an installer so that it comes with that nice shield icon on it that lets users know that Admin permission will be requested. Does anybody know how to do that?
Any help would be very much appreciated, as this is the only thing currently preventing the deployment of my app.
Outfile RequireAdmin.exe
RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on)
!include LogicLib.nsh
Function .onInit
UserInfo::GetAccountType
pop $0
${If} $0 != "admin" ;Require admin rights on NT4+
MessageBox mb_iconstop "Administrator rights required!"
SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
Quit
${EndIf}
FunctionEnd
Page InstFiles
Section
SectionEnd
is the basic code I usually recommend to make sure the installer is running as an Administrator.
IMHO it does not make sense to prompt for credentials on a custom page unless only parts of the install process requires administrator access and the other part requires access to the users profile. If this applies to you then you should take a look at the UAC plug-in (It is a bit complicated to use and makes it impossible for your exe file to get the shield overlay icon)
I don't think the RunAs plug-in works correctly on Vista+ when UAC is on so trying to get it to work might be a dead end...
The recommended way to get the shield is to request elevation in the exe manifest, RequestExecutionLevel admin does that. If you don't use RequestExecutionLevel at all in your script your installer might be detected as a legacy installer and it will also get the shield overlay.
In Windows Vista, if an executable file requires elevation to launch,
then the executable's icon should be "stamped" with a shield icon to
indicate this fact. The executable's application manifest must mark
"requireAdministrator" to designate the executable as requiring a full
administrative access token. The shield icon overlay will also be
automatically placed on executables that are deemed to require
elevation as per the installer detection heuristics. For example, a
file named setup.exe will automatically receive a shield icon overlay
even if the executable does not have an embedded application manifest.

Resources