Signing an Uninstaller: Difference between revisions
(The script will not work if copy/paste, Fixed the error SetOutPath ${INSTDIR} -> SetOutPath $INSTDIR.) |
(Use case of dummy !uninstfinalize) |
||
(11 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
Especially | <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 <code>File</code> command rather than <code>WriteUninstaller</code>. | 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 13: | Line 21: | ||
!echo "Outer invocation" | !echo "Outer invocation" | ||
; Call makensis again, defining INNER. This writes an installer for us which, when | ; 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. | ; 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 | ; So now run that installer we just created as %TEMP%\tempinstaller.exe. | ||
!system "$%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 | ; 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 | ||
Line 35: | Line 41: | ||
!endif | !endif | ||
... | ; ... | ||
Function .onInit | Function .onInit | ||
Line 41: | Line 47: | ||
; If INNER is defined, then we aren't supposed to do anything except write out | ; If INNER is defined, then we aren't supposed to do anything except write out | ||
; the | ; 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. | ; 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 67: | Line 74: | ||
!endif | !endif | ||
... | ; ... | ||
SectionEnd | SectionEnd | ||
Line 74: | Line 81: | ||
; your normal uninstaller section or sections (they're not needed in the "outer" | ; your normal uninstaller section or sections (they're not needed in the "outer" | ||
; installer and will just cause warnings because there is no | ; installer and will just cause warnings because there is no WriteUninstaller command) | ||
SectionEnd | SectionEnd | ||
Line 83: | Line 90: | ||
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>WriteUninstaller</code> again we just package the already-created, signed uninstaller instead. | 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. | Hope you find this helpful. | ||
Line 97: | Line 104: | ||
This prevents yet another warning message. | 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]] | [[Category: Code Examples]] | ||
Latest revision as of 18:44, 21 January 2024
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.