GetCaretPos Error Invalid Access Memory Location - winapi

I'm trying to call the GetCaretPos function in User32.dll from an NSIS installer, and the only thing I can seem to get is an invalid access to memory location error.
Here is my code:
Function fixUpRegKeyText
Exch $0 ; HWND to text box
Push $1 ; text of edit box
Push $2 ; pointer to point structure
Push $3 ; getlasterror result
System::Alloc 16 ;struct long, long
pop $2
;messageBox MB_OK $2
;get the caret position
System::Call "User32::GetCaretPos(p .r2) i.. ? e"
pop $3
messageBox MB_OK $3 ; 998!
${NSD_GetText} $0 $1
Push $1
call StrUpper
Pop $1
${NSD_SetText} $0 $1
; now set the caret position
;System::call "user32::SetCaretPos(p s.) i .."
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
Edit In case anyone is interested in using Windows API to move the text caret, I blogged about it. Using anders' answer, it's straightforward to do this in NSIS script.

The p pointer type does not work in 2.46, use i. (Use your offline helpfile, the online helpfile has features only available in SVN)
Also, when a function needs a pointer to a struct that you have allocated, use "i register", not "i .register" (The function needs to know the memory address so it can fill it with data)
System::Call '*(i,i)i.r0'
System::Call "USER32::GetCaretPos(i r0)i.r3"
System::Call '*$0(i.r1,i.r2)'
DetailPrint "$1x$2 return=$3"
System::Free $0

Related

retrieve variable value from nsh file nsis

I am creating installer using nsis. I have created .nsh file using SIMPLE SC to start, stop and remove services. I have used macros for this.
When I call this in nsi file it works. But I would like to know the return value for start service (SimpleSC::StartService "${SVC}" "${Args}" "${Timeout}", Pop $0). how do I retrieve the $0 value of nsh file in my nsis file
If you have some helper macros that return a result, you can leave the result on the stack:
!macro DoSomething
Push $0
Push $1
Push "Pretend that this is a function that does something and puts the result on the stack"
Pop $0 ; Result we want to return
Pop $1
Exch $0
!macroend
!insertmacro DoSomething
Pop $0
MessageBox MB_OK $0
or store it in a register as part of the macro:
!macro DoSomething outvar
Push $0
Push $1
Push "Pretend that this is a function that does something and puts the result on the stack"
Pop $0 ; Result we want to return
Pop $1
Exch $0
Pop ${outvar}
!macroend
!insertmacro DoSomething $0
MessageBox MB_OK $0

NSIS. Create a fill in form to write the inserted data into a txt file

Just started using NSIS for my first installer. Found everything that i need apart from one thing only. I found how to write and create a txt file but couldn't find any info whether it is possible or not to allow the user to do some input (open a form in the installer) and then write the inserted data into a txt file.
Right now i'm able to write an input from an nsDialog, this is the code:
nsDialogs::Create 1018
Pop $Dialog
${NSD_CreateText} 10% 20u 80% 12u "Insert the API KEY"
Pop $Text
nsDialogs::Show
${NSD_GetText} $Text $0
MessageBox MB_OK "You typed:$\n$\n$0"
FileOpen $0 "$DESKTOP\Hello_world.txt" w
FileWrite $0 $Text
FileClose $0
However, the problem is that the data that is being written in the Hello_world.txt are some random digits, right now i'm not really understanding what are these numbers, shouldn't $Text be a String?
The dialog (and its child controls) only exists between nsDialogs::Create and nsDialogs::Show. You get random information because you are trying to read from something that no longer exists. Also, in your example $Text is the edit control handle (HWND), not the text, your text would be in $0 in your case.
To finalize and display the dialog you must call nsDialogs::Show. This function will not return until the user clicks Next, Back or Cancel.
You should read user input in the leave callback of a page:
Page Custom MyPageCreate MyPageLeave
Page Directory
Page InstFiles
Var MyTextControlHandle
Function MyPageCreate
nsDialogs::Create 1018
Pop $0
${NSD_CreateText} 10% 20u 80% 12u "Insert the API KEY"
Pop $MyTextControlHandle
nsDialogs::Show
; $MyTextControlHandle is no longer valid here
FunctionEnd
Function MyPageLeave
${NSD_GetText} $MyTextControlHandle $0 ; Get text from $MyTextControlHandle and store in $0
MessageBox MB_OK "You typed:$\n$\n$0"
; Save $0 somewhere if desired
FunctionEnd

NSIS How to modify MUI_PAGE_INSTFILES during installtion?

I am wondering if it will be possible to perform the following UI modifications / tweaks (programmatically in the nsi script) WHILE the installation is running on the MUI_PAGE_INSTFILES page:
Set the style of the progress bar to marque (continuous "scrolling")
Hide the details text field (where the progress text is displayed) and show a predefined picture instead (that was included with the setup)
[EDIT] Alternatively for number 2: maybe shrink the details field, and display the picture as a banner like label just below it (so that the details field would occupy only half the space vertically, and the other half below it would be used by the picture - that way a user can see both the progress and the "banner").
[EDIT] Even better still would be to have some sort of basic slide show: define several pictures and rotate them every 10 seconds
The idea behind this is that during parts of the installation that may take some time (say over 10 seconds) to display something more informative / valuable to the user, such as how to get started graphic, a sales promotion or something else.
How can this be done?
Thanks.
XPStyle on ; This must be on for the Marquee to show
!include nsDialogs.nsh ; For WS_* and NSD_SetImage
!include WinMessages.nsh
!ifndef PBS_MARQUEE
!define PBS_MARQUEE 8
!endif
!ifndef PBM_SETMARQUEE
!define PBM_SETMARQUEE 0x40A
!endif
!macro ProgressMarquee_Begin
FindWindow $0 "#32770" "" $HWNDPARENT ; Find inner dialog
GetDlgItem $0 $0 1004 ; Find progress-bar
System::Call USER32::GetWindowLong(ir0,i-16)i.r1
IntOp $1 $1 | ${PBS_MARQUEE}
System::Call USER32::SetWindowLong(ir0s,i-16,ir1s) ; This also saves the handle and style on the stack, we need those to turn it off again.
SendMessage $0 ${PBM_SETMARQUEE} 1 0 ; The last parameter is the speed or 0 for the default
!macroend
!macro ProgressMarquee_Remove
Exch
Pop $1
IntOp $0 ${PBS_MARQUEE} ~
IntOp $1 $1 & $0
System::Call USER32::SetWindowLong(is,i-16,ir1)
!macroend
Function DoLongOperation
DetailPrint "Starting long operation"
Sleep 4444
DetailPrint "Long operation ended..."
FunctionEnd
Section
DetailPrint "Simulating other install tasks..."
Sleep 222
Sleep 222
Sleep 222
Sleep 222
Sleep 222
Sleep 222
; Add a image and hide the log
FindWindow $2 "#32770" "" $HWNDPARENT ; Find inner dialog
GetDlgItem $0 $2 0x3F8 ; Find log window
System::Call *(i,i,i,i)i.r1 ; Allocate a RECT
System::Call 'USER32::GetWindowRect(ir0s,ir1)' ; Also saves log HWND on stack
ShowWindow $0 0 ; Hide log window
System::Call '*$1(i.r4,i.r5,i.r6,i.r7)'
IntOp $6 $6 - $4
IntOp $7 $7 - $5
System::Call 'USER32::ScreenToClient(ir2,ir1)'
System::Call '*$1(i.r4,i.r5)'
System::Free $1
System::Call 'USER32::CreateWindowEx(i0,t"STATIC",i0,i${WS_CHILD}|${WS_CLIPSIBLINGS}|${WS_VISIBLE}|${SS_BITMAP},ir4,ir5,ir6,ir7,ir2,i0,i0,i0)i.r2'
Push $2 ; Save HWND
SetDetailsPrint none
File "/oname=$pluginsdir\img.bmp" "${NSISDIR}\Contrib\Graphics\Header\orange.bmp"
SetDetailsPrint lastused
${NSD_SetImage} $2 "$pluginsdir\img.bmp" $9
Push $9 ; Save HBITMAP
!insertmacro ProgressMarquee_Begin
Call DoLongOperation
!insertmacro ProgressMarquee_Remove
; Remove and cleanup image
Pop $0
${NSD_FreeBitmap} $0
Pop $0
ShowWindow $0 0
Pop $0 ; Log window handle we saved
ShowWindow $0 1
SectionEnd
If you want to show multiple images then you need multiple calls to ${NSD_SetImage} but if you want to go down this route it is probably much better to write a NSIS plugin that does all this on a secondary thread...

unexpected logiclib behavior when testing string returned by system plugin

I am getting system information via the System plugin by using code similar to the answer to my previous question. The API function is very similar except that it returns (or not) a wide char string.
While using that script on a computer that is member of a domain, I cannot understand why the logiclib test always pass in the ${else} branch of the test. The following capture shows that $1 is not null:
While reading more the NetGetDCName reference I think that it would be preferable to check the function returned value instead of the wide char string pointer. I am a bit confused, why does my ${If} 0 <> $1 always fails? I have checked on a virtual machine not member of a domain that $1 equals 0 in this case. I suppose then that the ${IfThen} test to free the memory is wrong too.
Here is the sample code:
!include "logiclib.nsh"
outfile "hello2.exe"
!define DEBUG `System::Call kernel32::OutputDebugString(ts)`
section
Call GetDCName
pop $0
Messagebox MB_OK "DCName=$0"
sectionEnd
Function GetDCName
;Push the domain controler name to the stack (or an empty string if no dc)
push $0
push $1
push $2
System::Call "netapi32::NetGetDCName(i0, i0, *w 0 r1) i.r2"
Dumpstate::debug
${If} 0 <> $1
${debug} "copy $1"
StrCpy $0 $1
${else}
${debug} "copy empty string"
StrCpy $0 ""
${endif}
${IfThen} $1 <> 0 ${|} System::Call "netapi32::NetApiBufferFree(ir1)" ${|}
Pop $2
Pop $1
Exch $0
FunctionEnd
Edit: thanks to Anders, here is the corrected function:
Function GetDCName
;Push the domain controler name to the stack (or an empty string if no dc)
push $0
push $1
push $2
System::Call "netapi32::NetGetDCName(i0, i0, *i 0 r1) i.r2" ;do not convert r1 to wchar, keep the pointer to free it
${If} $2 = 0
System::Call "*$1(&w${NSIS_MAX_STRLEN} .r0)" ;get the wchar from the pointer
${IfThen} $1 <> 0 ${|} System::Call "netapi32::NetApiBufferFree(ir1)" ${|} ;free the pointer
${else}
StrCpy $0 ""
${endif}
Pop $2
Pop $1
Exch $0
FunctionEnd
The <> operator is for numbers, not strings. This is not your only problem though; * can (normally) only be used with i, not t/m/w. The t/m/w types convert to and from C style strings but in your case you need the actual memory address since you have to pass it to the free function.
You need to pass *i0r1 to NetGetDCName, then if $2 is 0 then you can extract the string with the struct syntax (*$1(&w${NSIS_MAX_STRLEN}.r5) or *$1(w.r5)) and finally pass $1 untouched to the free function...

NSIS ExecDos plug-in - IsDone function works on some computers but not others

I have an NSIS installer that uses the ExecDOS plug-in to call a command line tool that runs SQL scripts. ExecDos is called in async mode and I then loop around updating a progress bar and calling the IsDone function until the command line tool finishes.
The problem I am having is that on some computers the IsDone check is wrongly reporting the process as finished when it is in fact still running and so it continues on in the background after the installer has finished. I cannot find any commonality between the computers it doesn't work on so I am hoping someone has some ideas or can spot an error in my code.
Function UpdateDatabase
!insertmacro SetBannerText "Updating database" 0 0
!insertmacro Log "About to update the database"
StrCpy $UpdateDatabase_PreviousScriptNumber 0
SetShellVarContext all
Push "ExecDos::End" # Add a marker for the loop to test for.
ExecDos::exec /NOUNLOAD /ASYNC '"$ComSpec" /S /C ""$INSTDIR\Database\Tool\DatabaseResetTool.Console.exe" -sp="$INSTDIR\Database\Scripts" -cs="Data Source=$DatabaseServer;Initial Catalog=$Database;User ID=sa;Password=*********;Persist Security Info=true;MultipleActiveResultSets=True;Language=English" -eoe"' '' '$APPDATA\Company\$ApplicationPrefix\Database Update.log'
Pop $DatabaseUpdate_ProcessHandle
Loop:
ExecDos::isdone /NOUNLOAD $DatabaseUpdate_ProcessHandle
Pop $DatabaseUpdate_Done
StrCpy $0 "$APPDATA\Company\$ApplicationPrefix\Database Update.log"
StrCpy $1 "$TEMP\Database Update.log"
StrCpy $2 0
System::Call 'kernel32::CopyFile(t r0, t r1, b r2) ?e'
${LineRead} "$TEMP\Database Update.log" "-2" $DatabaseUpdate_LogFileLine
${StrTok} $DatabaseUpdate_LogFileLineEndPart $DatabaseUpdate_LogFileLine "(" "L" "1"
${StrTok} $DatabaseUpdate_ScriptNumber $DatabaseUpdate_LogFileLineEndPart "/" "0" "1"
${StrTok} $DatabaseUpdate_ScriptCountUnprocessed $DatabaseUpdate_LogFileLineEndPart "/" "1" "1"
${StrTok} $DatabaseUpdate_ScriptCount $DatabaseUpdate_ScriptCountUnprocessed ")" "0" "1"
${If} $DatabaseUpdate_ScriptNumber != ""
${AndIf} $DatabaseUpdate_ScriptCount != ""
FloatOp::D $DatabaseUpdate_ScriptNumber $DatabaseUpdate_ScriptCount
FloatOp::M $0 100
${If} $UpdateDatabase_PreviousScriptNumber != $DatabaseUpdate_ScriptNumber
!insertmacro SetBannerText "Updating database$\r$\n$\r$\nRunning script $DatabaseUpdate_ScriptNumber of $DatabaseUpdate_ScriptCount" 0 $0
StrCpy $UpdateDatabase_PreviousScriptNumber $DatabaseUpdate_ScriptNumber
${EndIf}
${EndIf}
Sleep 200
StrCmp $DatabaseUpdate_Done "0" Loop Done
Done:
Push "$TEMP\Database Update.log"
Push "exception"
Call FileSearch
Pop $0
Pop $1
${If} $0 != "0"
StrCpy $InstallError 1
!insertmacro LogError "Database update failed. Please review the 'Database Update.log' to see the detail error message" ""
${EndIf}
FunctionEnd
I don't know what the exact problem is but you have some other issues with your script you should probably fix:
System::Call 'kernel32::CopyFile(t r0, t r1, b r2) ?e' b is not a valid system plugin type, use i. ?e pushes to the stack, you never pop this value and you don't check if the copy succeeded. Replace it with System::Call 'kernel32::CopyFile(t r0, t r1, i 0)i.r0' and check with ${If} $0 = 0 ...deal with error
You are using $ComSpec and "" which I assume is to deal with cmd.exe quote issues, ExecDos uses pipes so redirection should work without bringing cmd.exe into the mix. Try executing just ExecDos::exec /NOUNLOAD /ASYNC '"$INSTDIR\Da...
You don't really control all files in $Temp, use $Pluginsdir as your scratch space
You never pop and test for Push "ExecDos::End"
Some other things you might want to investigate:
SetShellVarContext all converts $APPDATA to the common appdata/programdata so make sure you are admin.
Try a longer sleep...
Is it possible that the ?e value you never pop ends up filling the stack so that the isdone parameter is never pushed to the stack so the function ends up with a invalid parameter?
To debug this you should be able to replace the call to isdone with System::Call 'kernel32::WaitForSingleObject(i $DatabaseUpdate_ProcessHandle
,i 0)i.r0' and compare the value in $0 to the return values listed on MSDN.

Resources