Window Socket programming in NSIS - sockets

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.

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.

NSI script : calling one macro from another using insertmacro gives error

I am trying to call 1 macro from another in NSI script. Both the macros have MB_OKCANCEL . It gives following error when compiled:
**
[exec] Error: label "abort_inst:" already declared in function
**
!include "MUI2.nsh"
OutFile abc.exe
!macro installA
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
ABORT
GoTo lblinstall
lblinstall:
!macroend
!macro uninstallA
MessageBox MB_OKCANCEL "?" IDOK install_A IDCANCEL abort_uninstall
abort_uninstall:
ABORT
install_A:
!insertmacro installA
!macroend
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
!insertmacro installA
${Else}
!insertmacro uninstallA
${EndIf}
FunctionEnd
Section "required" main_section
SectionEnd
Please help
(Next time, please make sure your code doesn't do weird line breaks)
When inserting a macro, all the code between !macro and !macroend will substitute your !insertmacro. Therefor, you should't use static labels in macros–you could only insert the macro once (making a macro pointless!) You can use relative jumps (e.g. Goto +2) or make your labels dynamic by adding parameters to your labels, e.g.:
!macro myMacro param1
${param1}_loop:
MessageBox MB_YESNO "Loop this message?" IDYES ${param1}_loop
# some more code
${param1}_end:
!macroend
However, since you don't pass any parameters to your macros, why don't you simply use functions?
Function installA
# your code here
Function
Function uninstallA
# your code here
Call installA
FunctionEnd
This got resolved by converting one macro into Function, as suggested by #idelberg
Again, as long as you're working with macros, you cannot use static labels. A static label can only be used once in a function or section. Your use of macros would translate to the following:
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
Abort
Goto lblinstall
lblinstall: # FIRST TIME USE
${Else}
MessageBox MB_OKCANCEL "?" IDOK install_A IDCANCEL abort_uninstall
abort_uninstall:
Abort
install_A:
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
Abort
Goto lblinstall
lblinstall: # SECOND TIME USE
${EndIf}
FunctionEnd
So, that won't work because of the lblinstall label which is being used twice. Instead, you could do something like this:
Function installA
MessageBox MB_OKCANCEL "A?" IDOK lblinstall
Abort
lblinstall:
FunctionEnd
Function uninstallA
MessageBox MB_OKCANCEL "?" IDOK install_A
Abort
install_A:
Call installA
FunctionEnd
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
Call installA
${Else}
Call uninstallA
${EndIf}
FunctionEnd
(I also took the freedom to strip some unnecessary labels from your example)

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...

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

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

Perl - $? shows 0 for a -1 return code on Windows 2008 64bit

Using Strawberry Perl 5.12.3
Running manually:
E:\informatica\tools>infacmd isp ping -sn tt -re 0
[ICMD_10033] Command [ping] failed with error [[INFACMD_10053] Service [tt] Domain [dmt3-9-dom-poc] has failed to ping back.].
E:\informatica\tools>echo %ERRORLEVEL%
-1
When I run the same command through Perl's "system", the $? shows 0. Perl code:
use strict;
use warnings;
my $cmd = system("infacmd isp ping -sn tt -re 0");
if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
Output:
[ICMD_10033] Command [ping] failed with error [[INFACMD_10053] Service [tt] Domain [dmt3-9-dom-poc] has failed to ping back.].
child exited with value 0
The same on Windows 2003 32bit and ActiveState Perl 5.8.8 shows correct results.
You might have better luck with ${^CHILD_ERROR_NATIVE} than with the emulation of unix's process status structure.
I believe that your manual test is not entirely sufficient, as the "errorlevel" variable can be shadowed or matters can otherwise be confused by usage of the shell, so your "infacmd" may not be exiting with the exit code that you think it is.
Your perl script is invoking this subprocess via the shell. Does the behavior change if you invoke it directly instead? (which is usually good practice...)
i.e. if you change the system line to this:
my $cmd = system('infacmd', 'isp', 'ping', '-sn', 'tt', '-re', '0');
... is the behavior affected at all?
I think you should shift $? (I.e., divide it by 256) before doing the branch...
$res = $? >>8;
if ($res == -1)
....
According to system definition in perlfunc
EDIT: I have just read in perl 5 by examples (chapter: handling errors and signals) that on windows you cannot rely on $? To represent the exit state of pipes and system. this could well your case.
the suggestion is,then, to capture the command output and parse it to find out which is the error code...
my $output = `mycmd 2>&1`;
if ($output =~ /.....
That leaves Win32::Process. If the exit code from this is zero, the exit code is zero.