How to save DetailPrint command message in file in NSIS Script? - deployment

Idea is to run a NSIS script in silent mode to a remote machine and once installation is successfully done than get the log file back to host machine.
I have lots of Detailprint command messages that show the progress of the script. Now question is how am i gonna save these messages to the log file. I was looking into this http://nsis.sourceforge.net/Dump_log_to_file but it says it wont work in silent mode.

I use this method to save logs. I don't know if this the best method but it works well for me.
!ifndef DEBUG_MODE
!define DEBUG_MODE true
!endif
!define LOGFILE "$TEMP\my_logfile.log"
!if ${DEBUG_MODE} == true
!define DetailPrint '!insertmacro _debugMsg'
!else
!define DetailPrint '!insertmacro _nodebugMsg'
!endif
!macro _debugMsg MSG
push $3
push $2
push $1
push $0
push $R0
strcpy $R0 "${MSG}"
ClearErrors
FileOpen $1 ${LOGFILE} a
FileSeek $1 0 END
IfErrors +8
${GetTime} "" "L" $0 $0 $0 $0 $0 $2 $3
FileWrite $1 "$0:$2:$3$\t"
FileWrite $1 "${__FUNCTION__}$\t"
FileWrite $1 "$R0"
FileWrite $1 "$\t(${__FILE__},${__FUNCTION__},${__LINE__})"
FileWrite $1 "$\n"
FileClose $1
pop $R0
pop $0
pop $1
pop $2
pop $3
!macroend
!macro _nodebugMsg _MSG
;you can put here nothing or you can put the regular DetailPrint
DetailPrint "${MSG}"
!macroend
After that you just have to find and replace DetailPrint with ${DetailPrint}

That is correct, the listview window does not exist in silent mode.
I guess you have two options, roll your own logging:
var hFileLog
!macro Log_Init logfile
FileOpen $hFileLog "${logfile}" w ;Or "a" for append
!macroend
!macro Log_String msg
DetailPrint "${msg}"
FileWrite $hFileLog "${msg}$\r$\n"
!macroend
!macro Log_Close
FileWrite $hFileLog 'Done.'
FileClose $hFileLog
!macroend
!macro File src
File "${src}"
FileWrite $hFileLog 'Extracting "${src}" to $outdir$\r$\n'
!macroend
Section "Main Program"
!insertmacro Log_Init "$instdir\install.log"
!insertmacro Log_String "Starting install..."
SetOutPath $instdir
!insertmacro File "${__FILE__}"
SectionEnd
Section "Optional Foo Component"
!insertmacro Log_String "Installing Foo"
SectionEnd
Section
!insertmacro Log_Close
SectionEnd
or add more hacks to make DumpLog work:
Function .onInit
IfSilent 0 +2
StrCpy $0 1 ;We need to know if we are in "real silent" mode
SetSilent normal ;Turn off real silent mode so we can fake it
FunctionEnd
Function .onGuiInit
StrCmp $0 1 0 +3
HideWindow
SetSilent silent ;The docs say this is only supported in .onInit but all we care about is the internal flag used by IfSilent
FunctionEnd
LicenseData "${__FILE__}"
page license skipifsilent ; All pages except instfiles needs this hack
page directory skipifsilent
page instfiles
Function skipifsilent
IfSilent 0 +3
HideWindow
Abort
FunctionEnd
Section
IfSilent 0 +2
HideWindow
SetOutPath $instdir
File "${__FILE__}"
Sleep 2222 ;So we can see that it is hidden and not just finishing quickly
DetailPrint "$(^Completed)" ; Fake the completed message show it shows in the log
Push "$EXEDIR\install.log"
Call DumpLog
IfSilent 0 +2
Quit
SetDetailsPrint textonly
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"
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

Related

ITaskScheduler::Delete fails

!define GUIDTaskScheduler "{148BD52A-A2AB-11CE-B11F-00AA00530503}"
!define GUIDITaskScheduler "{148BD527-A2AB-11CE-B11F-00AA00530503}"
Function DeleteTask
!define DeleteTask "!insertmacro _DeleteTask"
!macro _DeleteTask _TASK _RESULT
Push ${_Task}
Call DeleteTask
Pop ${_RESULT}
!macroend
Exch $0
Push $1
Push $2
StrCpy $0 false
System::Call "ole32::CoCreateInstance(g'${GUIDTaskScheduler}',i0,i11,g '${GUIDITaskScheduler}',*i.r1)i.r2"
IntCmp $2 0 0 +4
System::Call '$1->7(w r0)i.r2'
IntCmp $2 0 0 +2
StrCpy $0 true
Pop $2
Pop $1
END:
Exch $0
FunctionEnd
${DeleteTask} "TaskName" $0
Pop $0
$0 should set to true or 1 but it's false. The task isn't deleting.
What am I doing wrong here?
Printing out the HRESULT error would probably give you a clue.
It looks like you are overwriting the service name with StrCpy $0 false!
You should also release the interface in $1 after you are done with it.
!define GUIDTaskScheduler "{148BD52A-A2AB-11CE-B11F-00AA00530503}"
!define GUIDITaskScheduler "{148BD527-A2AB-11CE-B11F-00AA00530503}"
Function DeleteTask
!define DeleteTask "!insertmacro _DeleteTask"
!macro _DeleteTask _TASK _RESULT
Push ${_Task}
Call DeleteTask
Pop ${_RESULT}
!macroend
Exch $0
Push $1
Push $2
System::Call "ole32::CoCreateInstance(g'${GUIDTaskScheduler}',i0,i11,g '${GUIDITaskScheduler}',*i.r1)i.r2"
IntCmp $2 0 "" fail
System::Call '$1->7(w r0)i.r2' ; Delete($0)
System::Call '$1->2()' ; Release
fail:
StrCpy $0 $2 ; HRESULT
Pop $2
Pop $1
Exch $0
FunctionEnd
Section
${DeleteTask} "TaskName" $0
DetailPrint HRESULT=$0
SectionEnd
I've figured it out. This was bothering me to no end but I've managed to figure it out after rewriting it. Here's the working function.
Function DeleteTask
!define TaskGUID `{148BD52A-A2AB-11CE-B11F-00AA00530503}`
!define ITaskGUID `{148BD527-A2AB-11CE-B11F-00AA00530503}`
!define OLE `ole32::CoCreateInstance(g"${TaskGUID}",`
!define OLE32 `${OLE}i0,i11,g "${ITaskGUID}",*i.r1)i.r2`
!define DeleteTask "!insertmacro _DeleteTask"
!macro _DeleteTask _RESULT _TASK
Push ${_Task}
Call DeleteTask
Pop ${_RESULT}
!macroend
Exch $0
Push $0
Push $1
Push $2
Push $3
StrCpy $3 false
System::Call `${OLE32}`
IntCmp $2 0 0 +5
System::Call "$1->7(w r0)i.r2"
IntCmp $2 0 0 +3
System::Call "$1->2()"
StrCpy $3 true
Pop $2
Pop $1
Pop $0
Exch $3
FunctionEnd
;= $0 Should return either true on success or false on failure.
${DeleteTask} $0 "Task Name"
StrCmpS $0 true 0 +2
DetailPrint "${TASK} was successfully deleted."
DetailPrint "Failed to remove the task ${TASK}."
Thanks goes to Anders for his help.

svn diff through perltidy

I want to run perltidy before i look for diff in my subversion working copy.
in svn config i wrote:
diff-cmd = /usr/bin/d.sh
As David W said in this answer https://stackoverflow.com/a/5834900/1927848 i make a script /usr/bin/d.sh:
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$1" > "/tmp/$1"
/usr/local/bin/perltidy "$2" > "/tmp/$2"
/usr/bin/diff "$1" "$2"
/bin/rm "/tmp/$1" "/tmp/$2"
exit 0
and when i make svn diff in working copy i got some errors:
dev# svn diff
Index: nodeny/new_month.pl
===================================================================
Unknown option: u
Error on command line; for help try 'perltidy -h'
Option l is ambiguous (libpods, line-up-parentheses, logfile, logfile-gap, long-block-line-count, look-for-autoloader, look-for-hash-bang, look-for-selfloader)
Error on command line; for help try 'perltidy -h'
diff: option requires an argument -- L
/usr/bin/diff: Try `/usr/bin/diff --help' for more information.
where is my errors?
UPD: $1 and $2 are not file names, $6 and $7 contains file names. i made some modifications to code, thanks to ikegami comment
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$6" -st > "/tmp/tidy001"
/usr/local/bin/perltidy "$7" -st > "/tmp/tidy002"
/usr/bin/diff "/tmp/tidy001" "/tmp/tidy002"
/bin/rm "/tmp/tidy001" "/tmp/tidy002"
exit 0
but now script only does first perltidy command and wait... whats wrong?
UPD2: perl script, that works:
#!/usr/bin/perl
use Text::Diff;
if (-e $ARGV[-2] && -e $ARGV[-1]) {
my $str1 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-2] -st`;
my $str2 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-1] -st`;
my $diff = diff(\$str1, \$str2);
print $diff;
}
else {
print "Error file $ARGV[-2] or $ARGV[-1] not exists\n";
}
exit 0;
I'm not an experienced bash code, so the following may not be optimal, especially given the redundancy, but it solves your problem by assuming the last two args are the file names.
#!/bin/bash
args=("$#")
f1_idx=$(( ${#args[#]} - 2 ))
f1="${args[$f1_idx]}"
/usr/local/bin/perltidy "$f1" -st > "/tmp/$f1"
args[$f1_idx]="/tmp/$f1"
f2_idx=$(( ${#args[#]} - 1 ))
f2="${args[$f2_idx]}"
/usr/local/bin/perltidy "$f2" -st > "/tmp/$f2"
args[$f2_idx]="/tmp/$f2"
/usr/bin/diff "$args[#]"
/bin/rm "/tmp/$f1" "/tmp/$f2"
exit 0
Or if you don't care about the actual file names (as your update implies), you can avoid the temporary files altogether.
#!/bin/bash
args=("$#")
last_idx=$(( ${#args[#]} - 1 ))
f2="${args[$last_idx]}"
unset args[$last_idx]
last_idx=$(( ${#args[#]} - 1 ))
f1="${args[$last_idx]}"
unset args[$last_idx]
/usr/bin/diff "$args[#]" \
<( /usr/local/bin/perltidy "$f1" -st ) \
<( /usr/local/bin/perltidy "$f2" -st )
exit 0

Write string to Unicode File

I am using NSIS Unicode version and I am trying to append a string to an existing Unicode file(UTF-16LE).
My Problem: After I write the string to the file then open the file, the string I wrote is just jibberish. I have a feeling that its trying to write an ANSI string to a UTF-16LE file.
How can I write a string to a unicode file?
Function ${prefix}AppendFile
# Note: Will automatically create file if it doesn't exist
# $0 = fName
# $1 = strToWrite
Pop $1
Pop $0
ClearErrors
FileOpen $3 $0 a
FileSeek $3 0 END
FileWrite $3 "$\r$\n" # write a new line
FileWrite $3 "$1"
FileWrite $3 "$\r$\n" # write an extra line
FileClose $3 # close the file
IfErrors 0 +2
MessageBox MB_OK "Append Error: $1 $\r$\n$\r$\n$0"
FunctionEnd
If you're dealing with UTF-16LE file, you need to use FileWriteUTF16LE, which writes Unicode text, rather than FileWrite, which writes ANSI text.

Getting Error result in System::call for MsiEnumProducts and GetLastError in NSIS script

I have written the following script to Enumerate Installed Products from MSI.
!include LogicLib.nsh
!define MsiGetProductCodeFromName "!insertmacro MsiGetProductCodeFromName"
!macro MsiGetProductCodeFromName MSINSISPRODUCTNAME
Push ${MSINSISPRODUCTNAME}
Call MsiGetProductCodeFromName
!macroend
Function MsiGetProductCodeFromName
;================
; Stack : <ProductName>, ...
;================
Exch $0 ; ProductName
Push $1 ; For internal use as buffer for ProductCode
Push $2 ; For internal use as counter to MsiEnumProducts
Push $3 ; For internal use as result of MsiEnumProducts
; loop through all productcodes
Strcpy $2 0
System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
DetailPrint "User name - $0"
DetailPrint "String length - $1"
DetailPrint "Return value - $2"
loop:
DetailPrint "Calling MsiEnumProducts ( $2, $1 )"
System::call "msi::MsiEnumProducts (i r2, t .r1) i.r3"
DetailPrint "Result = $3"
System::call "kernel32::GetLastError () i.r3"
DetailPrint "LastError = $3"
${If} $R1 == 0
DetailPrint $1
Intop $R0 $R0 + 1
goto loop
${Endif}
end:
Pop $3
Pop $2
Exch
Pop $0
;=========================
; Stack : <ProductCode>, ...
; ========================
FunctionEnd
When I use the above script in the following manner:
${MsiGetProductCodeFromName} ${PRODUCT_NAME}
I am getting the following output:
Calling MsiEnumProducts(0, )
Result = error
LastError = error
Completed
Please note that this is excluding the output of System call to GetUserName, which is working fine.
Any help would be appreciated.
The first call to MsiEnumProducts has to start at index 0 so add Strcpy $2 0 before the loop label.
I'm not sure if you can have a space between dll::function and (), when you just get "error" from the system plugin it usually means there is something wrong with the syntax.
You cannot call kernel32::GetLastError and get a valid result, it has to be part of the original call: System::Call 'dll::function() ?e' and then pop the error code off the stack...

Window Socket programming in NSIS

I am trying to create a socket in the .nsi file to check whether the socket will get created successfully or not to check for port availability. So any help with respect to windows socket programmin in nsis is highly appreciated.
thank u
!include LogicLib.nsh
!ifndef AF_INET
!define AF_INET 2
!define SOCK_STREAM 1
!define IPPROTO_TCP 6
!define INADDR_ANY 0
!define SOL_SOCKET 0xffff
!define /math SO_EXCLUSIVEADDRUSE 0xffffffff ^ 0x4
!endif
Function QueryCanBindToIP4TCPPort
Exch $0
Push $1
Push $2
Push $0 ;Push port
System::Alloc 16 ;WSADATA & sockaddr_in
System::Call 'Ws2_32::WSAStartup(i 2,isr2)i.r0'
${If} $0 = 0
System::Call 'Ws2_32::socket(i ${AF_INET},i ${SOCK_STREAM},i ${IPPROTO_TCP})i.r1 ?e'
Pop $0
${If} $1 <> -1
System::Call 'Ws2_32::setsockopt(ir1,i ${SOL_SOCKET},i ${SO_EXCLUSIVEADDRUSE},*i 1,i4)i.r0'
System::Call 'Ws2_32::htons(iss)i.s' ;Convert port (and leaves the original push on the stack)
;Skipping htonl on address since INADDR_ANY is 0
System::Call '*$2(&i2 ${AF_INET},&i2 s,&i4 ${INADDR_ANY},&i8 0)'
System::Call 'Ws2_32::bind(ir1,ir2,i16)i.r0'
${If} $0 = 0
System::Call 'Ws2_32::listen(ir1,i0)i.r0'
;Listening here but once we hit WSACleanup the port will be free again (This is fine if all you need to do is make sure no app is bound to the port at this moment)
; System::Call 'Ws2_32::closesocket(ir1)'
${EndIf}
${EndIf}
System::Call 'Ws2_32::WSACleanup()' ;Remove this call to leave the port open for the duration of the installer
${EndIf}
System::Free $2
Pop $2 ;Throw away port
Pop $2
Pop $1
Exch $0
FunctionEnd
Section
Push 666
call QueryCanBindToIP4TCPPort
Pop $0
${If} $0 = 0
DetailPrint "Bind OK"
${Else}
DetailPrint "Bind Failed!"
${EndIf}
SectionEnd
As NSIS doesn't have any network functionality either built-in or in any plugins that I could find, the simplest solution in my opinion is to make a very small program that tries to create a listening socket on the specified port (where the port can either be hard-coded or passed as an argument). This program can then be used in the install script to check if the port is available.