NSIS Unicode - How to get all entries in section of INI file - nsis

At https://nsis.sourceforge.io/Get_all_entries_in_section_of_INI_file there is an ANSI version, but I need an unicode solution.
My first try was remove the A in the api calls (GetPrivateProfileSection and lstrlen), and increasing the memory to 8192.
Function GetSection
Exch $2
Exch
Exch $1
Exch
Exch 2
Exch $0
Exch 2
Push $3
Push $4
Push $5
Push $6
Push $8
Push $9
System::Alloc 8192
Pop $3
StrCpy $4 $3
System::Call "kernel32::GetPrivateProfileSection(t, i, i, t) i(r1, r4, 8192, r0) .r5"
enumok:
System::Call 'kernel32::lstrlen(t) i(i r4) .r6'
StrCmp $6 '0' enumex
System::Call '*$4(&t8192 .r9)'
Push $0
Push $1
Push $2
Push $3
Push $4
Push $5
Push $6
Push $8
Call $2
Pop $9
Pop $8
Pop $6
Pop $5
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
StrCmp $9 'StopGetSection' enumex
IntOp $4 $4 + $6
IntOp $4 $4 + 1
goto enumok
enumex:
System::Free $3
Pop $9
Pop $8
Pop $6
Pop $5
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
Despite that my application crashes.

Unicode characters are 2 bytes, use System::StrAlloc to allocate the buffer or double the number passed to Alloc.
The ini function deals with number of characters, not number of bytes. It overflowed the buffer because you had not allocated as much as you told it.
I edited the wiki page, you can try the new version...

Related

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.

CPU Features.GetCount return incorrect number for CPU Cores

I'm using CPU Features plug-in to get CPU core and this is the code:
${CPUFeatures.GetCount} $CPUCore
Apparently, there is a computer with 12 CPU Cores and the $CPUCore shows only 1 core. I think maybe there is a possibility that the $CPUCore only return the first digit, but how can I know it for sure?
Or, is there any other way to get the CPU Core numbers?
I don't know why the plugin is failing but you could ask Windows with something like this:
!include LogicLib.nsh
!ifndef ERROR_INSUFFICIENT_BUFFER
!define ERROR_INSUFFICIENT_BUFFER 122
!endif
!define RelationProcessorCore 0
!if "${NSIS_PTR_SIZE}" <= 4
Function GetProcessorPhysCoreCount
System::Store S
StrCpy $9 0 ; 0 if we fail
System::Call 'kernel32::GetLogicalProcessorInformationEx(i${RelationProcessorCore},i,*i0r2)i.r0?e'
Pop $3
${If} $3 = ${ERROR_INSUFFICIENT_BUFFER}
${AndIf} $2 <> 0
System::Alloc $2
System::Call 'kernel32::GetLogicalProcessorInformationEx(i${RelationProcessorCore},isr1,*ir2r2)i.r0'
Push $1
${If} $0 <> 0
loop_7:
IntOp $9 $9 + 1
System::Call *$1(i,i.r3)
IntOp $1 $1 + $3
IntOp $2 $2 - $3
IntCmp $2 0 "" loop_7 loop_7
${EndIf}
Pop $1
System::Free $1
${Else}
System::Call 'kernel32::GetLogicalProcessorInformation(i,*i0r2)i.r0?e'
Pop $3
${If} $3 = ${ERROR_INSUFFICIENT_BUFFER}
System::Alloc $2
System::Call 'kernel32::GetLogicalProcessorInformation(isr1,*ir2r2)i.r0'
Push $1
${If} $0 <> 0
loop_v:
System::Call *$1(i,i.r3)
${If} $3 = ${RelationProcessorCore}
IntOp $9 $9 + 1
${EndIf}
IntOp $1 $1 + 24
IntOp $2 $2 - 24
IntCmp $2 0 "" loop_v loop_v
${EndIf}
Pop $1
System::Free $1
${EndIf}
${EndIf}
Push $9
System::Store L
FunctionEnd
Function CountSetBits32
Exch $0
Push $1
Push $2
Push $3
StrCpy $3 0
StrCpy $1 0
loop:
IntOp $2 1 << $1
IntOp $2 $2 & $0
${IfThen} $2 <> 0 ${|} IntOp $3 $3 + 1 ${|}
IntOp $1 $1 + 1
StrCmp $1 32 "" loop
StrCpy $0 $3
Pop $3
Pop $2
Pop $1
Exch $0
FunctionEnd
Function GetProcessorLogicalCoreCount
System::Store S
StrCpy $9 0 ; 0 if we fail
System::Call 'kernel32::GetLogicalProcessorInformationEx(i${RelationProcessorCore},i,*i0r2)i.r0?e'
Pop $3
${If} $3 = ${ERROR_INSUFFICIENT_BUFFER}
${AndIf} $2 <> 0
System::Alloc $2
System::Call 'kernel32::GetLogicalProcessorInformationEx(i${RelationProcessorCore},isr1,*ir2r2)i.r0'
Push $1
${If} $0 <> 0
loop_7:
System::Call *$1(i,i.r3,&i22,&i2,i.r5)
Push $5
Call CountSetBits32
Pop $5
IntOp $9 $9 + $5
IntOp $1 $1 + $3
IntOp $2 $2 - $3
IntCmp $2 0 "" loop_7 loop_7
${EndIf}
Pop $1
System::Free $1
${Else}
System::Call 'kernel32::GetLogicalProcessorInformation(i,*i0r2)i.r0?e'
Pop $3
${If} $3 = ${ERROR_INSUFFICIENT_BUFFER}
System::Alloc $2
System::Call 'kernel32::GetLogicalProcessorInformation(isr1,*ir2r2)i.r0'
Push $1
${If} $0 <> 0
loop_v:
System::Call *$1(i,i.r3)
${If} $3 = ${RelationProcessorCore}
System::Call *$1(i.r3)
Push $3
Call CountSetBits32
Pop $3
IntOp $9 $9 + $3
${EndIf}
IntOp $1 $1 + 24
IntOp $2 $2 - 24
IntCmp $2 0 "" loop_v loop_v
${EndIf}
Pop $1
System::Free $1
${EndIf}
${EndIf}
Push $9
System::Store L
FunctionEnd
!endif
Section
Call GetProcessorPhysCoreCount
Pop $0
Call GetProcessorLogicalCoreCount
Pop $1
DetailPrint PhysCores=$0,LogicalCores=$1
SectionEnd
You should still use ${CPUFeatures.GetCount} as a fallback because GetLogicalProcessorInformation[Ex] does not exist on all versions of Windows...
I try to use the code from this forum and modified it a little bit so I only get the processor core numbers:
!include "LogicLib.nsh"
!define ERROR_INSUFFICIENT_BUFFER 122
; Size of SYSTEM_LOGICAL_PROCESSOR_INFORMATION on 32-bit systems
!define SYS_LOG_PROC_INFO_SIZE 24
; Offset of Relationship in the SYSTEM_LOGICAL_PROCESSOR_INFORMATION structure
!define RELATIONSHIP_OFFSET 4
; Enum value of Relationship identifying Processor Core
!define RELATIONPROCESSORCORE 0
; Count the number of bits set in given value
; Parameters: value
; Returns: number of bits set in given value
Function countbits
Exch $0
Push $1
Push $2
; Set initial value for number of bits set in $0
StrCpy $1 0
${While} $0 > 0
; Clear least significant bit set
IntOp $2 $0 - 1
IntOp $0 $0 & $2
; Increment number of bits set
IntOp $1 $1 + 1
${EndWhile}
; Return number of bits set
StrCpy $0 $1
Pop $2
Pop $1
Exch $0
FunctionEnd
; Evaluate processor information
; Paramaters: buffer, length
; Returns: number of cores
Function evalcpuinfo
Exch $0 ; length
Exch
Exch $1 ; buffer
Push $2
Push $3
Push $4
Push $5 ; Processor Cores
Push $6 ; Logical Processors
; Set buffer offset at the end of the buffer
StrCpy $2 $0
; Initialize number of Processor Cores and Logical Processors
StrCpy $5 0
StrCpy $6 0
; Iterate through buffer starting from end
${While} $2 >= ${SYS_LOG_PROC_INFO_SIZE}
; Calculate start address of an element
IntOp $2 $2 - ${SYS_LOG_PROC_INFO_SIZE}
IntOp $3 $1 + $2
; Get ProcessorMask value from element
System::Call "*$3(i.r4)"
Push $4
IntOp $3 $3 + ${RELATIONSHIP_OFFSET}
; Get Relationship value from element
System::Call "*$3(i.r4)"
${If} $4 == ${RELATIONPROCESSORCORE}
; Increment Processor cores
IntOp $5 $5 + 1
; Determine number of Logical Processor by counting the bits
; set in the value of ProcessorMask
Call countbits
Pop $4
; Sum up Logical Processors
IntOp $6 $6 + $4
${Else}
Pop $4
${EndIf}
${EndWhile}
; Set processor information as return value
StrCpy $0 $5
Pop $6
Pop $5
Pop $4
Pop $3
Pop $2
Pop $1
Exch $0
FunctionEnd
; Get processor information
; Returns: number of Processor Cores and Logical Processors
Function getcpuinfo
Push $0
Push $1
Push $2
Push $3
Push $4
; GetLogicalProcessorInformation is only available on
; Windows XP SP3 or its successors.
; Initialize buffer and its length
StrCpy $1 0
StrCpy $2 0
; Determine required length of buffer
System::Call "kernel32::GetLogicalProcessorInformation(ir1, *ir2r2) i.r3 ? e"
Pop $4
${If} $3 == 0
${If} $4 == ${ERROR_INSUFFICIENT_BUFFER}
; Allocate buffer
System::Alloc $2
Pop $1
${If} $1 != 0
; Get processor information
System::Call "kernel32::GetLogicalProcessorInformation(ir1, *ir2r2) i.r3 ? e"
Pop $4
${If} $3 != 1
StrCpy $0 "Error: $4"
${Else}
Push $1 ; buffer
Push $2 ; length
Call evalcpuinfo
Pop $0
${EndIf}
; Deallocate buffer
System::Free $1
${Else}
StrCpy $0 "Error: memory allocation failed!"
${EndIf}
${Else}
StrCpy $0 "Error: $4"
${EndIf}
${Else}
StrCpy $0 "GetLogicalProcessorInformation is not available on your system!"
${EndIf}
Pop $4
Pop $3
Pop $2
Pop $1
Exch $0
FunctionEnd
I think the code is quite similar with Anders code. I have tried it in three different PCs and get the correct numbers for processor core.

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)

Count and split string with space except if between quotes in NSIS

My question is like Split string on spaces in Java, except if between quotes (i.e. treat \"hello world\" as one token) but in NSIS way.
Hopefully without any external plugins.
How do I split a String based on space but take quoted substrings as
one word?
Example:
Location "Welcome to india" Bangalore Channai "IT city" Mysore
it should be stored in ArrayList as
Location
Welcome to india
Bangalore
Channai
IT city
Mysore
There is no array support without plugins so I left out the array part.
Any string operation can be performed with the 3 basic string functions in NSIS (StrLen, StrCpy and StrCmp):
!include LogicLib.nsh
Function SplitArg
Exch $0 ; str
Push $1 ; inQ
Push $3 ; idx
Push $4 ; tmp
StrCpy $1 0
StrCpy $3 0
loop:
StrCpy $4 $0 1 $3
${If} $4 == '"'
${If} $1 <> 0
StrCpy $0 $0 "" 1
IntOp $3 $3 - 1
${EndIf}
IntOp $1 $1 !
${EndIf}
${If} $4 == '' ; The end?
StrCpy $1 0
StrCpy $4 ' '
${EndIf}
${If} $4 == ' '
${AndIf} $1 = 0
StrCpy $4 $0 $3
StrCpy $1 $4 "" -1
${IfThen} $1 == '"' ${|} StrCpy $4 $4 -1 ${|}
killspace:
IntOp $3 $3 + 1
StrCpy $0 $0 "" $3
StrCpy $1 $0 1
StrCpy $3 0
StrCmp $1 ' ' killspace
Push $0 ; Remaining
Exch 4
Pop $0
StrCmp $4 "" 0 moreleft
Pop $4
Pop $3
Pop $1
Return
moreleft:
Exch $4
Exch 2
Pop $1
Pop $3
Return
${EndIf}
IntOp $3 $3 + 1
Goto loop
FunctionEnd
Section
push 'Location "Welcome to india" Bangalore Channai "IT city" Mysore'
loop:
call SplitArg
Pop $0
StrCmp $0 "" done
DetailPrint Item=|$0|
goto loop
done:
SectionEnd

Resources