How to save log during uninstallation using NSIS - nsis

I'm trying to log the uninstallation process, especially the files and folders deleted by rmdir for debug. However, LogSet On and DumpLog function seems not working. Does anyone knows if there is a way to do it?
Edit:
I'm using DumpLog like:
Section "Uninstall"
;ADD YOUR OWN FILES HERE...
LogSet On
DetailPrint "this is an uninstall test"
Delete "$INSTDIR\Uninstall.exe"
DeleteRegKey /ifempty HKCU "Software\Test"
DetailPrint "dumplog section"
StrCpy $0 "$INSTDIR\install.log"
Push $0
Call un.DumpLog
SectionEnd
Function un.DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 exit
FileOpen $5 $5 a
StrCmp $5 "" exit
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Alloc ${NSIS_MAX_STRLEN}
Pop $3
StrCpy $2 0
System::Call "*(i, i, i, i, i, i, i, i, i) i \
(0, 0, 0, 0, 0, r3, ${NSIS_MAX_STRLEN}) .r1"
loop: StrCmp $2 $6 done
System::Call "User32::SendMessageA(i, i, i, i) i \
($0, ${LVM_GETITEMTEXT}, $2, r1)"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWrite $5 "$4$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
exit:
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
More Edit:
Things are getting more complex.
I put the structure like:
-InstDir
-UnInstDir
-Uninstaller.exe
When I run uninstaller or use execwait $InstDir\UnInstDir\Uninstall.exe, LogSet On works and a new install.log was created in UnInstDir.
However, when I use execwait $InstDir\UnInstDir\Uninstall.exe _?=$InstDir, LogSet On doesn't work.
Anyone has any clue on it?

LogSet requires that you use the logging build of NSIS. DumpLog only writes the same info as you see on the InstFiles page.
Another alternative is to track the files as you install them.
The final option is to use !system to execute a script (batch file etc.) that generates two .nsh files (one for the installer, one for the uninstaller) with File and Delete instructions based on the output of dir /s.

Related

How to Use the NSIS Unzip Plugin properly?

Im new to NSIS programming, so i found the NSISUNZ plugin to extract files.
This is my Code:
OutFile "TEst.exe"
Section
!addplugindir nsisunz
initPluginsDir
nsisunz::Unzip "C:\Users\user\Downloads\TestVerzeichnis.zip" "C:\Users\user\Downloads"
SectionEnd
I do not get an Error or something but the file does not get extracted.
For the installation of the plugin I just extracted the .dll file into the plugins folder.
What am I doing wrong here?
Thanks for answering
If the output is just a single letter then you most likely using the Unicode version of the plug-in in a Ansi installer.
Ideally you should create a Unicode installer with NSIS v3:
Move the plug-in to the Unicode plug-in subfolder inside the root plugins folder.
Add Unicode True to your .NSI.
If you are still using NSIS v2 then you need to find a different version of the plug-in, most likely the other file on the wiki.
Unicode True
!addplugindir /x86-unicode "$%userprofile%\Downloads\Nsisunz\Plugin unicode"
!include LogicLib.nsh
Function SplitWrite
Pop $2
StrCpy $3 ""
StrCpy $4 0
loop:
StrCpy $5 $2 1 $4
${If} $5 == "|"
${OrIf} $5 == ""
IntOp $6 $4 - 2
StrCpy $6 $2 2 $6
FileWriteByte $1 "0x$6"
${EndIf}
IntOp $4 $4 + 1
StrCmp $5 "" 0 loop
FunctionEnd
Section
InitPluginsDir
; Create a example .zip file
FileOpen $1 "$PluginsDir\test.zip" w
Push 50|4B|03|04|0A|00|00|00|00|00|AC|BA|93|50
Call SplitWrite
Push F8|06|53|6B|08|00|00|00|08|00|00|00|08|00|00|00|54|65|73|74|2E|74|78|74|48|65|6C|6C|6F|20|0D|0A|50|4B|01|02|3F|00|0A|00|00|00|00|00|AC|BA|93|50
Call SplitWrite
Push F8|06|53|6B|08|00|00|00|08|00|00|00|08|00|24|00|00|00|00|00|00|00|20|20|00|00|00|00|00|00|54|65|73|74|2E|74|78|74|0A|00|20|00|00|00|00|00|01|00|18|00
Call SplitWrite
Push D6|75|96|79|90|16|D6|01|96|4F|96|79|90|16|D6|01|96|4F|96|79|90|16|D6|01|50|4B|05|06|00|00|00|00|01|00|01|00|5A|00|00|00|2E|00|00|00|00|00
Call SplitWrite
FileClose $1
CreateDirectory "$PluginsDir\TestDir"
nsisunz::Unzip "$PluginsDir\test.zip" "$PluginsDir\TestDir"
Pop $0
DetailPrint $0 ; "success"
${If} $0 == "success"
FileOpen $1 "$PluginsDir\TestDir\Test.txt" r
FileRead $1 $2
FileClose $1
DetailPrint $2 ; "Hello"
${EndIf}
SectionEnd

NSIS install path validation

I want to validate the installation path selected by the user. I can't figure out how to check that so it will look like this:
You can't select a path with spaces (except Program Files)
When you click "Install" then it will prompt the error saying that you have to change the installation directory
For now I have this:
Function StrStr
Exch $1 ; st=haystack,old$1, $1=needle
Exch ; st=old$1,haystack
Exch $2 ; st=old$1,old$2, $2=haystack
Push $3
Push $4
Push $5
StrLen $3 $1
StrCpy $4 0
; $1=needle
; $2=haystack
; $3=len(needle)
; $4=cnt
; $5=tmp
loop:
StrCpy $5 $2 $3 $4
StrCmp $5 $1 done
StrCmp $5 "" done
IntOp $4 $4 + 1
Goto loop
done:
StrCpy $1 $2 "" $4
Pop $5
Pop $4
Pop $3
Pop $2
Exch $1
FunctionEnd
Function .onVerifyInstDir
Push "$INSTDIR"
Push " "
Call StrStr
Pop $0
StrCpy $0 $0 1
StrCmp $0 " " 0 +2
Abort
FunctionEnd
It refuses to install while there is any space in the path. I need to modify this so Program Files will be the only exception for that rule. Also, printing error message would be helpful
This restriction makes no sense to me. Some legacy applications can't handle spaces in the path but that of course also includes the Program Files folder (although the progra~1 hack can be used as a workaround if short name generation is active).
NSIS does not have a specific way to display a error/warning message directly on the page but you can change existing text in the UI and/or display a balloon.
!include WinMessages.nsh
!define /IfNDef EM_SHOWBALLOONTIP 0x1503
!define /IfNDef EM_HIDEBALLOONTIP 0x1504
!define DIRPAGE_CHANGETEXT ; Remove this line to disable the text change
!define DIRPAGE_BALLOON ; Remove this line to disable the balloon
Function .onVerifyInstDir
FindWindow $9 "#32770" "" $HWNDPARENT
!ifdef DIRPAGE_CHANGETEXT
GetDlgItem $3 $9 1006 ; IDC_INTROTEXT
LockWindow on
!endif
StrCpy $1 0
loop:
StrCpy $2 $InstDir 1 $1
StrCmp $2 '' valid ; End of string
StrCmp $2 ' ' found_space
IntOp $1 $1 + 1
Goto loop
valid:
!ifdef DIRPAGE_CHANGETEXT
SetCtlColors $3 SYSCLR:18 SYSCLR:15
SendMessage $3 ${WM_SETTEXT} "" "STR:$(^DirText)"
LockWindow off
!endif
!ifdef DIRPAGE_BALLOON
GetDlgItem $3 $9 1019
SendMessage $3 ${EM_HIDEBALLOONTIP} "" "" ; Not required?
!endif
Return
found_space:
StrLen $1 "$ProgramFiles\"
StrCpy $2 "$InstDir\" $1
StrCmp $2 "$ProgramFiles\" valid
!ifdef DIRPAGE_CHANGETEXT
SetCtlColors $3 ff0000 transparent
SendMessage $3 ${WM_SETTEXT} "" "STR:Paths with spaces are not allowed, except for $ProgramFiles for some reason!"
LockWindow off
!endif
!ifdef DIRPAGE_BALLOON
GetDlgItem $3 $9 1019
System::Call '*(&l${NSIS_PTR_SIZE},w "Bad path!", w "Spaced not allowed in path!",p 3)p.r2'
SendMessage $3 ${EM_SHOWBALLOONTIP} "" $2 ; This will only work on XP and later (and you must use "XPStyle on")
System::Free $2
!endif
Abort
FunctionEnd
XPStyle on
Page Directory
The Next button is disabled when Abort is called inside .onVerifyInstDir. If you want to display a MessageBox when the user clicks next then you can't call Abort in .onVerifyInstDir, you will have to use the page leave function callback (where you have to verify the path again and maybe call MessageBox+Abort).

NSIS Get Product Version solution failing

I have been trying to use the system calls GetFileVersionInfo and VerQueryValue to get the product version of an exe. I am using a legacy NSIS v2.0b3 (lots of scripts already in use and just wanting to make one little change).
After searching for a while I saw this solution
Product version string from an exe - nsis
...but am having problems getting it to work sensibly.
The main call seems to work... ie
System::Call 'VERSION::GetFileVersionInfo(tr3,i,ir4,ir5)i.r0'
MessageBox MB_OK "GetFileVersionInfo returned dwLen=[$4] and lpData=[$5] for the block of Version Info"
...shows a sensible ptr in $5.
The next call is where things go wrong...
System::Call 'VERSION::VerQueryValue(ir5,t"\",*i.r6,*i.r7)i.r0'
StrCmp $0 0 fail
MessageBox MB_OK "VS_FIXEDFILEINFO returned as lplpBuffer=[$6] and PUINT=[$7]"
This call returns 0,0 for $6 and $7. And then of course the parsing fails...
;;---Parse buffer at $6 (lplp)
System::Call '*$6(i,i,i,i,i.r2,i.r1)'
MessageBox MB_OK "Read data from struct #$6: skip 4 ints then ints are dwProductVersionMS:[$2] dwProductVersionLS:[$1]"
...returns 0,0.
I'm thinking the problem is the indirect pointer in $6 here.
That is, $6 is type
LPVOID *lplpBuffer
....so I think the syntax of the call to set the value of $6 may need to be different.
Any help welcome... I tried some variations without success.
===Following request posted, here is the latest of many variations I have tried... hopefully that will help clarify what I am doing===
Function GetDllProductVersion
; https://stackoverflow.com/questions/34616470/nsis-get-product-version?rq=1
; https://stackoverflow.com/questions/38707235/product-version-string-from-an-exe-nsis slightly different System::Call's, but also later nsis not compatible
;;System::Store S ;;;removed this and the matching Store L, as that crashes
Pop $3
;; System::Call 'VERSION::GetFileVersionInfoSize(tr3,*i)i.r4'
;; MessageBox MB_OK "GetFileVersionInfoSize gets size [$4]" ; cannot get a sensible answer, returns "error" in $4
;;---allocate block, address into $5
StrCpy $4 0
IntOp $4 $4 + 10000 ; set $4 to 10000
System::Call '*(&i$4,t""r1,t""r2)i.r5' ; Set $1 and $2 to "" so they are empty if we fail
MessageBox MB_OK "System::Call allocs [$4] bytes at addr [$5], next call GetFileVersionInfo"
StrCmp $4 0 fail
StrCmp $5 0 fail
;;---GetFileVersionInfo now-----
System::Call 'VERSION::GetFileVersionInfo(tr3,i,ir4,ir5)i.r0' ;; ir5 not isr5 ?? diff between solutions
StrCmp $0 0 fail
MessageBox MB_OK "GetFileVersionInfo returned dwLen=[$4] and lpData=[$5] for the block of Version Info"
;;---Now we get the VS_FIXEDFILEINFO structure using $5.... $6 will be lplpBuffer for it and $7 will be PUINT ptr to size of data in lplpBuffer
System::Call 'VERSION::VerQueryValue(ir5,t"\",*i.r6,*i.r7)i.r0' ;; using &i.r6 etc, not *i.r6 gives 0,0 no good, go back to *
StrCmp $0 0 fail
MessageBox MB_OK "VS_FIXEDFILEINFO returned as lplpBuffer=[$6] and PUINT=[$7]"
;;---Parse buffer at $6 (lplp)
System::Call '**$6(i,i,i,i,i.r2,i.r1)'
MessageBox MB_OK "Read data from struct #$6: skip 4 ints then ints are dwProductVersionMS:[$2] dwProductVersionLS:[$1]"
;;;or?????
System::Call '**$6(i,i,i,i,&i.r2,&i.r1)'
MessageBox MB_OK "Read data using & from struct #$6: skip 4 ints then ints are dwProductVersionMS:[$2] dwProductVersionLS:[$1]"
fail:
System::Free $5
MessageBox MB_OK "After System::Free [$5]"
Push $1
Push $2
;;System::Store L ;;;this crashes!!! so push and pop indiv registers used
FunctionEnd
I can't explain why System::Store crashes, it is documented to work even in v2.0b3. Then again, you are using 15 year old beta software so you can't expect everything to work correctly. Could be related to bug #1620178 which was fixed in v2.23 (11 years ago).
Your main issue is that v2.0b3 does not automatically support function names suffixed with A (most functions that take/return a string). It seems like support for this was added in v2.0b4.
You can modify the code you found to be compatible by hardcoding the suffix:
Function GetDllProductVersion
Exch $3
Push $1
Push $2
Exch 2
Push $4
Push $5
Push $6
Push $7
Push $0
System::Call 'VERSION::GetFileVersionInfoSizeA(tr3,*i)i.r4'
System::Call '*(&i$4,t""r1,t""r2)i.r5' ; Set $1 and $2 to "" so they are empty if we fail
StrCmp $4 0 fail
StrCmp $5 0 fail
System::Call 'VERSION::GetFileVersionInfoA(tr3,i,ir4,ir5)i.r0'
StrCmp $0 0 fail
System::Call 'VERSION::VerQueryValueA(ir5,t"\",*i.r6,*i.r7)i.r0'
StrCmp $0 0 fail
System::Call '*$6(i,i,i,i,i.r2,i.r1)'
fail:
System::Free $5
Pop $0
Pop $7
Pop $6
Pop $5
Pop $4
Pop $3
Exch $1
Exch
Exch $2
FunctionEnd
Section
!define DllName "c:\windows\system32\ComCtl32.dll"
Push "${DllName}"
Call GetDllProductVersion
Pop $R0
Pop $R1
IntOp $R2 $R0 / 0x00010000
IntOp $R3 $R0 & 0x0000FFFF
IntOp $R4 $R1 / 0x00010000
IntOp $R5 $R1 & 0x0000FFFF
DetailPrint 'ProdVer: $R2.$R3.$R4.$R5'
SectionEnd
but I would strongly recommend that you upgrade to a more recent version. v2.51 as a minimum to get all security fixes.

How do I redirect the output of commands to file in silent mode in NSIS?

In GUI mode, commands like CopyFiles , Delete , etc. output their data to GUI (may be using DetailPrint) and their is a function available on NSIS forums to copy that data (at the end of section) to a file.
Queries:
If the installer is being run in silent mode, how do I get the same data (which was being directed to GUI in non-silent mode) to the file?
In GUI mode, since I am directing custom logs through DetailPrint to the log file with the help of function so that all the logs are received in order. Here the issue is that line breaks are removed from the custom logs. May be DetailPrint removes it. How shall I avoid this?
Example:
DetailPrint "This is a custom log1"
DetailPrint "$\r$\nThis is a custom log2"
/*
Dumped these logs using function mentioned above
Output in logs(with no line breaks):
This is a custom log1
This is a custom log2
Required output:
This is a custom log1
This is a custom log2
*/
Your Second query is Solved.
!include "MUI2.nsh"
section
StrCpy $0 "$EXEDIR\install.log"
Push $0
DetailPrint "This is a custom log1"
DetailPrint "This is a custom log2"
Call DumpLog
sectionend
;!define LVM_GETITEMCOUNT 0x1004
!define LVM_GETITEMTEXT 0x102D
Function DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 exit
FileOpen $5 $5 "w"
StrCmp $5 "" exit
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Alloc ${NSIS_MAX_STRLEN}
Pop $3
StrCpy $2 0
System::Call "*(i, i, i, i, i, i, i, i, i) i \
(0, 0, 0, 0, 0, r3, ${NSIS_MAX_STRLEN}) .r1"
loop: StrCmp $2 $6 done
System::Call "User32::SendMessageA(i, i, i, i) i \
($0, ${LVM_GETITEMTEXT}, $2, r1)"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWrite $5 "$4$\r$\n"
FileWrite $5 "$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
exit:
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
Your sample code is works for me. Its gives me output as your requirement.
Dont put "$\r$\n" in Detailprint. Add FileWrite $5 "$\r$\n" in DumpLog function as i did. By this you will not have to put $\r$\n in each detail print.

Can NSIS DumpLog be used with 64 bit version?

I'm using the 64 bit build of NSIS and everything works except the DumpLog function to write the install log to a file.
(NSIS 64 is here: https://bitbucket.org/dgolub/nsis64)
DumpLog uses Windows messages to get the text value and it seems the calls are 32 bit only.
Here's an example of the function:
https://svn.xiph.org/trunk/oggdsf/build/NSIS/Release/extra/DumpLog.nsh
It defines these which are incorrect for 64 bit:
!define LVM_GETITEMCOUNT 0x1004
!define LVM_GETITEMTEXT 0x1073
I found the 64 bit version of LVM_GETITEMCOUNT which is 0x00001004.
Has anyone got this function to with 64 bit?
The bitbucket port does not support calling arbitrary functions with System::Call and that DumpLog function is also not 64 bit compatible because it uses the i type when p is required and there are some additional padding issues.
Here is a 64 bit compatible version:
!define LVM_GETITEMCOUNT 0x1004
!define LVM_GETITEMTEXT 0x1073
Function DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
Push $7
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 error
FileOpen $5 $5 "w"
FileWriteWord $5 0xfeff ; Write the BOM
StrCmp $5 0 error
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Call "*(&t${NSIS_MAX_STRLEN})p.r3"
System::Call "*(i0,i0,i0,i0,&i${NSIS_PTR_SIZE} 0,p$3,i${NSIS_MAX_STRLEN},i0,p0)p.r1" ; NSIS_PTR_SIZE is used to align the pszText member on x64
StrCpy $2 0
loop: StrCmp $2 $6 done
System::Call "User32::SendMessage(p$0,i${LVM_GETITEMTEXT},p$2,pr1)p"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWriteUTF16LE $5 "$4$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
Goto exit
error:
MessageBox MB_OK|MB_ICONSTOP "Error at DumpLog"
exit:
Pop $7
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
You need a version of System.dll that is able to call arbitrary functions and I'm afraid the only way to get that is to compile the official SVN trunk as 64 bit.
(Since StackOverflow does not support file uploads you can rename this image to .zip and open it in 7Zip)

Resources