使用嵌入式签名而不是单独的卸载程序脚本创建签名的卸载程序
creating signed uninstaller using embedded signing rather than separate uninstaller script
为了创建签名的卸载程序,我无法使用通常的 NSIS WriteUninstaller
命令创建它,因为通常脚本会创建卸载程序并将其嵌入到安装程序中,所以没有机会签名卸载程序。详情请见https://nsis-dev.github.io/NSIS-Forums/html/t-245688.html.
这意味着我必须创建一个单独的卸载程序脚本,对其进行签名,然后使用普通的文件命令将其嵌入到安装程序中。这适用于删除文件;然而,由于卸载程序可执行文件是 运行,它无法删除自身,并留下 uninstaller.exe
和 MyApp
目录。这是我的卸载程序脚本:
!include "LogicLib.nsh"
!include "Sections.nsh"
;Include Modern UI
!include "MUI2.nsh"
!define MAJOR_VERSION "1"
!define MINOR_VERSION "2"
!define PATCH_VERSION "3"
!define BUILD_VERSION "4"
!define APP_COPYRIGHT "MyApp © MyCompany 2021"
!define COMPANY_NAME "MyCompany"
!define FLEX_LM "FlexLM"
!define FLEX_DIR "FlexSMyApp"
!define LANG_ENGLSH "English"
!define PRODUCT_NAME "MyApp"
!define PRODUCT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}.${BUILD_VERSION}"
!define SETUP_NAME "MyAppSetup.exe"
BrandingText "MyCompany"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME}"
InstallDir "$PROGRAMFILES64${PRODUCT_NAME}\"
InstallDirRegKey HKLM "Software$PRODUCT_NAME" ""
ShowInstDetails hide
ShowUnInstDetails hide
SetCompressor /SOLID lzma
SetCompressorDictSize 12
Var MyAppInstallDir
Var FlexLmInstallDir
;Request application privileges for Windows
RequestExecutionLevel admin
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "MyAppLicense.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesCheck
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_A)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install MyApp."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesA
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
## This is the title on the FlexLM Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_B)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install FlexLM."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesB
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DeleteSectionsINI
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
LangString NoSectionsSelected ${LANG_ENGLSH} "You haven't selected any sections!"
LangString MUI_DIRECTORYPAGE_TEXT_TOP_A ${LANG_ENGLSH} "Setup will install \
${PRODUCT_NAME} in the following folder..."
LangString MUI_DIRECTORYPAGE_TEXT_TOP_B ${LANG_ENGLSH} "Setup will install \
${FLEX_LM} in the following folder..."
;--------------------------------
; Settings
!define PROG1_InstDir "$PROGRAMFILES64${PRODUCT_NAME}\"
!define PROG1_StartIndex ${SEC1}
!define PROG1_EndIndex ${SEC1}
!define PROG2_InstDir "C:${FLEX_DIR}"
!define PROG2_StartIndex ${SEC3}
!define PROG2_EndIndex ${SEC3}
;--------------------------------
; Function
; StrContains
; This function does a case sensitive searches for an occurrence of a substring in a string.
; It returns the substring if it is found.
; Otherwise it returns null("").
; Written by kenglish_hi
; Adapted from StrReplace written by dandaman32
Var STR_HAYSTACK
Var STR_NEEDLE
Var STR_CONTAINS_VAR_1
Var STR_CONTAINS_VAR_2
Var STR_CONTAINS_VAR_3
Var STR_CONTAINS_VAR_4
Var STR_RETURN_VAR
Function StrContains
Exch $STR_NEEDLE
Exch 1
Exch $STR_HAYSTACK
; Uncomment to debug
;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
StrCpy $STR_RETURN_VAR ""
StrCpy $STR_CONTAINS_VAR_1 -1
StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
loop:
IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
Goto loop
found:
StrCpy $STR_RETURN_VAR $STR_NEEDLE
Goto done
done:
Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the
Exch $STR_RETURN_VAR
FunctionEnd
!macro _StrContainsConstructor OUT NEEDLE HAYSTACK
Push `${HAYSTACK}`
Push `${NEEDLE}`
Call StrContains
Pop `${OUT}`
!macroend
!define StrContains '!insertmacro "_StrContainsConstructor"'
;--------------------------------
; Start sections
Section "MyApp" SEC1
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=10=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=10=]"
StrCmp [=10=] "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} [=10=] "Flex" "$INSTDIR"
StrCmp [=10=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File MyApp.exe
File ReleaseNotes.txt
File MyCompany_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Version' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
!ifndef INNER
SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
doneFlex:
# messagebox mb_ok sec1
SectionEnd
Section "FlexLM" SEC3
;MessageBox MB_OK "SEC3 #1 INSTDIR is $INSTDIR"
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=10=] "Pro" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=10=]"
StrCmp [=10=] "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
${StrContains} [=10=] "Flex" "$INSTDIR"
StrCmp [=10=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
doneFlex:
##All the files in Group 2 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File installs.exe
File lmdown.exe
File lmflex.exe
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneMyApp:
# messagebox mb_ok sec3
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecMyApp ${LANG_ENGLISH} "MyAppTM software is an easy-to-use suite of tools."
LangString DESC_SecFlexLM ${LANG_ENGLISH} "FlexSMyApp contains all the files necessary to implement the FlexLM license server."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC1} $(DESC_SecMyApp)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC3} $(DESC_SecFlexLM)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
; ...
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"
Quit ; just bail out quickly when running the "inner" installer
!endif
## Create $PLUGINSDIR
InitPluginsDir
SetOutPath $TEMP
File /oname=spltmp.bmp "MyCompany_LandingPage_114.bmp"
splash::show 2000 $TEMP\spltmp
Pop [=10=] ; [=10=] has '1' if the user closed the splash screen early,
; '0' if everything closed normally, and '-1' if some error occurred.
Delete $TEMP\spltmp.bmp
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)
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed files and registry keys for MyApp
Delete $INSTDIR\config.dat
Delete $INSTDIR\MyApp.exe
Delete $INSTDIR\ReleaseNotes.txt
Delete $INSTDIR\MyCompany_LandingPage_114.bmp
Delete $INSTDIR\MyAppLicense.txt
Delete "$SMPROGRAMS\MyApp.lnk"
DeleteRegKey HKCU "SOFTWARE${PRODUCT_NAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
# now delete installed files and registry keys for FlexLM
Delete $INSTDIR\installs.exe
Delete $INSTDIR\lmborrow.exe
Delete $INSTDIR\lmflex.exe
# Delete the MyApp and FlexLM directories
RMDir $INSTDIR
SectionEnd
!endif
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## Also if no sections are selected, warn the user!
Function ComponentsLeave
Push $R0
Push $R1
Call IsPROG1Selected
Pop $R0
Call IsPROG2Selected
Pop $R1
StrCmp $R0 1 End
StrCmp $R1 1 End
Pop $R1
Pop $R0
MessageBox MB_OK|MB_ICONEXCLAMATION "$(NoSectionsSelected)"
Abort
End:
Pop $R1
Pop $R0
FunctionEnd
Function IsPROG1Selected
Push $R0
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
SectionGetFlags 0 $R0 # Get section flags
IntOp $R0 $R0 & ${SF_SELECTED}
StrCmp $R0 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R0 1
Exch $R0
FunctionEnd
Function IsPROG2Selected
Push $R1
StrCpy $R1 ${PROG2_StartIndex} # Group 2 start
IntOp $R1 $R1 + 1
SectionGetFlags 1 $R1 # Get section flags
IntOp $R1 $R1 & ${SF_SELECTED}
StrCmp $R1 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R1 1
Exch $R1
FunctionEnd
## Here we are selecting first sections to install
## by unselecting all the others!
Function SelectFilesA
${If} ${SectionIsSelected} ${SEC1}
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
# If user clicks Back now, we will know to reselect Group 2`s sections for
# Components page
StrCpy $IfBack 1
# We need to save the state of the Group 2 Sections
# for the next InstFiles page
Push $R0
Push $R1
StrCpy $R0 ${PROG2_StartIndex} # Group 2 start
; Loop:
; IntOp $R0 $R0 + 1
; SectionGetFlags $R0 $R1 # Get section flags
; WriteINIStr "$PLUGINSDIR\sections.ini" Sections $R0 $R1 # Save state
; !insertmacro UnselectSection $R0 # Then unselect it
; StrCmp $R0 ${PROG2_EndIndex} 0 Loop
# Don`t install prog 1?
Call IsPROG1Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG1_InstDir define
StrCpy $INSTDIR "${PROG1_InstDir}"
Pop $R1
Pop $R0
FunctionEnd
## Here we need to unselect all Group 1 sections
## and then re-select those in Group 2 (that the user had selected on
## Components page)
Function SelectFilesB
${If} ${SectionIsSelected} ${SEC3}
;MessageBox MB_OK "SEC1 #3 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
Push $R0
;Push $R1
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
; Loop:
; IntOp $R0 $R0 + 1
; !insertmacro UnselectSection $R0 # Unselect it
; StrCmp $R0 ${PROG1_EndIndex} 0 Loop
; Call ResetFiles
# Don't install prog 2?
Call IsPROG2Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG2_InstDir define
StrCpy $INSTDIR "${PROG2_InstDir}"
;Pop $R1
Pop $R0
FunctionEnd
有人有什么建议吗?
更新:
谢谢@Anders,我不知何故误删了SelectFilesCheck
。现在,如果我使用此代码:
!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. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SMyApp-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
Section "MyApp" SEC1
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=11=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=11=]"
StrCmp [=11=] "" notfoundMyApp
StrCpy $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} [=11=] "Flex" "$INSTDIR"
StrCmp [=11=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
然后我得到这些错误:
!makensis: returned 0
!system: "set __COMPAT_LAYER=RunAsInvoker&"C:\Users\moorer\AppData\Local\Temp\tempinstaller.exe""
!system: returned 2
Error: Can't add entry, no section or function is open!
Error in script "C:\Project\MsiPackaging\Installer\MyAppInstaller.nsi" on line 169 -- aborting creation process
但是如果我尝试将其放在这样的部分中:
;--------------------------------
; Start sections
Section "MyApp" SEC1
!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. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SQI-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=13=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=13=]"
StrCmp [=13=] "" notfoundQI
StrCpy $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
Goto doneQI
notfoundQI:
;MessageBox MB_OK 'Did not find QI string'
doneQI:
${StrContains} [=13=] "Flex" "$INSTDIR"
StrCmp [=13=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
doneFlex:
# messagebox mb_ok sec1
SectionEnd
然后我得到这个错误:
Error: command OutFile not valid in Section
Error in script "MyAppInstaller.nsi" on line 157 -- aborting creation process
!makensis: returned 1, aborting
Error in script "C:\Projects\MsiPackaging\Installer\MyAppInstaller.nsi" on line 165 -- aborting creation process
你有什么建议吗? TIA.
来自 Wiki 的签名代码根本没有使用 StrCpy
,它必须来自您的 StrCpy $IfBack 1
行。也许你忘记了Var IfBack
上面的函数。
code from the wiki 应该可以,但这里有一个替代版本:
#TODO: RequestExecutionLevel
#TODO: InstallDir
!include MUI2.nsh
!macro WriteSignedUninstaller Destination
!makensis '"/DGENRATINGUNINST=$%TEMP%\Uninst.exe" "${__FILE__}" "/XOutfile `$%TEMP%\tempinstaller.exe`"' = 0 ; Create fake installer
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2 ; Run fake installer to generate the uninstaller
!system 'SIGNCODE <signing options> "$%TEMP%\Uninst.exe"' = 0 ; Change this line. As a demonstration, use !system 'echo Dummy >> "$%TEMP%\Uninst.exe"'
File "/oname=${Destination}" "$%TEMP%\Uninst.exe"
!macroend
!macro DeclareLanguages
!insertmacro MUI_LANGUAGE English
!macroend
!ifndef GENRATINGUNINST
# Installer:
############
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro DeclareLanguages
Section
SetOutPath $InstDir
!insertmacro WriteSignedUninstaller "$InstDir\Uninst.exe"
; File MyApp.exe
SectionEnd
!else
# Uninstaller:
##############
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro DeclareLanguages
!verbose push 2
SilentInstall Silent
Section
WriteUninstaller "${GENRATINGUNINST}"
Quit
SectionEnd
!verbose pop
Section -Uninstall
; Delete "$InstDir\MyApp.exe"
Delete "$InstDir\Uninst.exe"
RMDir "$InstDir"
SectionEnd
!endif # EOF
感谢您的反馈,@Anders。最后,我找到了这个 SelfDel
插件,它允许我在删除卸载程序的同时使用单独的安装程序和卸载程序脚本:
https://nsis.sourceforge.io/SelfDel_plug-in
Name `SelfDel plug-in example`
OutFile Example.exe
XPStyle on
; Administrator privileges required.
RequestExecutionLevel admin
Page InstFiles
Section
;SelfDel::Del /RMDIR
;SelfDel::Del /REBOOT
;SelfDel::Del /SHUTDOWN
SelfDel::Del
SetAutoClose true
SectionEnd
更新:
当我使用 SelfDel
时,它会删除 uninstaller.exe
但不会删除原始应用程序目录,即使我使用 SelfDel::Del /RMDIR
所以我最终选择了 Anders以上回答。
为了创建签名的卸载程序,我无法使用通常的 NSIS WriteUninstaller
命令创建它,因为通常脚本会创建卸载程序并将其嵌入到安装程序中,所以没有机会签名卸载程序。详情请见https://nsis-dev.github.io/NSIS-Forums/html/t-245688.html.
这意味着我必须创建一个单独的卸载程序脚本,对其进行签名,然后使用普通的文件命令将其嵌入到安装程序中。这适用于删除文件;然而,由于卸载程序可执行文件是 运行,它无法删除自身,并留下 uninstaller.exe
和 MyApp
目录。这是我的卸载程序脚本:
!include "LogicLib.nsh"
!include "Sections.nsh"
;Include Modern UI
!include "MUI2.nsh"
!define MAJOR_VERSION "1"
!define MINOR_VERSION "2"
!define PATCH_VERSION "3"
!define BUILD_VERSION "4"
!define APP_COPYRIGHT "MyApp © MyCompany 2021"
!define COMPANY_NAME "MyCompany"
!define FLEX_LM "FlexLM"
!define FLEX_DIR "FlexSMyApp"
!define LANG_ENGLSH "English"
!define PRODUCT_NAME "MyApp"
!define PRODUCT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}.${BUILD_VERSION}"
!define SETUP_NAME "MyAppSetup.exe"
BrandingText "MyCompany"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME}"
InstallDir "$PROGRAMFILES64${PRODUCT_NAME}\"
InstallDirRegKey HKLM "Software$PRODUCT_NAME" ""
ShowInstDetails hide
ShowUnInstDetails hide
SetCompressor /SOLID lzma
SetCompressorDictSize 12
Var MyAppInstallDir
Var FlexLmInstallDir
;Request application privileges for Windows
RequestExecutionLevel admin
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "MyAppLicense.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesCheck
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_A)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install MyApp."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesA
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
## This is the title on the FlexLM Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_B)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install FlexLM."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesB
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DeleteSectionsINI
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
LangString NoSectionsSelected ${LANG_ENGLSH} "You haven't selected any sections!"
LangString MUI_DIRECTORYPAGE_TEXT_TOP_A ${LANG_ENGLSH} "Setup will install \
${PRODUCT_NAME} in the following folder..."
LangString MUI_DIRECTORYPAGE_TEXT_TOP_B ${LANG_ENGLSH} "Setup will install \
${FLEX_LM} in the following folder..."
;--------------------------------
; Settings
!define PROG1_InstDir "$PROGRAMFILES64${PRODUCT_NAME}\"
!define PROG1_StartIndex ${SEC1}
!define PROG1_EndIndex ${SEC1}
!define PROG2_InstDir "C:${FLEX_DIR}"
!define PROG2_StartIndex ${SEC3}
!define PROG2_EndIndex ${SEC3}
;--------------------------------
; Function
; StrContains
; This function does a case sensitive searches for an occurrence of a substring in a string.
; It returns the substring if it is found.
; Otherwise it returns null("").
; Written by kenglish_hi
; Adapted from StrReplace written by dandaman32
Var STR_HAYSTACK
Var STR_NEEDLE
Var STR_CONTAINS_VAR_1
Var STR_CONTAINS_VAR_2
Var STR_CONTAINS_VAR_3
Var STR_CONTAINS_VAR_4
Var STR_RETURN_VAR
Function StrContains
Exch $STR_NEEDLE
Exch 1
Exch $STR_HAYSTACK
; Uncomment to debug
;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
StrCpy $STR_RETURN_VAR ""
StrCpy $STR_CONTAINS_VAR_1 -1
StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
loop:
IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
Goto loop
found:
StrCpy $STR_RETURN_VAR $STR_NEEDLE
Goto done
done:
Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the
Exch $STR_RETURN_VAR
FunctionEnd
!macro _StrContainsConstructor OUT NEEDLE HAYSTACK
Push `${HAYSTACK}`
Push `${NEEDLE}`
Call StrContains
Pop `${OUT}`
!macroend
!define StrContains '!insertmacro "_StrContainsConstructor"'
;--------------------------------
; Start sections
Section "MyApp" SEC1
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=10=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=10=]"
StrCmp [=10=] "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} [=10=] "Flex" "$INSTDIR"
StrCmp [=10=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File MyApp.exe
File ReleaseNotes.txt
File MyCompany_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Version' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
!ifndef INNER
SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
doneFlex:
# messagebox mb_ok sec1
SectionEnd
Section "FlexLM" SEC3
;MessageBox MB_OK "SEC3 #1 INSTDIR is $INSTDIR"
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=10=] "Pro" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=10=]"
StrCmp [=10=] "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
${StrContains} [=10=] "Flex" "$INSTDIR"
StrCmp [=10=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
doneFlex:
##All the files in Group 2 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File installs.exe
File lmdown.exe
File lmflex.exe
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneMyApp:
# messagebox mb_ok sec3
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecMyApp ${LANG_ENGLISH} "MyAppTM software is an easy-to-use suite of tools."
LangString DESC_SecFlexLM ${LANG_ENGLISH} "FlexSMyApp contains all the files necessary to implement the FlexLM license server."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC1} $(DESC_SecMyApp)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC3} $(DESC_SecFlexLM)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
; ...
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"
Quit ; just bail out quickly when running the "inner" installer
!endif
## Create $PLUGINSDIR
InitPluginsDir
SetOutPath $TEMP
File /oname=spltmp.bmp "MyCompany_LandingPage_114.bmp"
splash::show 2000 $TEMP\spltmp
Pop [=10=] ; [=10=] has '1' if the user closed the splash screen early,
; '0' if everything closed normally, and '-1' if some error occurred.
Delete $TEMP\spltmp.bmp
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)
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed files and registry keys for MyApp
Delete $INSTDIR\config.dat
Delete $INSTDIR\MyApp.exe
Delete $INSTDIR\ReleaseNotes.txt
Delete $INSTDIR\MyCompany_LandingPage_114.bmp
Delete $INSTDIR\MyAppLicense.txt
Delete "$SMPROGRAMS\MyApp.lnk"
DeleteRegKey HKCU "SOFTWARE${PRODUCT_NAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
# now delete installed files and registry keys for FlexLM
Delete $INSTDIR\installs.exe
Delete $INSTDIR\lmborrow.exe
Delete $INSTDIR\lmflex.exe
# Delete the MyApp and FlexLM directories
RMDir $INSTDIR
SectionEnd
!endif
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## Also if no sections are selected, warn the user!
Function ComponentsLeave
Push $R0
Push $R1
Call IsPROG1Selected
Pop $R0
Call IsPROG2Selected
Pop $R1
StrCmp $R0 1 End
StrCmp $R1 1 End
Pop $R1
Pop $R0
MessageBox MB_OK|MB_ICONEXCLAMATION "$(NoSectionsSelected)"
Abort
End:
Pop $R1
Pop $R0
FunctionEnd
Function IsPROG1Selected
Push $R0
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
SectionGetFlags 0 $R0 # Get section flags
IntOp $R0 $R0 & ${SF_SELECTED}
StrCmp $R0 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R0 1
Exch $R0
FunctionEnd
Function IsPROG2Selected
Push $R1
StrCpy $R1 ${PROG2_StartIndex} # Group 2 start
IntOp $R1 $R1 + 1
SectionGetFlags 1 $R1 # Get section flags
IntOp $R1 $R1 & ${SF_SELECTED}
StrCmp $R1 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R1 1
Exch $R1
FunctionEnd
## Here we are selecting first sections to install
## by unselecting all the others!
Function SelectFilesA
${If} ${SectionIsSelected} ${SEC1}
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
# If user clicks Back now, we will know to reselect Group 2`s sections for
# Components page
StrCpy $IfBack 1
# We need to save the state of the Group 2 Sections
# for the next InstFiles page
Push $R0
Push $R1
StrCpy $R0 ${PROG2_StartIndex} # Group 2 start
; Loop:
; IntOp $R0 $R0 + 1
; SectionGetFlags $R0 $R1 # Get section flags
; WriteINIStr "$PLUGINSDIR\sections.ini" Sections $R0 $R1 # Save state
; !insertmacro UnselectSection $R0 # Then unselect it
; StrCmp $R0 ${PROG2_EndIndex} 0 Loop
# Don`t install prog 1?
Call IsPROG1Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG1_InstDir define
StrCpy $INSTDIR "${PROG1_InstDir}"
Pop $R1
Pop $R0
FunctionEnd
## Here we need to unselect all Group 1 sections
## and then re-select those in Group 2 (that the user had selected on
## Components page)
Function SelectFilesB
${If} ${SectionIsSelected} ${SEC3}
;MessageBox MB_OK "SEC1 #3 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
Push $R0
;Push $R1
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
; Loop:
; IntOp $R0 $R0 + 1
; !insertmacro UnselectSection $R0 # Unselect it
; StrCmp $R0 ${PROG1_EndIndex} 0 Loop
; Call ResetFiles
# Don't install prog 2?
Call IsPROG2Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG2_InstDir define
StrCpy $INSTDIR "${PROG2_InstDir}"
;Pop $R1
Pop $R0
FunctionEnd
有人有什么建议吗?
更新:
谢谢@Anders,我不知何故误删了SelectFilesCheck
。现在,如果我使用此代码:
!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. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SMyApp-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
Section "MyApp" SEC1
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=11=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=11=]"
StrCmp [=11=] "" notfoundMyApp
StrCpy $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} [=11=] "Flex" "$INSTDIR"
StrCmp [=11=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
然后我得到这些错误:
!makensis: returned 0
!system: "set __COMPAT_LAYER=RunAsInvoker&"C:\Users\moorer\AppData\Local\Temp\tempinstaller.exe""
!system: returned 2
Error: Can't add entry, no section or function is open!
Error in script "C:\Project\MsiPackaging\Installer\MyAppInstaller.nsi" on line 169 -- aborting creation process
但是如果我尝试将其放在这样的部分中:
;--------------------------------
; Start sections
Section "MyApp" SEC1
!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. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SQI-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} [=13=] "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is [=13=]"
StrCmp [=13=] "" notfoundQI
StrCpy $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
Goto doneQI
notfoundQI:
;MessageBox MB_OK 'Did not find QI string'
doneQI:
${StrContains} [=13=] "Flex" "$INSTDIR"
StrCmp [=13=] "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS${PRODUCT_NAME}.lnk" "$InstDir${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64${PRODUCT_NAME}${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"AppID" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
doneFlex:
# messagebox mb_ok sec1
SectionEnd
然后我得到这个错误:
Error: command OutFile not valid in Section
Error in script "MyAppInstaller.nsi" on line 157 -- aborting creation process
!makensis: returned 1, aborting
Error in script "C:\Projects\MsiPackaging\Installer\MyAppInstaller.nsi" on line 165 -- aborting creation process
你有什么建议吗? TIA.
来自 Wiki 的签名代码根本没有使用 StrCpy
,它必须来自您的 StrCpy $IfBack 1
行。也许你忘记了Var IfBack
上面的函数。
code from the wiki 应该可以,但这里有一个替代版本:
#TODO: RequestExecutionLevel
#TODO: InstallDir
!include MUI2.nsh
!macro WriteSignedUninstaller Destination
!makensis '"/DGENRATINGUNINST=$%TEMP%\Uninst.exe" "${__FILE__}" "/XOutfile `$%TEMP%\tempinstaller.exe`"' = 0 ; Create fake installer
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2 ; Run fake installer to generate the uninstaller
!system 'SIGNCODE <signing options> "$%TEMP%\Uninst.exe"' = 0 ; Change this line. As a demonstration, use !system 'echo Dummy >> "$%TEMP%\Uninst.exe"'
File "/oname=${Destination}" "$%TEMP%\Uninst.exe"
!macroend
!macro DeclareLanguages
!insertmacro MUI_LANGUAGE English
!macroend
!ifndef GENRATINGUNINST
# Installer:
############
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro DeclareLanguages
Section
SetOutPath $InstDir
!insertmacro WriteSignedUninstaller "$InstDir\Uninst.exe"
; File MyApp.exe
SectionEnd
!else
# Uninstaller:
##############
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro DeclareLanguages
!verbose push 2
SilentInstall Silent
Section
WriteUninstaller "${GENRATINGUNINST}"
Quit
SectionEnd
!verbose pop
Section -Uninstall
; Delete "$InstDir\MyApp.exe"
Delete "$InstDir\Uninst.exe"
RMDir "$InstDir"
SectionEnd
!endif # EOF
感谢您的反馈,@Anders。最后,我找到了这个 SelfDel
插件,它允许我在删除卸载程序的同时使用单独的安装程序和卸载程序脚本:
https://nsis.sourceforge.io/SelfDel_plug-in
Name `SelfDel plug-in example`
OutFile Example.exe
XPStyle on
; Administrator privileges required.
RequestExecutionLevel admin
Page InstFiles
Section
;SelfDel::Del /RMDIR
;SelfDel::Del /REBOOT
;SelfDel::Del /SHUTDOWN
SelfDel::Del
SetAutoClose true
SectionEnd
更新:
当我使用 SelfDel
时,它会删除 uninstaller.exe
但不会删除原始应用程序目录,即使我使用 SelfDel::Del /RMDIR
所以我最终选择了 Anders以上回答。