贾可同学写的,安装时记录释放文件日志,卸载时根据日志记录进行卸载,有效防止误删文件。完整代码如下:
!define nsAppSetup "App_20210916_x86.exe"
!define nsAppName "App"
!define nsAppDesc "${nsAppName} 1.0"
!define nsAppId "{03D9BE5D-2411-425F-96C7-24246D4D58FC}"
!define nsAppVersion "1.0.0.0"
!define nsAppUrl "http://www.example.cn/"
!define nsAppPublisher "Example Inc."
!define nsAppCompany "Example"
!define NS_UNINST_ROOT_KEY "HKLM"
!define NS_UNINST_INFO_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${nsAppId}"
!define NS_UNINST_MASK_DIR 0x8000
!define NS_UNINST_MASK_LEN 0x7fff
!include LogicLib.nsh
!include WinCore.nsh
SetCompressor lzma
SetCompressorDictSize 32
Name "${nsAppName}"
OutFile "${nsAppSetup}"
#Icon ".\install.ico"
#UninstallIcon ".\uninstall.ico"
InstallDirRegKey ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "InstallLocation"
InstallDir "$PROGRAMFILES\${nsAppCompany}\${nsAppName}"
RequestExecutionLevel admin
AutoCloseWindow false
ShowInstDetails show
ShowUninstDetails show
InstallColors /windows
XPStyle on
BrandingText "${nsAppUrl}"
#ChangeUI all ".\example.exe"
Page directory
Page instfiles
UninstPage uninstConfirm
UninstPage instfiles
LoadLanguageFile "${NSISDIR}\Contrib\Language files\SimpChinese.nlf"
LoadLanguageFile "${NSISDIR}\Contrib\Language files\TradChinese.nlf"
LoadLanguageFile "${NSISDIR}\Contrib\Language files\English.nlf"
LangString MSG_FILE_NAME_INFO ${LANG_SIMPCHINESE} "文件"
LangString MSG_FILE_NOT_FOUND ${LANG_SIMPCHINESE} "不存在!"
LangString MSG_FILE_CORRUPTED ${LANG_SIMPCHINESE} "已损坏!"
LangString MSG_FILE_NAME_INFO ${LANG_TRADCHINESE} "檔案"
LangString MSG_FILE_NOT_FOUND ${LANG_TRADCHINESE} "不存在!"
LangString MSG_FILE_CORRUPTED ${LANG_TRADCHINESE} "已損毀!"
LangString MSG_FILE_NAME_INFO ${LANG_ENGLISH} "The file"
LangString MSG_FILE_NOT_FOUND ${LANG_ENGLISH} "doesn't exist!"
LangString MSG_FILE_CORRUPTED ${LANG_ENGLISH} "is corrupted!"
VIProductVersion "${nsAppVersion}"
VIAddVersionKey "FileDescription" "${nsAppDesc}"
VIAddVersionKey "FileVersion" "${nsAppVersion}"
VIAddVersionKey "ProductName" "${nsAppName}"
VIAddVersionKey "ProductVersion" "${nsAppVersion}"
VIAddVersionKey "LegalCopyright" "${nsAppPublisher}"
VIAddVersionKey "CompanyName" "${nsAppCompany}"
Section "-Install" SEC_01
SetOutPath $INSTDIR
File "$%WINDIR%\notepad.exe"
File "$%WINDIR%\regedit.exe"
SetOutPath $INSTDIR\dir1
File "$%WINDIR%\notepad.exe"
File "$%WINDIR%\regedit.exe"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "DisplayIcon" "$INSTDIR\uninst.exe"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "DisplayName" "${nsAppDesc}"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "DisplayVersion" "${nsAppVersion}"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "UninstallString" '"$INSTDIR\uninst.exe"'
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "Publisher" "${nsAppPublisher}"
WriteRegStr ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "HelpLink" "${nsAppUrl}"
SectionGetSize ${SEC_01} $R0
WriteRegDWORD ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "EstimatedSize" "$R0"
WriteRegDWORD ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "NoModify" "1"
WriteRegDWORD ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}" "NoRepair" "1"
; 创建卸载程序,日志要记录卸载程序的修改时间供校验。
WriteUninstaller "$INSTDIR\uninst.exe"
GetTempFileName $R0
; 生成一个临时文件作为日志,要不然会把日志文件自己的路径也记录进去。
FileOpen $R1 $R0 w
${IfNot} ${Errors}
GetFileTime "$INSTDIR\uninst.exe" $1 $2
;
; 我们在这里写一个 UTF-8 文件头,主要是为了防止一般用户随意修改此文件。
; 但实际上是二进制格式,包含一些二进制数据和路径,路径以 UTF-16 写入。
; 这样 Windows 记事本打开后默认以 UTF-8 解析,会无法正常显示文本。
;
FileWriteByte $R1 0xef
FileWriteByte $R1 0xbb
FileWriteByte $R1 0xbf
;
; 然后我们再在这里写一些字节 (FileWriteByte),或字符串 (FileWrite),比如公司名称之类的。
; 请尽量保证这些字节和 UTF-8 头(三字节) 加起来是 4 的倍数,不够可以加空格。
;
; 写一些 byte 数据:
; FileWriteByte $R1 0x6e ; 'N'
; FileWriteByte $R1 0x73 ; 'S'
;
; 写字符串:
; 注意这里就用三个空格凑满 17 个字节 (如果还写入了 byte 数据,应该计算在内),包含文件头一共凑到 20 字节。
FileWrite $R1 "Nullsoft, Inc. "
; 写入卸载程序的修改时间
IntOp $0 $1 >> 16
FileWriteWord $R1 $0
IntOp $0 $1 & 0xffff
FileWriteWord $R1 $0
IntOp $0 $2 >> 16
FileWriteWord $R1 $0
IntOp $0 $2 & 0xffff
FileWriteWord $R1 $0
; 开始写入文件夹和文件信息
Push $INSTDIR # 路径
Push $R1 # 日志
Call LoggerWriter
FileClose $R1
; 将生成的临时日志文件移动到安装目录
SetDetailsPrint none
Delete "$INSTDIR\uninst.dat"
Rename $R0 "$INSTDIR\uninst.dat"
SetDetailsPrint lastused
${EndIf}
SectionEnd
Section Uninstall
StrCpy $R0 "$INSTDIR\uninst.dat"
ClearErrors
FileOpen $R1 $R0 r
${IfNot} ${Errors}
; 由于 un.onInit 中已经判断,这里直接跳过。
; 也可以使用一次性跳过
FileReadByte $R1 $0
FileReadByte $R1 $0
FileReadByte $R1 $0
; FileReadByte $R1 $0
; FileReadByte $R1 $0
; 注意这里长度
FileRead $R1 $0 17
; 跳过卸载程序日期
FileReadWord $R1 $0
FileReadWord $R1 $0
FileReadWord $R1 $0
FileReadWord $R1 $0
; 开始根据日志卸载
Push $R1
Call un.LoggerDeleter
FileClose $R1
DeleteRegKey ${NS_UNINST_ROOT_KEY} "${NS_UNINST_INFO_KEY}"
; 删除日志自身...
Delete "$INSTDIR\uninst.dat"
; 删除安装目录
RMDir "$INSTDIR"
${EndIf}
SectionEnd
;
; 这个是递归函数,会遍历文件夹中的所有文件,并将路径写入日志中。
; 如果遇到文件夹,则先进入遍历文件,最后再退出并记录文件夹自身路径,符合卸载流程。
;
Function LoggerWriter
Exch $R0
Exch
Exch $R1
Push $R2
Push $R3
Push $R4
FindFirst $R3 $R2 "$R1\*.*"
lbl_loop:
StrCmp $R2 ".." lbl_skip
StrCmp $R2 "." lbl_skip
StrCmp $R2 "" lbl_done
StrLen $R4 "$R1\$R2"
IfFileExists "$R1\$R2\*.*" 0 lbl_write
IntOp $R4 $R4 | ${NS_UNINST_MASK_DIR}
Push "$R1\$R2"
Push "$R0"
Call LoggerWriter
lbl_write:
; $R4 是 16 位整数,最高位代表文件夹标志,其余位为路径长度,将其有意地取反再写入到日志中。
IntOp $R4 $R4 ~
; 写入标志
FileWriteWord $R0 $R4
; 写入路径
FileWriteUTF16LE $R0 "$R1\$R2"
lbl_skip:
FindNext $R3 $R2
Goto lbl_loop
lbl_done:
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Pop $R0
FunctionEnd
Function un.LoggerDeleter
Exch $R0
Push $R1
Push $R2
Push $R3
Push $R4
lbl_loop:
; 读取一个 16 位整数
FileReadWord $R0 $R1
IntCmp $R1 0 lbl_done
; 由于之前已经取反,现在取反以恢复正确数据。
IntOp $R1 $R1 ~
; 得到长度信息
IntOp $R3 $R1 & ${NS_UNINST_MASK_LEN}
; 得到文件夹标记
IntOp $R4 $R1 & ${NS_UNINST_MASK_DIR}
; 根据长度读取路径字符串
FileReadUTF16LE $R0 $R2 $R3
; 根据文件夹标记执行对应的删除操作
IfFileExists $R2 0 lbl_done
IntOp $R4 $R4 !
IntCmp $R4 0 lbl_path
DetailPrint `Delete $R2`
Delete $R2
Goto lbl_loop
lbl_path:
DetailPrint `RMDir $R2`
RMDir $R2
Goto lbl_loop
lbl_done:
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Pop $R0
FunctionEnd
Function un.onInit
InitPluginsDir
StrCpy $R0 "$INSTDIR\uninst.dat"
ClearErrors
FileOpen $R1 $R0 r
IfErrors 0 lbl_read
MessageBox MB_OK|MB_ICONINFORMATION `$(MSG_FILE_NAME_INFO) "$R0" $(MSG_FILE_NOT_FOUND)`
Abort
lbl_read:
GetFileTime "$INSTDIR\uninst.exe" $1 $2
FileReadByte $R1 $R2
FileReadByte $R1 $R3
FileReadByte $R1 $R4
; 读取之前写入的字节以供后续校验
; FileReadByte $R1 $R5
; FileReadByte $R1 $R6
; 读取之前写入的字符串以供后续校验
; 注意必须指定正确长度(这里是17)
FileRead $R1 $R7 17
FileReadWord $R1 $0
IntOp $R8 $0 << 16
FileReadWord $R1 $0
IntOp $R8 $R8 | $0
FileReadWord $R1 $0
IntOp $R9 $0 << 16
FileReadWord $R1 $0
IntOp $R9 $R9 | $0
FileClose $R1
; 验证卸载程序的修改时间是否正确
IntOp $0 $R9 ^ $2
StrCmp $0 0 0 lbl_error
IntOp $0 $R8 ^ $1
StrCmp $0 0 0 lbl_error
StrCmpS $R7 "Nullsoft, Inc. " 0 lbl_error
; IntOp $0 $R6 ^ 0x73
; StrCmp $0 0 0 lbl_error
; IntOp $0 $R5 ^ 0x6e
; StrCmp $0 0 0 lbl_error
IntOp $0 $R4 ^ 0xbf
StrCmp $0 0 0 lbl_error
IntOp $0 $R3 ^ 0xbb
StrCmp $0 0 0 lbl_error
IntOp $0 $R2 ^ 0xef
StrCmp $0 0 lbl_done
lbl_error:
MessageBox MB_OK|MB_ICONINFORMATION `$(MSG_FILE_NAME_INFO) "$R0" $(MSG_FILE_CORRUPTED)`
Abort
lbl_done:
FunctionEnd
评论 (0)