Signing an Uninstaller: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
No edit summary
(Use case of dummy !uninstfinalize)
 
(21 intermediate revisions by 8 users not shown)
Line 1: Line 1:
Especially under Windows Vista, installer/uninstaller binaries need to be signed to avoid alarming looking dialog boxes with dire warnings about "unknown publishers" etc.   
<div style="background-color:#FFFFDD;border:1px solid #aaaa22;color:#333311;padding:0.4em"><b>Note:</b> NSIS v3.08 added support for <span style="font-family: monospace, monospace;">!uninstfinalize</span> that allows you to sign the uninstaller directly without the workaround from this page.<br>
<b>Note 2:</b> Running a dummy command with <span style="font-family: monospace, monospace;">!uninstfinalize</span> may also be useful if only the installer will be signed. This could prevent bogus header fields in the uninstaller, see [https://sourceforge.net/p/nsis/feature-requests/577/ feature request ticket #577].
</div>
 
 
Especially since Windows Vista, installer/uninstaller binaries need to be signed to avoid alarming looking dialog boxes with dire warnings about "unknown publishers" etc.   


This presents a difficulty in that the uninstaller binary would normally never be present on your development/packaging machine, only being written onto the target machine at install time.  So how can you sign it?
This presents a difficulty in that the uninstaller binary would normally never be present on your development/packaging machine, only being written onto the target machine at install time.  So how can you sign it?


The answer is to run the installer on the development machine in a special mode which *only* writes the uninstaller to some known location, then sign that binary in the usual way, and finally package the signed uninstaller using a normal File command rather than WriteUninstaller.
The answer is to run the installer on the development machine in a special mode which ''only'' writes the uninstaller to some known location, then sign that binary in the usual way, and finally package the signed uninstaller using a normal <code>[[Reference/File|File]]</code> command rather than <code>[[Reference/WriteUninstaller|WriteUninstaller]]</code>.


<highlight-nsis>
<highlight-nsis>
#TODO: RequestExecutionLevel
#TODO: InstallDir
!ifdef INNER
!ifdef INNER
   !echo "Inner invocation"                  ; just to see what's going on
   !echo "Inner invocation"                  ; just to see what's going on
Line 12: Line 20:
!else
!else
   !echo "Outer invocation"
   !echo "Outer invocation"
   !system "$\"${NSISDIR}\makensis$\" /DINNER <name_of_script>.nsi" = 0
 
   !system "$%TEMP%\tempinstaller.exe /z" = 2
  ; Call makensis again against current file, defining INNER.  This writes an installer for us which, when
  ; it is invoked, will just write the uninstaller to some location, and then exit.
 
   !makensis '/DINNER "${__FILE__}"' = 0
 
  ; So now run that installer we just created as %TEMP%\tempinstaller.exe.
 
   !system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 0
 
  ; That will have written an uninstaller binary for us.  Now we sign it with your
  ; favorite code signing tool.
 
   !system "SIGNCODE <signing options> $%TEMP%\uninstaller.exe" = 0
   !system "SIGNCODE <signing options> $%TEMP%\uninstaller.exe" = 0
  ; Good.  Now we can carry on writing the real installer.
   OutFile "my_real_installer.exe"
   OutFile "my_real_installer.exe"
   SetCompressor /SOLID lzma
   SetCompressor /SOLID lzma
!endif
!endif


...
; ...


Function .onInit
Function .onInit
!ifdef INNER
!ifdef INNER
  ; If INNER is defined, then we aren't supposed to do anything except write out
  ; the uninstaller.  This is better than processing a command line option as it means
  ; this entire code path is not present in the final (real) installer.
  SetSilent silent
   WriteUninstaller "$%TEMP%\uninstaller.exe"
   WriteUninstaller "$%TEMP%\uninstaller.exe"
  SetErrorLevel 0  ; avoid exit code 2
   Quit  ; just bail out quickly when running the "inner" installer
   Quit  ; just bail out quickly when running the "inner" installer
!endif
!endif


...[the rest of your normal .onInit]...
; ...[the rest of your normal .onInit]...
FunctionEnd
FunctionEnd


...
; ...


Section "Files" ; or whatever
Section "Files" ; or whatever


...
; ...


   ; where you would normally put WriteUninstaller ${INSTDIR}\uninstaller.exe put instead:
   ; where you would normally put WriteUninstaller ${INSTDIR}\uninstaller.exe put instead:
Line 40: Line 68:
!ifndef INNER
!ifndef INNER
   SetOutPath $INSTDIR
   SetOutPath $INSTDIR
  ; this packages the signed uninstaller
   File $%TEMP%\uninstaller.exe
   File $%TEMP%\uninstaller.exe
!endif
!endif


...
; ...
SectionEnd
SectionEnd


Line 49: Line 80:
Section "Uninstall"
Section "Uninstall"


   ; your normal uninstaller section or sections (they're not needed in the "outer" installer
   ; your normal uninstaller section or sections (they're not needed in the "outer"
  ; installer and will just cause warnings because there is no WriteUninstaller command)


SectionEnd
SectionEnd
Line 56: Line 88:


</highlight-nsis>
</highlight-nsis>
This script should just be compiled in the usual way.  It will invoke makensis a second time with the additional symbol INNER defined to generate a slightly unusual installer that does nothing except write the uninstaller binary to a specified location (%TEMP%/uninstaller.exe).  Next it actually invokes the generated installer and sure enough it writes %TEMP%/uninstaller.exe with no user interaction needed.  Now we can sign the binary with our usual tools (I use SIGNCODE because I have a rather arcane development environment, but SIGNTOOL would be more usual I guess).  Once that is done, we run through the rest of the normal stuff, except instead of calling <code>[[Reference/WriteUninstaller|WriteUninstaller]]</code> again we just package the already-created, signed uninstaller instead. Job done.
Hope you find this helpful.
[[User:Davida|Davida]] 07:26, 3 December 2009 (UTC): If you are using the Modern UI, you also need to add the conditionals as follows:
<highlight-nsis>
!ifdef INNER 
  !insertmacro MUI_UNPAGE_CONFIRM
  !insertmacro MUI_UNPAGE_INSTFILES
!endif
</highlight-nsis>
This prevents yet another warning message.
[[User:9999|9999]] 4 July 2021: Setting "SetCompress off" is not a good idea since it will result in a larger uninstaller.
[[Category: Code Examples]]

Latest revision as of 18:44, 21 January 2024

Note: NSIS v3.08 added support for !uninstfinalize that allows you to sign the uninstaller directly without the workaround from this page.

Note 2: Running a dummy command with !uninstfinalize may also be useful if only the installer will be signed. This could prevent bogus header fields in the uninstaller, see feature request ticket #577.


Especially since Windows Vista, installer/uninstaller binaries need to be signed to avoid alarming looking dialog boxes with dire warnings about "unknown publishers" etc.

This presents a difficulty in that the uninstaller binary would normally never be present on your development/packaging machine, only being written onto the target machine at install time. So how can you sign it?

The answer is to run the installer on the development machine in a special mode which only writes the uninstaller to some known location, then sign that binary in the usual way, and finally package the signed uninstaller using a normal File command rather than WriteUninstaller.

#TODO: RequestExecutionLevel
#TODO: InstallDir 
 
!ifdef INNER
  !echo "Inner invocation"                  ; just to see what's going on
  OutFile "$%TEMP%\tempinstaller.exe"       ; not really important where this is
  SetCompress off                           ; for speed
!else
  !echo "Outer invocation"
 
  ; Call makensis again against current file, defining INNER.  This writes an installer for us which, when
  ; it is invoked, will just write the uninstaller to some location, and then exit.
 
  !makensis '/DINNER "${__FILE__}"' = 0
 
  ; So now run that installer we just created as %TEMP%\tempinstaller.exe.
 
  !system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 0
 
  ; That will have written an uninstaller binary for us.  Now we sign it with your
  ; favorite code signing tool.
 
  !system "SIGNCODE <signing options> $%TEMP%\uninstaller.exe" = 0
 
  ; Good.  Now we can carry on writing the real installer.
 
  OutFile "my_real_installer.exe"
  SetCompressor /SOLID lzma
!endif
 
; ...
 
Function .onInit
!ifdef INNER
 
  ; If INNER is defined, then we aren't supposed to do anything except write out
  ; the uninstaller.  This is better than processing a command line option as it means
  ; this entire code path is not present in the final (real) installer.
  SetSilent silent
  WriteUninstaller "$%TEMP%\uninstaller.exe"
  SetErrorLevel 0  ; avoid exit code 2
  Quit  ; just bail out quickly when running the "inner" installer
!endif
 
; ...[the rest of your normal .onInit]...
FunctionEnd
 
; ...
 
Section "Files" ; or whatever
 
; ...
 
  ; where you would normally put WriteUninstaller ${INSTDIR}\uninstaller.exe put instead:
 
!ifndef INNER
  SetOutPath $INSTDIR
 
  ; this packages the signed uninstaller
 
  File $%TEMP%\uninstaller.exe
!endif
 
; ...
SectionEnd
 
!ifdef INNER
Section "Uninstall"
 
  ; your normal uninstaller section or sections (they're not needed in the "outer"
  ; installer and will just cause warnings because there is no WriteUninstaller command)
 
SectionEnd
!endif


This script should just be compiled in the usual way. It will invoke makensis a second time with the additional symbol INNER defined to generate a slightly unusual installer that does nothing except write the uninstaller binary to a specified location (%TEMP%/uninstaller.exe). Next it actually invokes the generated installer and sure enough it writes %TEMP%/uninstaller.exe with no user interaction needed. Now we can sign the binary with our usual tools (I use SIGNCODE because I have a rather arcane development environment, but SIGNTOOL would be more usual I guess). Once that is done, we run through the rest of the normal stuff, except instead of calling WriteUninstaller again we just package the already-created, signed uninstaller instead. Job done.

Hope you find this helpful.

Davida 07:26, 3 December 2009 (UTC): If you are using the Modern UI, you also need to add the conditionals as follows:

!ifdef INNER  
  !insertmacro MUI_UNPAGE_CONFIRM
  !insertmacro MUI_UNPAGE_INSTFILES
!endif

This prevents yet another warning message.

9999 4 July 2021: Setting "SetCompress off" is not a good idea since it will result in a larger uninstaller.