How do you pass a POINT struct to WindowFromPoint() in system plugin syntax?
System::Alloc 8
Pop $0
System::Call 'USER32::GetCursorPos(ir0)'
System::Call '*$0(i.r8,i.r9)'
System::Free $0
System::Call 'USER32::WindowFromPoint(ir8,ir9)i.r1'
System::Call 'USER32::GetClassName(ir1,t.r2,i ${NSIS_MAX_STRLEN})'
MessageBox mb_ok "$8x$9: $1=$2"
Related
I have a python app that I'm installing with NSIS. What I cant figure out is how you get an App into the "priority list" that is in the Focus Assist area of windows. The dialog suggests you can add any app - but you can't. Its a really small amount of apps that are available. How can you make an app available to this Focus Assist priority list?
It might have something to do with AUMID. You need to set this when you install your app. When using NSIS these two options don't seem to fix this issue
https://github.com/safing/nsis-shortcut-properties
https://nsis.sourceforge.io/WinShell_plug-in
I'm not sure if setting the ToastActivatorCLSID is going to help but here is some NSIS code to do that. I noticed that OneDrive also sets the ID so this function sets both:
!include LogicLib.nsh
!include Win\COM.nsh
!include Win\Propkey.nsh
Function SetLnkToastActivatorCLSIDAndAUMI
System::Store S
Pop $3
Pop $4
Pop $6
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r1 ""
${If} $1 P<> 0
${IUnknown::QueryInterface} $1 '("${IID_IPersistFile}",.r2)'
${IUnknown::Release} $1 ""
${If} $2 P<> 0
${IPersistFile::Load} $2 '(r3,${STGM_READWRITE})i.r0'
${If} $0 >= 0
${IUnknown::QueryInterface} $2 '("${IID_IPropertyStore}",.r1)'
${If} $1 P<> 0
System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ToastActivatorCLSID})p.r3'
System::Call '*(&g16"$4")p.r4'
System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_CLSID},,p$4)p.r5'
${IPropertyStore::SetValue} $1 '($3,$5)i.r0'
System::Free $4
${If} $6 != ""
System::Call '*$3${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})'
System::Call '*(&w${NSIS_MAX_STRLEN}s)p.r4' $6
${V_SetPointer} $5 ${VT_LPWSTR} $4
${IPropertyStore::SetValue} $1 '($3,$5)'
System::Free $4
${EndIf}
${IPropertyStore::Commit} $1 ""
System::Free $3
System::Free $5
${IUnknown::Release} $1 ""
${If} $0 >= 0
${IPersistFile::Save} $2 '(p0,1)'
${EndIf}
${EndIf}
${EndIf}
${IUnknown::Release} $2 ""
${EndIf}
${EndIf}
System::Store L
FunctionEnd
!macro SetLnkToastActivatorCLSIDAndAUMI lnkpath clsid aumi
Push "${aumi}"
Push ${clsid}
Push "${lnkpath}"
Call SetLnkToastActivatorCLSIDAndAUMI
!macroend
Section
!define MYTOASTCLASSGUID {696a4cab-ef17-48d1-8bb5-6ebc36b94d0a} ; Replace this with your own CLSID
CreateShortcut /NoWorkingDir "$SMPrograms\NSIS_toast_test.lnk" "$sysdir\winver.exe"
!insertmacro SetLnkToastActivatorCLSIDAndAUMI "$SMPrograms\NSIS_toast_test.lnk" ${MYTOASTCLASSGUID} "My.AppModel.Id.Goes.Here"
SectionEnd
Is it possible to paint to a Static control in NSIS? If it is how do I go about declaring variables such as a PAINTSTRUCT and etc? I can do this easily in regular C but using NSIS and the assembly type language is throwing me.
Below I'm trying to draw a border on a static control:
Var MainWndSubProc
Function MainWndSubProc
${If} $2 = ${WM_DRAWITEM}
# I'm assuming $3 = the WPARAM and $4 = LPARAM?
$LOWORD $R0 $3 # get id of window we are painting
${If} $R0 == $myStaticId
# C code which works: how to translate to NSIS 'assembly'?
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(args.hwnd, &ps);
RECT mRect;
GetClientRect(args.hwnd, &mRect);
HBRUSH brush = CreateSolidBrush(RGB(50, 50, 50));
HPEN pen = CreatePen(PS_SOLID, 5, RGB(191, 191, 191));
SelectObject(hdc, pen);
SelectObject(hdc, brush);
Rectangle(hdc, 0, 0, mRect.right, mRect.bottom);
DeleteObject(pen);
DeleteObject(brush);
EndPaint(args.hwnd, &ps);
# NSIS translation
# how to declare PAINTSTRUCT
$LOWORD $R0 $4
System::Call `user32::BeginPaint(i $R0, i R1)`
.. ?
${EndIf}
${EndIf}
FunctionEnd
Function MyGUIInit
${WndSubclass_Subclass} $HWNDPARENT MainWndSubProc $MainWndSubProc $MainWndSubProc
FunctionEnd
Types <= 64-bit are stored as strings in normal NSIS variables. For larger types you need to use the system plug-in struct syntax:
OutFile test.exe
RequestExecutionLevel user
Page InstFiles
!include WinMessages.nsh
!include nsDialogs.nsh
!include LogicLib.nsh
!include Colors.nsh
!include WndSubclass.nsh
Var MainWndSubProc
Var hStaticCtrl
Function MainWndSubProc
${If} $2 = ${WM_DRAWITEM}
System::Call '*$4(i,i,i,i,i,i.r5,i.r6,i,i,i.r8,i.r9)' ; Get HWND, HDC and size from DRAWITEMSTRUCT
${If} $hStaticCtrl = $5
System::Call 'GDI32::CreateSolidBrush(i 0x2277ee)i.s' ; Just made up a color here
System::Call 'GDI32::SelectObject(ir6,is)i.s'
${RGB} $7 191 191 191
System::Call 'GDI32::CreatePen(i${PS_SOLID}, i5, i "0x$7")i.s'
System::Call 'GDI32::SelectObject(ir6,is)i.s'
System::Call 'GDI32::Rectangle(ir6, i0, i0, ir8, ir9)'
System::Call 'GDI32::SelectObject(ir6,is)i.s'
System::Call 'GDI32::DeleteObject(is)'
System::Call 'GDI32::SelectObject(ir6,is)i.s'
System::Call 'GDI32::DeleteObject(is)'
${EndIf}
${EndIf}
FunctionEnd
Function .onGUIInit
; Your example failed to show how you create the static control so I'm forced to just create one here at run-time
GetDlgItem $0 $hwndparent 2 ; Find cancel button so we can put our control there
ShowWindow $0 0
System::Call '*(i,i,i,i)i.r1'
System::Call 'USER32::GetWindowRect(ir0,ir1)'
System::Call 'USER32::MapWindowPoints(i0,i$hwndparent,ir1,i2)'
System::Call '*$1(i.r4,i.r5,i.r6,i.r7)'
System::Free $1
IntOp $6 $6 - $4
IntOp $7 $7 - $5
System::Call 'USER32::CreateWindowEx(i0, t "Static", i0, i${WS_CHILD}|${WS_VISIBLE}|${SS_OWNERDRAW}, ir4, ir5, ir6, ir7, i$hwndparent, i0, i0, i0)i.r0'
StrCpy $hStaticCtrl $0
${WndSubclass_Subclass} $HWNDPARENT MainWndSubProc $MainWndSubProc $MainWndSubProc
FunctionEnd
Section
SectionEnd
The quality of your C code leads me to believe that you are not ready to write this kind of code. If you look at the documentation for DRAWITEMSTRUCT you will see this comment for the hDC member: "this device context must be used when performing drawing operations on the control." but you decided to call BeginPaint! You also fail to restore the DC and you delete objects that are selected into a DC and that is not allowed.
I'm using WindowsXP, NSIS 2.46 with nsSCM plug-in, and have 4 machines with nearly the same environment(hardware + software) since they come from a same GHOST image but with very limited changes on application layer(no any system settings changed).
I'm using NSIS installer to install my application to them, detail process are:
Stop 'Apache2.2' service.
Copy files to Apache root folder.
CreateShortCut
With script:
CreateShortCut "$SMPROGRAMS\MyApp\Stop.lnk" "$SYSDIR\sc.exe" "stop MyAppService" "C:\WINDOWS\system32\SHELL32.dll" 27 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\MyApp\ShowDemo.lnk" "$PROGRAMFILES\MyAppPath\MyAppShowDemoHelper.exe" "-b 102" "C:\WINDOWS\system32\SHELL32.dll" 24 SW_SHOWMINIMIZED
Start 'Apache2.2' service.
With script:
nsSCM::Start /NOUNLOAD "Apache2.2"
Pop $0 ; return error/success
${If} $0 == "success"
MessageBox MB_ICONINFORMATION|MB_OK "Successfully started 'Apache2.2' service"
${Else}
MessageBox MB_ICONSTOP|MB_OK "Failed to start 'Apache2.2' service with result: $0, Contact help desk or start it manually!"
${EndIf}
===========================
Now the problem is the step 4, in two of those machines, it always popup ERROR and always need manually to start the service(with no error), but meantime, the shortcuts are created successfully.
I've checked system logs, Apache logs, but no error logs/message could be found.
I spend one day and tried everything, at last, I found once I remove the Step 3, everything is fine, so any idea Why?
[Edit0]:
For work around, I have to switch Step 3 and 4, at least now it works good.
CreateShortcut should have no impact on plugins so this is rather strange. It would be nice if we could narrow this down to a problem with the plugin API in NSIS or a problem in the plugin itself. Maybe you could try the SimpleSC plugin?
Here is some code that does it manually with the system plugin. It is not pretty but should display a relevant error code if it fails.
Section
!define MyServiceName "Spooler" ;"stisvc"
ExecWait '"$SysDir\cmd.exe" /c net stop "${MyServiceName}"' ; Hack to make sure the service is stopped so we can try starting it again
Sleep 1111
InitPluginsDir
CreateShortcut "$PluginsDir\Test1.lnk" "$ExePath" ; Create some dummy shortcuts to possibly trigger the bug
CreateShortcut "$PluginsDir\Test2.lnk" "$ExePath"
!include LogicLib.nsh
!include Util.nsh
!define ERROR_SERVICE_ALREADY_RUNNING 1056
!define SC_MANAGER_CONNECT 0x0001
!define SERVICE_QUERY_STATUS 0x0004
!define SERVICE_START 0x0010
!define SERVICE_PAUSE_CONTINUE 0x0040
!define SERVICE_CONTROL_CONTINUE 3
!define SERVICE_STOPPED 1
!define SERVICE_START_PENDING 2
!define SERVICE_STOP_PENDING 3
!define SERVICE_RUNNING 4
!define SERVICE_CONTINUE_PENDING 5
!define SERVICE_PAUSE_PENDING 6
!define SERVICE_PAUSED 7
!macro WaitForServiceRunningStatus_
System::Store S
Pop $6
Pop $1
System::Call KERNEL32::GetTickCount()i.r7
System::Call '*(i,i,i,i,i,i,i)i.r2'
loop:
System::Call 'ADVAPI32::QueryServiceStatus(ir1, ir2)i.r3'
System::Call '*$2(i,i.r4)'
${If} $3 <> 0
${If} $4 = ${SERVICE_RUNNING}
DetailPrint 'Service is now running.'
${Else}
Sleep 250
System::Call KERNEL32::GetTickCount()i.r8
${IfThen} $8 < $7 ${|} StrCpy $7 $8 ${|} ; Reset on GetTickCount rollover
IntOp $8 $8 - $7
IntCmpU $8 $6 "" loop
DetailPrint 'Timeout! Service status is $4'
${EndIf}
${EndIf}
System::Free $2
System::Store L
!macroend
!macro WaitForServiceRunningStatus hSC MsTimeout
Push ${hSC}
Push ${MsTimeout}
${CallArtificialFunction} WaitForServiceRunningStatus_
!macroend
System::Call 'ADVAPI32::OpenSCManager(t"", i0, i${SC_MANAGER_CONNECT})i.r0 ?e'
Pop $9
${If} $0 = 0
DetailPrint 'OpenSCManager(t"", i0, i${SC_MANAGER_CONNECT}) failed with error $9'
${Else}
System::Call 'ADVAPI32::OpenService(ir0, t"${MyServiceName}", i${SERVICE_QUERY_STATUS}|${SERVICE_START}|${SERVICE_PAUSE_CONTINUE})i.r1 ?e'
Pop $9
${If} $1 = 0
DetailPrint 'OpenService("${MyServiceName}") failed with error $9'
${Else}
System::Call '*(i,i,i,i,i,i,i)i.r2'
System::Call 'ADVAPI32::QueryServiceStatus(ir1, ir2)i.r3 ?e'
Pop $9
${If} $3 = 0
DetailPrint 'QueryServiceStatus failed with error $9'
StrCpy $4 0 ; We failed, set to unused code so we stop processing
${Else}
System::Call '*$2(i.r3,i.r4,i.r5,i,i,i,i)'
IntFmt $3 "%#x" $3
IntFmt $4 "%#x" $4
IntFmt $5 "%#x" $5
DetailPrint 'QueryServiceStatus: Type=$3, CurrentState=$4 ControlsAccepted=$5'
${EndIf}
${If} $4 = ${SERVICE_PAUSE_PENDING}
${OrIf} $4 = ${SERVICE_PAUSED}
System::Call 'ADVAPI32::ControlService(ir1, i${SERVICE_CONTROL_CONTINUE}, ir2)i.r3 ?e'
Pop $9
${If} $3 = 0
DetailPrint 'ControlService(SERVICE_CONTROL_CONTINUE) failed with error $9'
${Else}
DetailPrint 'Resuming "${MyServiceName}"...'
!insertmacro WaitForServiceRunningStatus $1 5000
${EndIf}
${ElseIf} $4 = ${SERVICE_CONTINUE_PENDING}
!insertmacro WaitForServiceRunningStatus $1 5000
${ElseIf} $4 >= ${SERVICE_STOPPED}
${If} $4 = ${SERVICE_RUNNING}
DetailPrint "Service already running"
${Else}
System::Call 'ADVAPI32::StartService(ir1, i0, i0)i.r3 ?e'
Pop $9
${If} $3 = 0
DetailPrint 'StartService failed with error $9'
${Else}
DetailPrint 'Starting "${MyServiceName}"...'
!insertmacro WaitForServiceRunningStatus $1 9000
${EndIf}
${EndIf}
${EndIf}
System::Free $2
System::Call 'ADVAPI32::CloseServiceHandle(i.r1)'
${EndIf}
System::Call 'ADVAPI32::CloseServiceHandle(i.r0)'
${EndIf}
SectionEnd
I run a certain process via ExecShell from my NSIS installer. That process takes sometime to start (5 - 40 seconds maybe), during which I want the NSIS window to remain visible.
The problem is that while the process itself starts almost instantly, it is sometime before a user would see anything, thus I want the NSIS installer window to remain visible UNTIL the started process's main window (or any window for that matter is shown).
What I need to know thus is how do I get processid from ExecShell (can't use Exec or ExecWait for other reasons), and then how do I use that process id to see if the window has been shown (I know I can do it via a simple sleep, check, goto loop, so basically I am trying to figure out the check part of it)?
So, how exactly can I know if the process I spawned using ShellExec has shown a GUI. This needs to work in Windows XP SP3 and up.
Thank you.
RequestExecutionLevel user
Page InstFiles
!include LogicLib.nsh
!include WinMessages.nsh ; For SW_*
Var IsSlowFakeApp ; We can also pretend to be a silly little app that is slow to start up, this is just so the example code has no external dependencies
Function .onInit
StrCpy $0 $CMDLINE 1 -1
${If} $0 == "?"
StrCpy $IsSlowFakeApp 1
Sleep 3333
${EndIf}
FunctionEnd
Function StartMyAppAndWaitForWindow
StrCpy $0 "$ExePath" ; Application
StrCpy $1 "?" ; Parameters
!define SEE_MASK_NOCLOSEPROCESS 0x40
DetailPrint 'Starting "$0" $1'
System::Store S
System::Call '*(i60,i${SEE_MASK_NOCLOSEPROCESS},i$hwndparent,i0,tr0,tr1,i0,i${SW_SHOW},i,i,i,i,i,i,i)i.r0'
System::Call 'SHELL32::ShellExecuteEx(ir0)i.r1'
${If} $1 <> 0
System::Call '*$0(i,i,i,i,i,i,i,i,i,i,i,i,i,i,i.r1)'
System::Call 'USER32::WaitForInputIdle(ir1,2000)i'
System::Call 'KERNEL32::GetProcessId(ir1)i.r2' ; MSDN says this function is XP.SP1+
StrCpy $3 $2 ; Not found a window yet, keep looping
; Call EnumWindows until we find a window matching our target process id in $2
System::Get '(i.r5, i) iss'
Pop $R0
callEnumWindows:
System::Call 'USER32::EnumWindows(k R0, i) i.s'
loopEnumWindows:
Pop $4
StrCmp $4 "callback1" 0 doneEnumWindows
System::Call 'USER32::GetWindowThreadProcessId(ir5,*i0r4)'
${If} $4 = $2
System::Call 'USER32::IsWindowVisible(ir5)i.r4'
${IfThen} $4 <> 0 ${|} StrCpy $3 0 ${|} ; Found a visible Window
${EndIf}
Push $3 ; EnumWindows callback's return value
System::Call "$R0"
Goto loopEnumWindows
doneEnumWindows:
${If} $3 <> 0
Sleep 1000
Goto callEnumWindows
${EndIf}
System::Free $R0
; Hide installer while app runs
/*HideWindow
System::Call 'KERNEL32::WaitForSingleObject(ir1,i-1)'
BringToFront*/
System::Call 'KERNEL32::CloseHandle(ir1)'
${EndIf}
System::Free $0
System::Store L
FunctionEnd
Section
${If} $IsSlowFakeApp <> 0
SetCtlColors $HWNDPARENT 0xffffff 0xdd0000
FindWindow $0 "#32770" "" $HWNDPARENT
SetCtlColors $0 0xffffff 0xdd0000
DetailPrint "This is a fake slow app and it will close soon..."
Sleep 5555
Quit
${Else}
Call StartMyAppAndWaitForWindow
${EndIf}
SectionEnd
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...