You are here

ProcFunc - NSIS Process Function Header

102 posts / 0 new
Last post
wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
ProcFunc - NSIS Process Function Header

Well this little project of mine has grown. The code's pretty long now, so it's available as a download. More macros available -

[See full code at end of post]

${ProcessExists}
Determines if a given process exists by name or PID, and returns the Process ID (PID).

${GetProcessPath}
Determines first if a process exists, and returns the full path by name or PID.
WARNING: This will only succeed if the calling process has sufficient rights to open the target process.

${GetProcessParent}
If the process exists, returns the parent process's PID.

${GetProcessName}
If the process exists, returns the name.

${EnumProcessPaths}
Passes all running process' full path and PID to a user defined function (works like the GetDrives macro).
WARNING: This will only succeed if the calling process has sufficient rights to open the target process.

${ProcessWait}
Will pause script execution until the given process exists or until timeout, by name. Will poll for the process every 500ms. Returns the process's PID, or -1 if timed out.
WARNING: this function is mildly CPU intensive, approx 2-10%. Be absolutely sure to include some value for RequestExecutionLevel. Omitting it will more than double CPU usage for this command on Windows Vista+.

${ProcessWait2}
Same as ${ProcessWait}, but requires the FindProcDLL plugin, polls every 250ms, and is slightly less CPU intensive.

${ProcessWaitClose}
Will pause script execution until the given process no longer exists, by name or PID. Can specify a timeout value (how long to wait), or wait indefinitely. Returns the PID of the closing process if successful, -1 if timed out.

${CloseProcess}
Closes a process by name or PID by sending the WM_CLOSE message to all windows owned by the process. This is the MS recommended method for closing a process. It will give you the opportunity, for example, to save an unsaved txt file open in notepad. This command will return immediately after sending the messages, so it's advisable to check when the process actually closes, ie with ${ProcessWaitClose}. Returns the closed process's PID.

${TerminateProcess}
Forcibly closes a process by name or PID. Returns the terminated process's PID.

${Execute}
Runs a process, supports commandline arguments. Allows you to optionally specify a working directory and returns the process's PID if successful, 0 if it fails.

Full code embedded:

/*
_____________________________________________________________________________

                      Process Functions Header v2.2
_____________________________________________________________________________

 2008-2010 Erik Pilsits aka wraithdu
 License: zlib/libpng

 See documentation for more information about the following functions.

 Usage in script:
 1. !include "ProcFunc.nsh"
 2. [Section|Function]
      ${ProcFunction} "Param1" "Param2" "..." $var
    [SectionEnd|FunctionEnd]


 ProcFunction=[GetProcessPID|GetProcessPath|GetProcessParent|GetProcessName|
               EnumProcessPaths|ProcessWait|ProcessWait2|ProcessWaitClose|
               CloseProcess|TerminateProcess|Execute]

 There is also a LogicLib extension:
    ${If} ${ProcessExists} file.exe
      ...
    ${EndIf}

_____________________________________________________________________________

                       Thanks to:
_____________________________________________________________________________

Some functions based on work by Donald Miller and Phoenix1701@gmail.com

_____________________________________________________________________________

                       Individual documentation:
_____________________________________________________________________________

${ProcessExists} "[process]"
	"[process]"			; Name or PID

	Use with a LogicLib conditional command like If or Unless.
	Evaluates to true if the process exists or false if it does not or
	the CreateToolhelp32Snapshot fails.

${GetProcessPID} "[process]" $var
	"[process]"			; Name or PID
	
	$var(output)		; -2 - CreateToolhelp32Snapshot failed
						;  0 - process does not exist
						; >0 - PID

${GetProcessPath} "[process]" $var
	"[process]"			; Name or PID
	
	$var(output)		; -2 - CreateToolhelp32Snapshot failed
						; -1 - OpenProcess failed
						;  0 - process does not exist
						; Or path to process

${GetProcessParent} "[process]" $var
	"[process]"			; Name or PID

	$var(output)		; -2 - CreateToolhelp32Snapshot failed
						;  0 - process does not exist
						; Or PPID

${GetProcessName} "[PID]" $var
	"[PID]"				; PID

	$var(output)		; -2 - CreateToolhelp32Snapshot failed
						;  0 - process does not exist
						; Or process name

${EnumProcessPaths} "Function" $var
	"Function"          ; Callback function
	$var(output)		; -2 - EnumProcesses failed
						;  1 - success

	Function "Function"
		Pop $var1			; matching path string
		Pop $var2			; matching process PID
		...user commands
		Push [1/0]			; must return 1 on the stack to continue
							; must return some value or corrupt the stack
							; DO NOT save data in $0-$9
	FunctionEnd

${ProcessWait} "[process]" "[timeout]" $var
	"[process]"			; Name
	"[timeout]"			; -1 - do not timeout
						; >0 - timeout in milliseconds

	$var(output)		; -2 - CreateToolhelp32Snapshot failed
						; -1 - operation timed out
						; Or PID

${ProcessWait2} "[process]" "[timeout]" $var
	"[process]"			; Name
	"[timeout]"			; -1 - do not timeout
						; >0 - timeout in milliseconds

	$var(output)		; -1 - operation timed out
						; Or PID

${ProcessWaitClose} "[process]" "[timeout]" $var
	"[process]"			; Name
	"[timeout]"			; -1 - do not timeout
						; >0 - timeout in milliseconds

	$var(output)		; -1 - operation timed out
						;  0 - process does not exist
						; Or PID of ended process

${CloseProcess} "[process]" $var
	"[process]"			; Name or PID

	$var(output)		; 0 - process does not exist
						; Or PID of ended process

${TerminateProcess} "[process]" $var
	"[process]"			; Name or PID

	$var(output)		; -1 - operation failed
						;  0 - process does not exist
						; Or PID of ended process

${Execute} "[command]" "[working_dir]" $var
	"[command]"			; '"X:\path\to\prog.exe" arg1 arg2 "arg3 with space"'
	"[working_dir]"		; Working directory ("X:\path\to\dir") or nothing ("")

	$var(output)		; 0 - failed to create process
						; Or PID
*/


;_____________________________________________________________________________
;
;                         Macros
;_____________________________________________________________________________
;
; Change log window verbosity (default: 3=no script)
;
; Example:
; !include "ProcFunc.nsh"
; ${PROCFUNC_VERBOSE} 4   # all verbosity
; ${PROCFUNC_VERBOSE} 3   # no script

!ifndef PROCFUNC_INCLUDED
!define PROCFUNC_INCLUDED

!include Util.nsh
!include LogicLib.nsh

!verbose push
!verbose 3
!ifndef _PROCFUNC_VERBOSE
	!define _PROCFUNC_VERBOSE 3
!endif
!verbose ${_PROCFUNC_VERBOSE}
!define PROCFUNC_VERBOSE `!insertmacro PROCFUNC_VERBOSE`
!verbose pop

!macro PROCFUNC_VERBOSE _VERBOSE
	!verbose push
	!verbose 3
	!undef _PROCFUNC_VERBOSE
	!define _PROCFUNC_VERBOSE ${_VERBOSE}
	!verbose pop
!macroend

!define PROCESS_QUERY_INFORMATION 0x0400
!define PROCESS_TERMINATE 0x0001
!define PROCESS_VM_READ 0x0010
!define SYNCHRONIZE 0x00100000

!define WAIT_TIMEOUT 0x00000102

!ifdef NSIS_UNICODE
	!define _PROCFUNC_WSTRING "&w260"
!else
	!define _PROCFUNC_WSTRING "&w520"
!endif

!macro ProcessExists
	!error "ProcessExists has been renamed to GetProcessPID"
!macroend
!macro _ProcessExists _a _b _t _f
	!insertmacro _LOGICLIB_TEMP
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${_b}`
	${CallArtificialFunction} LLProcessExists_
	IntCmp $_LOGICLIB_TEMP 0 `${_f}`
	Goto `${_t}`
	!verbose pop
!macroend
!define ProcessExists `"" ProcessExists`

!macro GetProcessPID
!macroend
!define GetProcessPID "!insertmacro GetProcessPIDCall"
!macro GetProcessPIDCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push 0
	Push `${process}`
	!ifdef CallArtificialFunction_TYPE ; macro nesting disallowed, breaks otherwise if used from WaitClose
	${CallArtificialFunction2} ProcFuncs_
	!else
	${CallArtificialFunction} ProcFuncs_
	!endif
	Pop ${outVar}
	!verbose pop
!macroend

!macro GetProcessPath
!macroend
!define GetProcessPath "!insertmacro GetProcessPathCall"
!macro GetProcessPathCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push 1
	Push `${process}`
	${CallArtificialFunction} ProcFuncs_
	Pop ${outVar}
	!verbose pop
!macroend

!macro GetProcessParent
!macroend
!define GetProcessParent "!insertmacro GetProcessParentCall"
!macro GetProcessParentCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push 2
	Push `${process}`
	${CallArtificialFunction} ProcFuncs_
	Pop ${outVar}
	!verbose pop
!macroend

!macro GetProcessName
!macroend
!define GetProcessName "!insertmacro GetProcessNameCall"
!macro GetProcessNameCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push 6
	Push `${process}`
	${CallArtificialFunction} ProcFuncs_
	Pop ${outVar}
	!verbose pop
!macroend

!macro EnumProcessPaths
!macroend
!define EnumProcessPaths "!insertmacro EnumProcessPathsCall"
!macro EnumProcessPathsCall user_func outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push $0
	GetFunctionAddress $0 `${user_func}`
	Push `$0`
	${CallArtificialFunction} EnumProcessPaths_
	Exch
	Pop $0
	Pop ${outVar}
	!verbose pop
!macroend

!macro ProcessWait
!macroend
!define ProcessWait "!insertmacro ProcessWaitCall"
!macro ProcessWaitCall process timeout outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${timeout}`
	Push `${process}`
	${CallArtificialFunction} ProcessWait_
	Pop ${outVar}
	!verbose pop
!macroend

!macro ProcessWait2
!macroend
!define ProcessWait2 "!insertmacro ProcessWait2Call"
!macro ProcessWait2Call process timeout outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${timeout}`
	Push `${process}`
	${CallArtificialFunction} ProcessWait2_
	Pop ${outVar}
	!verbose pop
!macroend

!macro ProcessWaitClose
!macroend
!define ProcessWaitClose "!insertmacro ProcessWaitCloseCall"
!macro ProcessWaitCloseCall process timeout outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${timeout}`
	Push `${process}`
	${CallArtificialFunction} ProcessWaitClose_
	Pop ${outVar}
	!verbose pop
!macroend

!macro CloseProcess
!macroend
!define CloseProcess "!insertmacro CloseProcessCall"
!macro CloseProcessCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${process}`
	${CallArtificialFunction} CloseProcess_
	Pop ${outVar}
	!verbose pop
!macroend

!macro TerminateProcess
!macroend
!define TerminateProcess "!insertmacro TerminateProcessCall"
!macro TerminateProcessCall process outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${process}`
	${CallArtificialFunction} TerminateProcess_
	Pop ${outVar}
	!verbose pop
!macroend

!macro Execute
!macroend
!define Execute "!insertmacro ExecuteCall"
!macro ExecuteCall cmdline wrkdir outVar
	!verbose push
	!verbose ${_PROCFUNC_VERBOSE}
	Push `${wrkdir}`
	Push `${cmdline}`
	${CallArtificialFunction} Execute_
	Pop ${outVar}
	!verbose pop
!macroend

!macro ProcFuncs_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process / PID
	Pop $1 ; mode
	
	Push 0 ; set return value if not found
	
	; set mode of operation in $1
	${Select} $1 ; mode 0 = GetProcessPID, mode 1 = GetProcessPath, mode 2 = GetProcessParent
		${Case} 0
			StrCpy $2 $0 4 -4
			${If} $2 == ".exe"
				; exists from process name
				StrCpy $1 0
			${Else}
				; exists from pid
				StrCpy $1 1
			${EndIf}
		${Case} 1
			StrCpy $2 $0 4 -4
			${If} $2 == ".exe"
				; get path from process name
				StrCpy $1 2
			${Else}
				; get path from pid
				StrCpy $1 3
			${EndIf}
		${Case} 2
			StrCpy $2 $0 4 -4
			${If} $2 == ".exe"
				; get parent from process name
				StrCpy $1 4
			${Else}
				; get parent from pid
				StrCpy $1 5
			${EndIf}
	${EndSelect}
	
	System::Call '*(&l4,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING})i .r2' ; $2 = PROCESSENTRY32W structure
	; take system process snapshot in $3
	System::Call 'kernel32::CreateToolhelp32Snapshot(i 2, i 0)i .r3'
	${Unless} $3 = -1
		System::Call 'kernel32::Process32FirstW(i r3, i r2)i .r4'
		${Unless} $4 = 0
			${Do}
				${Select} $1
					${Case3} 0 2 4
						; get process name in $5
						System::Call '*$2(i,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING} .r5)'
					${Case4} 1 3 5 6
						; get process PID in $5
						System::Call '*$2(i,i,i .r5)'
				${EndSelect}
				; is this process the one we are looking for?
				${If} $5 == $0 ; string test works ok for numeric PIDs as well
					${Select} $1 ; mode 0/1 = GetProcessPID, mode 2/3 = GetProcessPath, mode 4/5 = GetProcessParent, mode 6 = GetProcessName
						${Case2} 0 1
							; return pid
							Pop $5 ; old return value
							System::Call '*$2(i,i,i .s)'; process pid to stack
						${Case2} 2 3
							; return full path
							Pop $5
							; open process
							System::Call '*$2(i,i,i .s)'; process pid to stack
							System::Call 'kernel32::OpenProcess(i ${PROCESS_QUERY_INFORMATION}|${PROCESS_VM_READ}, i 0, i s)i .r5' ; process handle to $5
							${Unless} $5 = 0
								; full path to stack
								System::Call 'psapi::GetModuleFileNameExW(i r5, i 0, w .s, i ${NSIS_MAX_STRLEN})'
								System::Call 'kernel32::CloseHandle(i r5)'
							${Else}
								Push -1 ; OpenProcess failure return value
							${EndUnless}
						${Case2} 4 5
							; return parent PID
							Pop $5
							System::Call '*$2(i,i,i,i,i,i,i .s)'; parent pid to stack
						${Case} 6
							; return base name
							Pop $5
							System::Call '*$2(i,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING} .s)'
					${EndSelect}
					${Break}
				${EndIf}
				System::Call 'kernel32::Process32NextW(i r3, i r2)i .r4'
			${LoopUntil} $4 = 0
			System::Call 'kernel32::CloseHandle(i r3)' ; close snapshot
		${EndUnless}
	${Else}
		Pop $5
		Push -2 ; function failure return value
	${EndUnless}
	System::Free $2 ; free buffer
	
	System::Store "l" ; restore registers
!macroend

!macro EnumProcessPaths_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; user_func
	
	StrCpy $1 1 ; OK to loop
	
	System::Alloc 1024
	Pop $2 ; process list buffer
	; get an array of all process ids
	System::Call 'psapi::EnumProcesses(i r2, i 1024, *i .r3)i .r4' ; $3 = sizeof buffer
	${Unless} $4 = 0
		IntOp $3 $3 / 4 ; Divide by sizeof(DWORD) to get $3 process count
		IntOp $3 $3 - 1 ; decrement for 0 base loop
		${For} $4 0 $3
			${IfThen} $1 != 1 ${|} ${Break} ${|}
			; get a PID from the array
			IntOp $5 $4 * 4 ; calculate offset
			IntOp $5 $5 + $2 ; add offset to original buffer address
			System::Call '*$5(i .r5)' ; get next PID = $5
			${Unless} $5 = 0
				System::Call 'kernel32::OpenProcess(i ${PROCESS_QUERY_INFORMATION}|${PROCESS_VM_READ}, i 0, i r5)i .r6'
				${Unless} $6 = 0 ; $6 is hProcess
					; get full path
					System::Call 'psapi::GetModuleFileNameExW(i r6, i 0, w .r7, i ${NSIS_MAX_STRLEN})i .r8' ; $7 = path
					${Unless} $8 = 0 ; no path
						System::Store "s" ; store registers in System's private stack
						Push $5 ; PID to stack
						Push $7 ; path to stack
						Call $0 ; user func must return 1 on the stack to continue looping
						System::Store "l" ; restore registers
						Pop $1 ; continue?
					${EndUnless}
					System::Call 'kernel32::CloseHandle(i r6)'
				${EndUnless}
			${EndUnless}
		${Next}
		Push 1 ; return value
	${Else}
		Push -2 ; function failure return value
	${EndUnless}
	System::Free $2 ; free buffer
	
	System::Store "l" ; restore registers
!macroend

!macro ProcessWait_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process
	Pop $1 ; timeout
	
	StrCpy $6 1 ; initialize loop
	StrCpy $7 0 ; initialize timeout counter
	
	System::Call '*(&l4,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING})i .r2' ; $2 = PROCESSENTRY32W structure
	${DoWhile} $6 = 1 ; processwait loop
		; take system process snapshot in $3
		System::Call 'kernel32::CreateToolhelp32Snapshot(i 2, i 0)i .r3'
		${Unless} $3 = -1
			System::Call 'kernel32::Process32FirstW(i r3, i r2)i .r4'
			${Unless} $4 = 0
				${Do}
					; get process name in $5
					System::Call '*$2(i,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING} .r5)'
					${If} $5 == $0
						; exists, return pid
						System::Call '*$2(i,i,i .s)'; process pid to stack ; process pid
						StrCpy $6 0 ; end loop
						${Break}
					${EndIf}
					System::Call 'kernel32::Process32NextW(i r3, i r2)i .r4'
				${LoopUntil} $4 = 0
				System::Call 'kernel32::CloseHandle(i r3)' ; close snapshot
			${EndUnless}
		${Else}
			Push -2
			${Break}
		${EndUnless}
		; timeout loop
		${If} $6 = 1
			${If} $1 >= 0
				IntOp $7 $7 + 500 ; increment timeout counter
			${AndIf} $7 >= $1 ; timed out, break loop
				Push -1 ; timeout return value
				${Break} ; end loop if timeout
			${EndIf}
			Sleep 500 ; pause before looping
		${EndIf}
	${Loop} ; processwaitloop
	System::Free $2 ; free buffer
	
	System::Store "l" ; restore registers
!macroend

!macro ProcessWait2_
	System::Store "s" ; store registers in System's private stack
	System::Store "P0" ; FindProcDLL return value
	Pop $0 ; process
	Pop $1 ; timeout
	
	StrCpy $2 0 ; initialize timeout counter
	
	${Do}
		FindProcDLL::FindProc $0
		${IfThen} $R0 = 1 ${|} ${Break} ${|}
		${If} $1 >= 0
			IntOp $2 $2 + 250
		${AndIf} $2 >= $1
			Push -1 ; timeout return value
			${Break}
		${EndIf}
		Sleep 250
	${Loop}
	
	${If} $R0 = 1 ; success, get pid
		${GetProcessPID} $0 $0
		Push $0 ; return pid
	${EndIf}
	
	System::Store "R0" ; restore registers
	System::Store "l"
!macroend

!macro ProcessWaitClose_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process / PID
	Pop $1 ; timeout
	
	; passed process name or pid
	StrCpy $2 $0 4 -4
	${If} $2 == ".exe"
		${GetProcessPID} $0 $0
	${EndIf}
	
	; else passed pid directly
	
	${Unless} $0 = 0
		System::Call 'kernel32::OpenProcess(i ${SYNCHRONIZE}, i 0, i r0)i .r2'
		${Unless} $2 = 0 ; $2 is hProcess
			System::Call 'kernel32::WaitForSingleObject(i r2, i $1)i .r1'
			${If} $1 = ${WAIT_TIMEOUT}
				Push -1 ; timed out
			${Else}
				Push $0 ; return pid of ended process
			${EndIf}
			System::Call 'kernel32::CloseHandle(i r2)'
		${Else}
			Push 0 ; failure return value
		${EndUnless}
	${Else}
		Push 0 ; failure return value
	${EndUnless}
	
	System::Store "l" ; restore registers
!macroend

!macro CloseProcess_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process / PID
	
	; passed process name or pid
	StrCpy $1 $0 4 -4
	${If} $1 == ".exe"
		${GetProcessPID} $0 $0
	${EndIf}
	
	; else passed pid directly
	
	${Unless} $0 = 0 ; $0 = target pid
		Push $0 ; return pid of process
		; use EnumWindows and a callback
		System::Get '(i .r1, i)i sr4' ; $1 = hwnd, $4 = callback#, s (stack) = source for return value
		Pop $3 ; $3 = callback address
		System::Call 'user32::EnumWindows(k r3, i)i' ; enumerate top-level windows
		${DoWhile} $4 == "callback1"
			System::Call 'user32::GetWindowThreadProcessId(i r1, *i .r2)i' ; $2 = pid that created the window
			${If} $2 = $0 ; match to target pid
				SendMessage $1 16 0 0 /TIMEOUT=1  ; send WM_CLOSE to all top-level windows owned by process, timeout immediately
			${EndIf}
			Push 1 ; callback return value; keep enumerating windows (returning 0 stops)
			StrCpy $4 "" ; clear callback#
			System::Call '$3' ; return from callback
		${Loop}
		System::Free $3 ; free callback
	${Else}
		Push 0 ; failure return value
	${EndUnless}
	
	System::Store "l" ; restore registers
!macroend

!macro TerminateProcess_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process / PID
	
	; passed process name or pid
	StrCpy $1 $0 4 -4
	${If} $1 == ".exe"
		${GetProcessPID} $0 $0
	${EndIf}
	
	; else passed pid directly
	
	${Unless} $0 = 0
		System::Call 'kernel32::OpenProcess(i ${PROCESS_TERMINATE}, i 0, i r0)i .r1'
		${Unless} $1 = 0 ; $1 is hProcess
			System::Call 'kernel32::TerminateProcess(i r1, i 0)i .r1'
			${If} $1 = 0 ; fail
				Push -1
			${Else}
				Push $0 ; return pid of ended process
			${EndIf}
			System::Call 'kernel32::CloseHandle(i r1)'
		${Else}
			Push 0 ; failure return value
		${EndUnless}
	${Else}
		Push 0 ; failure return value
	${EndUnless}
	
	System::Store "l" ; restore registers
!macroend

!macro Execute_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; cmdline
	Pop $1 ; wrkdir
	
	System::Alloc 68 ; 4*16 + 2*2 / STARTUPINFO structure = $2
	Pop $2
	System::Call '*$2(i 68)' ; set cb = sizeof(STARTUPINFO)
	System::Call '*(i,i,i,i)i .r3' ; PROCESS_INFORMATION structure = $3
	
	${If} $1 == ""
		StrCpy $1 "i"
	${Else}
		StrCpy $1 'w "$1"'
	${EndIf}
	
	System::Call `kernel32::CreateProcessW(i, w '$0', i, i, i 0, i 0, i, $1, i r2, i r3)i .r4` ; return 0 if fail
	${Unless} $4 = 0 ; failed to create process
		System::Call '*$3(i .r4, i .r5, i .r6)' ; read handles and PID
		System::Call 'kernel32::CloseHandle(i $4)' ; close hProcess
		System::Call 'kernel32::CloseHandle(i $5)' ; close hThread
		Push $6 ; return PID
	${Else}
		Push 0 ; return val if failed
	${EndUnless}
	
	System::Free $2 ; free STARTUPINFO struct
	System::Free $3 ; free PROCESS_INFORMATION struct
	
	System::Store "l" ; restore registers
!macroend

!macro LLProcessExists_
	System::Store "s" ; store registers in System's private stack
	Pop $0 ; process name
	
	StrCpy $_LOGICLIB_TEMP 0
	
	System::Call '*(&l4,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING})i .r2' ; $2 = PROCESSENTRY32W structure
	; take system process snapshot in $3
	System::Call 'kernel32::CreateToolhelp32Snapshot(i 2, i 0)i .r3'
	IntCmp $3 -1 done
		System::Call 'kernel32::Process32FirstW(i r3, i r2)i .r4'
		IntCmp $4 0 endloop
			loop:
				System::Call '*$2(i,i,i,i,i,i,i,i,i,${_PROCFUNC_WSTRING} .r5)'
				StrCmp $5 $0 0 next_process
					StrCpy $_LOGICLIB_TEMP 1
					Goto endloop
				next_process:
				System::Call 'kernel32::Process32NextW(i r3, i r2)i .r4'
				IntCmp $4 0 endloop
				Goto loop
			endloop:
			System::Call 'kernel32::CloseHandle(i r3)' ; close snapshot
	done:
	System::Free $2 ; free buffer
	
	System::Store "l" ; restore registers
!macroend

!endif ; PROCFUNC_INCLUDED

/****************************************************************************
	Functions
	=========
	
	HANDLE WINAPI OpenProcess(
	__in  DWORD dwDesiredAccess,
	__in  BOOL bInheritHandle,
	__in  DWORD dwProcessId
	);
	
	BOOL WINAPI CreateProcess(
	__in_opt     LPCTSTR lpApplicationName,
	__inout_opt  LPTSTR lpCommandLine,
	__in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
	__in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
	__in         BOOL bInheritHandles,
	__in         DWORD dwCreationFlags,
	__in_opt     LPVOID lpEnvironment,
	__in_opt     LPCTSTR lpCurrentDirectory,
	__in         LPSTARTUPINFO lpStartupInfo,
	__out        LPPROCESS_INFORMATION lpProcessInformation
	);
	
	typedef struct _STARTUPINFO {
	DWORD cb;
	LPTSTR lpReserved;
	LPTSTR lpDesktop;
	LPTSTR lpTitle;
	DWORD dwX;
	DWORD dwY;
	DWORD dwXSize;
	DWORD dwYSize;
	DWORD dwXCountChars;
	DWORD dwYCountChars;
	DWORD dwFillAttribute;
	DWORD dwFlags;
	WORD wShowWindow;
	WORD cbReserved2;
	LPBYTE lpReserved2;
	HANDLE hStdInput;
	HANDLE hStdOutput;
	HANDLE hStdError;
	} STARTUPINFO, 
	*LPSTARTUPINFO;
	
	typedef struct _PROCESS_INFORMATION {
	HANDLE hProcess;
	HANDLE hThread;
	DWORD dwProcessId;
	DWORD dwThreadId;
	} PROCESS_INFORMATION, 
	*LPPROCESS_INFORMATION;

	BOOL WINAPI EnumProcesses(
	__out  DWORD* pProcessIds,
	__in   DWORD cb,
	__out  DWORD* pBytesReturned
	);

	DWORD WINAPI GetModuleBaseName(
	__in      HANDLE hProcess,
	__in_opt  HMODULE hModule,
	__out     LPTSTR lpBaseName,
	__in      DWORD nSize
	);
	
	DWORD WINAPI GetModuleFileNameEx(
	__in      HANDLE hProcess,
	__in_opt  HMODULE hModule,
	__out     LPTSTR lpFilename,
	__in      DWORD nSize
	);

	BOOL WINAPI CloseHandle(
	__in  HANDLE hObject
	);
	
	DWORD WINAPI WaitForSingleObject(
	__in  HANDLE hHandle,
	__in  DWORD dwMilliseconds
	);
	
	BOOL WINAPI TerminateProcess(
	__in  HANDLE hProcess,
	__in  UINT uExitCode
	);
	
	BOOL EnumWindows(
	__in  WNDENUMPROC lpEnumFunc,
	__in  LPARAM lParam
	);
	
	DWORD GetWindowThreadProcessId(      
    __in  HWND hWnd,
    __out LPDWORD lpdwProcessId
	);
	
	BOOL PostMessage(      
    __in  HWND hWnd,
    __in  UINT Msg,
    __in  WPARAM wParam,
    __in  LPARAM lParam
	);

****************************************************************************/
digitxp
digitxp's picture
Offline
Last seen: 13 years 3 months ago
Joined: 2007-11-03 18:33
Sounds useful

for an ejectscript I have no idea how to do. This'll work perfectly as soon as I find how to do wildcards :(...

Insert original signature here with Greasemonkey Script.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Updated to include a ${ProcessExists} macro. Could be used to replace FindProcDLL (doesn't require an extra DLL and has a user configurable outVar).

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Added more macros, hopefully useful Smile

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Added ${ProcessWait} and ${ProcessWait2}.

digitxp
digitxp's picture
Offline
Last seen: 13 years 3 months ago
Joined: 2007-11-03 18:33
Say,

does it support WildCards?
Or maybe some kind of 'processnamesearch'?
If that's not possible (or too tedious for you), anyone know how to do so with some looping?

Insert original signature here with Greasemonkey Script.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
No wildcard search. But I

No wildcard search. But I could make some kind of partial name search with ${WordFind}... that would require altering all the functions though, and I'm not sure how useful that is overall.

What kind of search are you thinking about? I mean, all the functions search for a process by name or PID already. Little more detail on what you want to do? I'm pretty sure one of the functions already does what you need.

digitxp
digitxp's picture
Offline
Last seen: 13 years 3 months ago
Joined: 2007-11-03 18:33
I wanna do an ejectscript

to go with UP ME :D.
Maybe I'll just use Xrxca's (Nah).

Insert original signature here with Greasemonkey Script.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Actually...

The _ProcFuncs (mode 1) already loops through all processes and looks up the path for one of them. A small change in logic could loop through all processes looking to see which ones of them were started from the current root directory (presumably the removable drive). For each match, it could call ${CloseProcess}.

Implementation is left as an exercise for the reader, who is required to share. Smile

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Yes, that's do-able. Just

Yes, that's do-able. Just move the compare part of the loop until after the full path is retrieved. However that's more of a custom function. Something I could definitely work up if someone wants it though. Waiting for request....

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Excellent. Quite excellent.

You are truly a NSIS demigod.

All we need now is an ${Exec} that will Exec a process and return its PID.

Combined with a saved-state file, this could easily Wink be used to provide the functionality requested in this reasonable topic.

Basically, FooPortable would do something like the following:

StrCpy $LASTRUNFILE "$SETTINGSDIRECTORY\lastrun.ini"

CalledWithSlashClose:
    ReadINIStr $PID "$LASTRUNFILE" "LastRun" "LastPID"
    StrCmp $PID "" TheEnd			; if missing or empty, we can't kill
    ReadINIStr $PROGRAMEXECUTABLE "$LASTRUNFILE" "LastRun" "LastEXE"
    StrCmp $PROGRAMEXECUTABLE "" TheEnd
    ${GetProcName} $PID $0
    StrCmp $0 $PROGRAMEXECUTABLE 0 TheEnd	; is PID still our process?
    ${CloseProcess} $PID $0
    Delete $LASTRUNFILE
    Goto TheEnd

LaunchNow:
    ${Exec} $EXECSTRING $PID			; $PID = child PID or 0 on error
    StrCmp $PID "0" NoLove
    StrCmp $SECONDARYLAUNCH "true" TheEnd	; LaunchAndExit
    WriteINIStr $LASTRUNFILE "LastRun" "LastPID" "$PID"
    WriteINIStr $LASTRUNFILE "LastRun" "LastEXE" "$PROGRAMEXECUTABLE"
    ${ProcessWaitClose} $PID -1 $0

In addition to enabling a /close option for compliant launchers, it enables PAM to look for PortableApps\*\Data\Settings\lastrun.ini and Close all matching processes. How often is this feature requested? Note that my example code doesn't handle AllowMultipleInstances=true, but it can certainly be tweaked to do so.

Note that I snuck in a request for a GetProcName (presumably _ProcFuncs mode 2) which calls psapi::GetModuleBaseName instead of psapi::GetModuleFileNameEx.

Thanks for considering my mindless ramblings...

-hea

@ John:
Something to consider for the next platform release that requires launcher updates?
Also, what about this other launcher suggestion? Smile

@ wraithdu:
Here's all of NSIS' code for Exec:

// exehead\util.c:
HANDLE NSISCALL myCreateProcess(char *cmd) {
  PROCESS_INFORMATION ProcInfo;
  static STARTUPINFO StartUp;
  StartUp.cb=sizeof(StartUp);
  if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 
			0, NULL, NULL, &StartUp, &ProcInfo))
    return NULL;
  CloseHandle(ProcInfo.hThread);
  return ProcInfo.hProcess;
}
// exehead\exec.c:
HANDLE hProc;
char *buf0=GetStringFromParm(0x00);
log_printf2("Exec: command=\"%s\"",buf0);
update_status_text(LANG_EXECUTE,buf0);

hProc=myCreateProcess(buf0);

if (hProc) {
  log_printf2("Exec: success (\"%s\")",buf0);
  CloseHandle( hProc );
} else {
  exec_error++;
  log_printf2("Exec: failed createprocess (\"%s\")",buf0);
}
wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Returning the PID is

Returning the PID is something that would be nice from NSIS. But you have to remember that NSIS is not really a scripting language. It is used for this purpose because it's fairly easy to code, and produces VERY small executables. Not to mention it's Open Source.

While the code for NSIS's exec is pretty simple, actually getting a return value is harder because of the way NSIS works with it's internal registers and stack (which I don't fully understand). I wouldn't want to go around modifying NSIS source anyway, cause then everyone would need the same build as you to work with your source code.

I think ${GetProcName}, while also do-able, is somewhat useless since you can't get a PID from NSIS.

To do what you want, you would run the process, then call ${ProcessExists} with the process's name, which will return the PID. Now you have the name and PID which you can use however you want.

And actually, while not as accurate as doing it by PID, ${CloseProcess} and ${TerminateProcess} both work by process name as well as PID.

Hmmm....maybe I'll work on something like ${Exec} that will return the PID since name searches can be wrong if there's more than one process with the same name....

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Actually...

I wasn't suggesting modifying NSIS source, just showing how little logic is actually involved in the Exec command (note that I omitted the interleaved code for ExecWait, since you don't need a PID for a dead process).

I was thinking more along the lines of something like:

System::Alloc 4*18			;// $1 = struct STARTUPINFO
Pop $1
System::Alloc 4*4			;// $2 = struct PROCESS_INFORMATION
Pop $2
System::Call "*$1(i 4*18)"		;// StartUp.cb=sizeof(StartUp);
System::Call /NOUNLOAD \
    'kernel32::CreateProcess(i 0, t "$EXECSTR", i 0, i 0, i 0, \
				i 0, i 0, i 0, i r1, i.r2)i.r0'
StrCmp $0 "0" NoLove			; in UNIX, would be PID or 0=error
					; in Win, non-zero "good" but undef
System::Call "*$2(i.R0,i.R1,i.R2,i.R3) ?!e"
StrCpy $PHND $R0			; process handle
StrCpy $PID $R2				; the PID
System::Free $1
System::Free $2

Note that I ignored handle closings in this version; it would be required if called repeatedly instead of once before handing cleanup off to the OS.

/*
    Here's the corresponding C header goodies for reference...

//  ************************************
    BOOL WINAPI CreateProcess(
      __in_opt     LPCTSTR lpApplicationName,
      __inout_opt  LPTSTR lpCommandLine,
      __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
      __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
      __in         BOOL bInheritHandles,
      __in         DWORD dwCreationFlags,
      __in_opt     LPVOID lpEnvironment,
      __in_opt     LPCTSTR lpCurrentDirectory,
      __in         LPSTARTUPINFO lpStartupInfo,
      __out        LPPROCESS_INFORMATION lpProcessInformation
    );

    typedef struct _STARTUPINFO {           // $1
      DWORD cb;
      LPTSTR lpReserved;
      LPTSTR lpDesktop;
      LPTSTR lpTitle;
      DWORD dwX;
      DWORD dwY;
      DWORD dwXSize;
      DWORD dwYSize;
      DWORD dwXCountChars;
      DWORD dwYCountChars;
      DWORD dwFillAttribute;
      DWORD dwFlags;
      WORD wShowWindow;
      WORD cbReserved2;
      LPBYTE lpReserved2;
      HANDLE hStdInput;
      HANDLE hStdOutput;
      HANDLE hStdError;
    } STARTUPINFO, 
     *LPSTARTUPINFO;

     typedef struct _PROCESS_INFORMATION {  // $2
      HANDLE hProcess;                      // $PHND = $R0
      HANDLE hThread;
      DWORD dwProcessId;                    // $PID = $R2
      DWORD dwThreadId;
    } PROCESS_INFORMATION, 
     *LPPROCESS_INFORMATION;
//  ************************************/

Your satisfaction is guaranteed ...
All code has been fully tested for Drupal comment submission behavioral dynamics compliance. The test organization is currently optimized for that type of testing and may not have performed the more unusual test suites such as cursory code review, attempted compilation, spell checking or logical analysis. We are proud of the products we offer, but cannot be held liable for any unforeseen issues that "might" have been prevented or resolved through a fancy-pants test getup. ... or DOUBLE my money back!

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Haha, nice.

Haha, nice.

This was exactly what I was going to cook up tomorrow, along with a running-process-path-search kinda thing. Stay tuned for ${Execute} and ${ProcessPathSearch}, coming soon to a forum near you!

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Added ${Execute} and ${ProcessPathSearch}

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Cool.

Good catch on the two WORDs in the STARTUPINFO struct; I simply counted lines in the struct for my POC code.

MSDN indicates that STARTUPINFO.cb should be set to sizeof(STARTUPINFO) before calling CreateProcess(), but, hey, whatever works. Smile I've never wrote no real code for Winders anyhow...

As for ProcessPathSearch, a more general approach would be to nix ${searchterm} and use two user-defined functions, a "match" function and a "do" function (both return 0/1). Then, you can match as simply or as complex as you want (even just Push 1). Also, the "do" function needs a PID argument (so it can reference the process it's supposed to work on).

Now what about GetProcName? Wink

-hea

P.S. I envision a usage something like this:

!define FORCECLOSEWAIT 10000		; if "false", try once and exit
Var ROOTDIR
Var ROOTDIRLEN
Var PPS_PATH
Var PPS_STRAGGLERS

Function "PPS_match"; [path]
    Pop $PPS_PATH
    Push $0
    StrCpy $0 $PPS_PATH $ROOTDIRLEN
    StrCmp $0 $ROOTDIR 0 +3
        StrCpy $0 1			; match!
        Goto +2
    StrCpy $0 0				; no match
    Exch $0
FunctionEnd

Function "PPS_close"; [path] [PID]
    Pop $PPS_PATH
    Exch $0				; the PID
    Push $1
    ${CloseProcess} $0 $1
    StrCmp $FORCECLOSEWAIT "false" TheEnd_PPS_close
        ${ProcessWaitClose} $0 $FORCECLOSEWAIT $1
        StrCmp $1 "-1" 0 TheEnd_PPS_close
            StrCpy $PPS_STRAGGLERS "true"
TheEnd_PPS_close:
    Pop $1
    StrCpy $0 1				; always continue
    Exch $0
FunctionEnd

Function "PPS_kill"; [path] [PID]
    Pop $PPS_PATH
    Exch $0				; the PID
    Push $1
    ${TerminateProcess} $0 $1
    StrCmp $1 "-1" 0 TheEnd_PPS_kill
        ${GetProcName} $0 $1
        StrCmp $1 "0" TheEnd_PPS_kill
            MessageBox MB_OK|MB_ICONINFORMATION "Couldn't kill process: $0"
TheEnd_PPS_kill:
    Pop $1
    StrCpy $0 1				; always continue
    Exch $0
FunctionEnd

Section "Main"
    ${GetRoot} $EXEDIR $ROOTDIR		; get drive or presumed mount
    StrLen $ROOTDIRLEN $ROOTDIR
    IntOp $ROOTDIRLEN $ROOTDIRLEN + 1	; add trailing backslash
    StrCpy $PPS_STRAGGLERS "false"
    new_ProcessPathSearch "PPS_match" "PPS_close"
    StrCmp $FORCECLOSEWAIT "false" TheEnd
    StrCmp $PPS_STRAGGLERS "false" TheEnd
        Sleep $FORCECLOSEWAIT
        new_ProcessPathSearch "PPS_match" "PPS_kill"
TheEnd:
    MessageBox MB_OK|MB_ICONINFORMATION "All your process are belong to us."
SectionEnd

<EDIT>
Same double-money-back satisfaction guarantee applies, of course!
</EDIT>

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
[quote]MSDN indicates that
MSDN indicates that STARTUPINFO.cb should be set to sizeof(STARTUPINFO) before calling CreateProcess()

I specifically looked for this, because it's needed for some other functions, but it is not stated anywhere on the CreateProcess() or STARTUPINFO MSDN pages...I really looked.

Regarding your 2 function idea, I don't see the advantage (and I can't really follow what you're outlining in your code). But basically there's already 2 functions here. The "match" function is built in. ${searchterm} can be any part of the path to match, and the "do" function is the user defined function so you can make it "do" anything you want. I'll add the PID to that func though (additional Pop off the stack).

There's no need for a ${GetProcName} function though. Two reasons -
1. if you have the PID, it's assumed that your have (or at some point had and should have saved) the process name
2. all the funcs take either a PID or process name, the PID being preferred, except for ${ProcessExists} - but if you have the PID for that function...then you already know the process exists

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
follow-up (too long, and edited)

As I said, hey, whatever works. I was merely going by the description from MSDN: STARTUPINFO Structure:

cb
The size of the structure, in bytes.

and the following example code from MSDN: Creating Processes (linked from the STARTUPINFO page):

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

Regarding your 2 function idea, I don't see the advantage

What if I want my "do" function called for every process that:

  1. exists?
  2. doesn't match a particular string?
  3. matches one of several strings?

As an example of #3, if my own "match" function were called, I could:

!define INTERESTING "|firefox.exe|thunderbird.exe|foo.exe|"

Function "PPS_match"; [path] [PID]
    Pop $PPS_PATH
    Exch $0				; the PID
    Push $1
    ${WordFind} "$PPS_PATH" "\" -1 $1	; get base name 
    ${WordFind} "${INTERESTING}" "|$1|" "E+1{" $1
    IfErrors PPS_match_notinteresting
        ; found an interesting process
        StrCpy $0 1			; match!
        Goto TheEnd_PPS_match
PPS_match_notinteresting:
    StrCpy $0 0				; no match
TheEnd_PPS_match:
    Pop $1
    StrCpy $0 1				; tell ProcessPathSearch to continue
    Exch $0
FunctionEnd

True, with your current implementation, I could use "\" as the search term to force my "do" function to get called every time and apply my matching logic there. But in the case of #1 or matching the root directory (like the example in my previous comment), WordFind is rather code-heavy.

(and I can't really follow what you're outlining in your code)

Undoubtedly there's something horribly wrong with it... wouldn't be the first time. Let me convert to pseudocode:

!define FORCECLOSEWAIT 10000	; if "false", try "close" & keep moving
;; my code didn't have the {} when referenced below

Function "match"; [path]
    if $rootdir == left($path,len($rootdir))
        if $path != $EXEPATH	;                   >
            return 1		; match!
        else
            return 0		; NO!!! it's ME!            :O
    else 
        return 0		; no match
FunctionEnd

Function "close"; [path] [PID]
    ${CloseProcess} $PID $ignore
    if ${FORCECLOSEWAIT} != "false" {
        ${ProcessWaitClose} $PID $FORCECLOSEWAIT $retval
        if $retval == -1
            $we_have_stragglers = true;
    }
    return 1			; tell ProcessPathSearch to continue
FunctionEnd

Function "kill"; [path] [PID]
    ${TerminateProcess} $PID $retval
    if $retval == -1 {
        ${GetProcName} $PID $process ; (also check if already dead)
        if $process != 0
            MessageBox "Couldn't kill process: $process"
            ;; my code accidentally specified $pid here
    }
    return 1			; tell ProcessPathSearch to continue
FunctionEnd

Section "Main"
    ${GetRoot} $EXEDIR $rootdir	; get drive or presumed mount
    $rootdir = "$rootdir\"
    ;; my code only pretended to add a backslash ;-)
    $we_have_stragglers = false;
    new_ProcessPathSearch "match" "close"
    if ${FORCECLOSEWAIT} != "false"
        if $we_have_stragglers == "true" {
            ; at least one CloseProcess timed out
            Sleep ${FORCECLOSEWAIT}
            new_ProcessPathSearch "match" "kill"
            ; die! die! die!
        }
TheEnd:
    MessageBox "All your process are belong to us."
SectionEnd

Why is Drupal converting ;−) into a smiley in the middle of a <pre> block?!?

There's no need for a ${GetProcName} function though.
1. if you have the PID, it's assumed that your have (or at some point had and should have saved) the process name

My first comment in this topic gave an example of having and saving the PID in a file from launcher instance A, then attempting to kill that specific process at some unknown time later from launcher instance B. In this case (since it may not be the same machine, or it may have rebooted), it's a reasonable sanity check to make sure the PID still matches the previously saved process name before killing it. Yes, I realize there are other ways to skin this cat, but it's an old habit to try to solve the broadest class of problems possible with the least increment of additional effort.

But you're right, there isn't strictly a need for ${GetProcName}. A code-heavy workaround would be to use ${GetProcPath} followed by [ ${WordFind} $path "\" -1 $basename ].

2. all the funcs take either a PID or process name, the PID being preferred, except for ${ProcessExists} - but if you have the PID for that function...then you already know the process exists

...unless it has already died.

Keep up the good work! The PortableApps.com and NSIS communities are certainly better off because of it.

-hea

<EDIT>
In case anyone was wondering, the above pseudocode (and the code it references) is intended to close all applications that were launched from the same root directory as the close program. (Of course, it really should make sure it doesn't kill itself! :-)) In most cases the "root directory" would be the portable drive letter. It politely sends a Window Close request to the windows of each matching process. If the FORCEWAITCLOSE constant is "false", then it assumes all is well and exits. Otherwise, it waits up to FORCEWAITCLOSE msec after each polite Close request for the targeted process to die. If any matching processes are still hanging around, it waits another FORCEWAITCLOSE msec then rudely asks the OS to kill them all. Sound useful?
</EDIT>

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

${ProcessPathSearch} now passes the matching process PID to the user function.

LOGAN-Portable
LOGAN-Portable's picture
Offline
Last seen: 11 years 10 months ago
Developer
Joined: 2007-09-11 12:24
Does the script use

Does the script use !define's to activate portions of the script so if parts are not used those script parts won't be included on compile time?

(I used similar functionality with ISSI.)

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Not exactly, but since

Not exactly, but since everything is a macro, the code is only included if you actually call the macro.

There are other ways to do it as macros + functions, like in FileFunc and WordFunc, but that would require a pretty major rewrite. I don't think my script is big enough yet to warrant that. These little text files compress down pretty good on compile anyway.

That said, I'll probably be bored enough at work tomorrow to give it a shot Wink The only downside, is that to use any of the functions, you have to !insertmacro Execute at the top, like for the FileFunc and WordFunc functions.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Status

Ok, I've got a pretty major rewrite coming today...I'm almost done converting everything into Function code. It runs like FileFunc and WordFunc. After thinking about it enough, I realized it's much more optimized this way, since each ${Func} call only has to add a few lines and call a function, instead of adding in the full macro code everytime it's used.

I'll take this opportunity to add a few things -
1. ${ProcessExists} will take a PID
2. ${GetProcName} - process name from PID, really only useful as a sanity check
3. allow ${ProcessPathSearch} to return all running processes without a dummy search like "\". This will at least cut out the ${WordFind} overhead. However any exclusive searches or multiple term searches will be left up to the user. That kind of thing is too specific for the scope of these functions.

Anything else while I'm at it?

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
it's all good

Let's see...

  1. Cool.
  2. Cool.
  3. I presume something like "" as the search term will by-pass WordFind and simply call the user_func? Will it still be necessary to indirectly [ !insertmacro WordFind ] if the rest of the user's script is WordFind-free?

Anything else while I'm at it?

Nah, I think you're good to go. Smile

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
I was gonna do something

I was gonna do something like _RETURN_ALL_ as it seems better than a blank string.

WordFunc and WordFind will be included with !insertmacro ProcessPathSearch. This actually adds the WordFind function and defines ${WordFind}. So I won't be able to selectively leave out the functions, since they're included before the ${ProcessPathSearch} macro is ever called. But it's not that much extra code, so it shouldn't make a size difference.

The extra !insertmacro PathSearch will no longer be necessary after the update.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
NULL

Actually, NULL pointers and empty strings are quite often used to represent special cases. As it turns out, this isn't really a special case... "" is the only string that is guaranteed to be a substring of any other string. Smile

You can actually use !ifdef/!ifndef to leave out the WordFind reference in your function (and therefore ignore the $searchstring) unless the user does the !insertmacro ProcessPathSearch.

Just a thought. -hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Well, NSIS doesn't really

Well, NSIS doesn't really have a NULL, and "" a blank string causes WordFind to fail. The `searchterm` is really a delimiter, and WordFind tests whether or not it exists.

WordFunc/WordFind are only included if the user does !insertmacro ProcessPathSearch. What I meant was that even if the user passes _RETURN_ALL_, WordFunc/WordFind are still included.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Well,

What I meant was, you can check for "" and bypass WordFind altogether (which is actually what you are currently doing with "_RETURN_ALL_". And, you could use something like this to avoid unnecesarily including WordFind for scripts that don't need it:

!ifdef ProcessPathSearch_WordFind
    !include WordFunc.nsh
    !insertmacro WordFind
;   $searchterm is relevant
;   WordFind is called only if $searchterm != ""
;   user_func is called only if 
;       $searchterm matches using WordFind's default logic
;else
;   $searchterm is ignored
;   user_func is always called    
!endif

; ...

    ${Unless} $7 = 0 ; $7 is hProcess
        ; get full path
        System::Call /NOUNLOAD 'psapi::GetModuleFileNameEx(i r7, i 0, t .r8, i ${NSIS_MAX_STRLEN})i' ; $8 = path
        ; search path
        ClearErrors
        
        !ifdef ProcessPathSearch_WordFind
            ${Unless} $0 == ""
                ${WordFind} "$8" "$0" "E*" $2 ; error if searchterm not found
            ${EndIf}
        !endif;

        ${Unless} ${Errors} ; some error
        
            ; Call user_func
        
        ${EndIf}
        System::Call /NOUNLOAD 'kernel32::CloseHandle(i r7)'
    ${EndUnless}

However slim the chances, it's possible for someone to need _RETURN_ALL_ (or any other contrived string with legal path characters) as a selective filter. If you'd rather not use "", I'd suggest incorporating illegal path characters (e.g., :ReturnAll: ).

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Hmm, but where to put

Hmm, but where to put !define ProcessPathSearch_WordFind? It must be set specifically and set outside the section since the !ifdef commands are compiler commands. I believe the Sections are compiled last, so it can't be conditional on the searchterm at runtime. Using an !ifdef on ${WordFind} doesn't save much, as that calls a small macro. All the real WordFind stuff is in a function from the !insertmacro WordFind call that must be defined outside your Section.

It's a bit of a catch22...

Currently, WordFunc/WordFind are included only with the ProcessPathSearch function, but included regardless of the searchterm.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Answer.

Hmm, but where to put !define ProcessPathSearch_WordFind?

The !define ProcessPathSearch would go above the user's section, just like usual, but would be hidden in the required !insert-macro. Note: I suggest renaming it from ProcessPathSearch_WordFind as I'll describe next.

If a user wants the default WordFunc/WordFind behavior you provide, that actually is a ProcessPathSearch. If they simply want their user_func called for every process (passing path and PID), that's actually a ProcessPathEnum. You can use the trivial !ifdef example I provided above, combined with WordFunc's clever way of defining a function once but allowing for it to be insert-macro'd by different names. In this case, if ProcessPathSearch is insert-macro'd, the function would be named that and the defining macro would include and insert the appropriate WordFunc goodies. If ProcessPathEnum is insert-macro'd, the function would be named that and no extraneous code need be inserted (yes, both versions of the function can be included in the same script). And, ProcessPathEnum wouldn't have a $searchterm argument, so it wouldn't need a special one, either.

That really is one of the more clever features of WordFunc.nsh. I was more impressed with that than with the rather impressive the string-handling code. Wink

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Figured it out

Took me a while to wrap my head around what you were suggesting, but I figured it out.......saves a whole 500 bytes LOL!

Although if you use both commands now, it's going to cost you 500 bytes Smile

Upload in a minute...

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Whew, big update. Basically

Whew, big update. Basically a whole rewrite. I converted everything to Function code, like WordFunc and FileFunc, and cleaned stuff up. Now all processing is done in Functions, eliminating lots of extra code if macros are called multiple times. If you want to use a function now, you must do !insertmacro FunctionName, just like WordFunc/FileFunc.

Please test for any bugs, crashes, stack mistakes, etc. I've tested everything, but another pair of eyes never hurts.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Fix

Quick fix for the ${ProcessWait} timeout counter.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Moved the .::RETURN_ALL::. ability from ${ProcessPathSearch} into its own command, ${ProcessPathEnum}. Saves a few bytes Wink

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
savings

As Ben Franklin would say, A half-K saved is a half-K earned.

And NSIS literally yells at you if you so much as declare a variable without referencing it: Variable "FOO" not referenced, wasting memory!

I think you've done a good thing. :lol:

-hea

P.S. It's possible to make both functions co-exist without the added ~500 bytes, but I'll let it go. Wink

P.P.S. Seriously, great job and elegant implementation. Truly excellent. And you have to admit that you enjoyed the challenge, no? Blum

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
I always enjoy a challenge,

I always enjoy a challenge, it's why I do this stuff Biggrin And I think I have an idea what you mean...I'll look into it.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Hmmm

So I took a look at it, and I can't find an elegant way of combining the functions, all because the decision has to be made at compile time.

Currently it goes like this - two small macros for each form (Enum or Search). The first inserts the correct form of the main PathSearch function and conditionally inserts WordFunc/WordFind, the second is the actual call that runs the function. So for each form there ends up being two small macros and one main function (3 blocks).

I was trying to think of a way to get it down so the main function is only inserted once. This means it must be static (instead of dynamic like it is now).

** Here's where you can correct me if my logic is wrong **

So the main, now static, function must call another function to make the form (Enum or Search) decision. Call it a helper function, it must also be static because it's being called from another static fuction (name is hard coded).

Now there must be a dynamic function (created in one or both of two forms at compile time) to either run the WordFind search or not (basically a dummy function that does nothing, but must exist).

The Enum or Search calling macros will then call the appropriate search function (WordFind or not) based on the define (the same one the helper function used to create the search functions.

Sooooooo, now we have 1 main function block, 1 helper function block, 2 WordFind-or-not function blocks, and the original 2 macro blocks for each of the Enum and Search forms. A grand total of 8 code blocks....... vs the 6 from before (assuming both Enum and Search are used in the same script). Not to mention for each different function call the stack must be Pushed and Popped to avoid losing any data. This makes my head hurt, and in the end, is it really saving any space with the addition of all the extra handling code?

** Now's your chance **

So did I get that all wrong, and there's actually an easier way to do this?

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Maybe this...

The user should [ !insert-macro ProcessPathSearch ] before ProcessPathEnum if they want to get a free ride on the previously defined ProcessPathSearch function. Slight mod to your macros:

!define PROCFUNC_RETURNALL `.::RETURN_ALL::.`
; corresponding change in macro _ProcessPathEnumCall, of course

!macro ProcessPathSearch
    !ifndef ProcessPathSearch
        !define _PATH_FUNC_SEARCH Search
        !insertmacro PathFunc
        ;; no !undef, so _ProcessPathEnumCall can be overloaded
        !define ProcessPathSearch "!insertmacro _ProcessPathSearchCall"
    !endif
!macroend

!macro ProcessPathEnum
    !ifndef ProcessPathEnum
        !ifndef ProcessPathSearch
            !insertmacro PathFunc
        !endif
        !define ProcessPathEnum "!insertmacro _ProcessPathEnumCall"
    !endif
!macroend

and to your ProcessPath function:

            !ifdef _PATH_FUNC_SEARCH
                ; search path
                StrCmp $0 ${PROCFUNC_RETURNALL} +2
                    ${WordFind} "$8" "$0" "E*" $2 ; error if searchterm not found
            !endif

Same bulletproof code warranty as always!

BTW, the reason that 0, NULL and empty string are often used for special cases is that zero is magic:

  • It's neither positive nor negative.
  • Most processors have historically cared whether arithmetic results in it (e.g., the zero flag, JMPZ and JMPNZ).
  • It's the only string ("") that is actually a substring of all strings, but matches no other string.

For length-encoded strings, clever matching routines abort if the lengths don't match, succeed if both lengths are zero, and match character-for-character otherwise. For zero-delimited strings (AKA C-strings), a single comparison to NULL (str == 0) or empty string (!str || *str == 0) is guaranteed to determine a match no later than the first character of the string; when comparing two unknown strings, matching proceeds character-for-character and aborts if you reach the end of one string before the other (len("") == 1, if you count the trailing zero). Your choice of special case now has illegal characters to prevent accidental matches with legitimate strings, but it isn't that magic. Blum

Not to mention for each different function call the stack must be Pushed and Popped to avoid losing any data.

Actually, that reminds me... the callee is usually responsible for saving registers and restoring the stack. Your code exhibits defensive programming -- which is a good thing -- but will lead to unnecessarily saved registers for most user functions (fewer registers actually used), and will likely to lead to doubly-saved registers for some user functions (because most function-aware users won't read your code to notice that you're doing it for them). I'd probably let the user function do it, but give a user_func example that shows that it's necessary to restore any of the registers that ProcessPath* cares about. Your way is definitely safer (in the "belt and suspenders" sort of way ;-)).

Am I FOS again? -hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Yep, that would work. I

Yep, that would work. I thought about doing it that way, but I decided counting on the user to insert the macros in the correct order was being optimistic.

Regarding the stack, I would tend to agree with you, however NSIS precedent is that a function should leave the stack in the same state as it found it.

After weighing options, I think I'm going to leave it as is. I hope you can live with the extra 500 bytes Wink

Seriously though, thanks for all the input here while I was putting this together. I don't think it would be quite what it is if not for your help. I'm going to get up a wiki page in the NSIS wiki when I get a chance. kichik, one of the NSIS devs, seemed to think it was worthy Smile

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Cool.

I decided counting on the user to insert the macros in the correct order was being optimistic.

Yes, but there's no harm if they ignore the recommendation and put them in the wrong order. If they get it right, they save 500 bytes!!! :lol:

Regarding the stack, I would tend to agree with you, however NSIS precedent is that a function should leave the stack in the same state as it found it.

Hmmm. You just described the same callee-is-responsible convention that I mentioned in my post. In addition, the callee is usually responsible for saving and restoring any registers it uses. Your functions all properly follow that convention, but when ProcessPath* is the caller, it doesn't trust user_func to behave properly as a callee. In searching the NSIS pages for "callback", I see both methods used for preserving the registers (not the stack -- it's always the callee's responsibility). But since both Instructor and kichik use the same defensive technique you used when calling a callback function, I'd call that correct. Blum

After weighing options, I think I'm going to leave it as is. I hope you can live with the extra 500 bytes
Shock It's literally removing one !undef and adding a StrCmp and a !ifndef..!endif pair. Wink

Otherwise, you should remove the reference to ${_PATH_FUNC_SEARCH} from !macro _ProcessPathEnumCall (since it will never be defined outside the scope of !macro ProcessPathSearch).

thanks for all the input here

No problemo... It's a creative outlet. Smile

NSIS wiki

Awesome, and congrats! -hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Ahh

Otherwise, you should remove the reference to ${_PATH_FUNC_SEARCH} from !macro _ProcessPathEnumCall (since it will never be defined outside the scope of !macro ProcessPathSearch).

It's interesting you should mention this. I thought the same as you originally, but the compiler errors out if ${_PATH_FUNC_SEARCH} is removed. Even though in the scope of _ProcessPathEnumCall it is undefined, it must still insert a NULL character or something, because the function address cannot be found without it.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Yup, you're right.

I had remembered this:

5.4.2 !undef

gflag

Removes an item from the global define list. Note that ${SYMBOL} 
where SYMBOL is undefined will be translated to "${SYMBOL}".

I originally thought it made more sense for it to insert NULL (nothing), but then you couldn't distinguish between the value of ${SYMBOL} when simply defined (no value) or when undefined. So their way actually makes sense in a bizarrely straightforward way... it isn't substituted at all, because there's no substitution defined.

But it leads to a very oddly named function, indeed! Shock

The manual doesn't say, so I decided to check if Function names really were that permissive or if undefined symbols were a case of special-handling.... Can you believe the compiler accepts [ Function "~!@#$%^&*()`;:?,./|[]{}" ] without complaint? I'm not sure you can get much more permissive than that. Apparently it doesn't pay much attention to the name other than for matching, address substitution and enforcement of the "un." prefix rule. BTW, Labels aren't nearly so permissive.

I got to thinking about my post a while afterwards (I hate when that happens), and realized that I hadn't considered that the Function was being defined outside the scope of !macro ProcessPathSearch as well. That's why you get the bizarre function name when inserted by !macro ProcessPathEnum, and the Enum Call requires the suffix.

Instructor handled it in WordFunc.nsh by defining his prefix and suffix constants either way -- unused defined empty, used defined with a value:

!macro un.WordFindS
    !ifndef un.WordFindS
        !undef _WORDFUNC_S
        !undef _WORDFUNC_UN
        !define _WORDFUNC_UN `un.`
        !define _WORDFUNC_S `S`

        !insertmacro WordFind

        !undef _WORDFUNC_UN
        !define _WORDFUNC_UN
        !undef _WORDFUNC_S
        !define _WORDFUNC_S
    !endif
!macroend

That way, the prefix and suffix constants are always defined and the functions get reasonable-looking names either way. You could use the same method by defining _PATH_FUNC_TYPE as either "Search" or "Enum" rather than "Search" or undefined. Then the call in !macro _ProcessPathEnumCall would simply be [ Call ProcessPathEnum ]. Or, you could save the user almost 700 bytes compressed (5KB uncompressed!!! :lol:) with the following:

!macro ProcessPathSearch
    !ifndef ProcessPathSearch
        !ifdef _PATH_FUNC_TYPE
            !warning `You should !insertmacro "ProcessPathSearch" before "ProcessPathEnum"`
            !undef _PATH_FUNC_TYPE
        !endif
        !define _PATH_FUNC_TYPE Search
        !insertmacro PathFunc
        !define ProcessPathSearch "!insertmacro _ProcessPathSearchCall"
    !endif
!macroend

!macro ProcessPathEnum
    !ifndef ProcessPathEnum
        !ifndef ProcessPathSearch
            !define _PATH_FUNC_TYPE Enum
            !insertmacro PathFunc
        !endif
        !define ProcessPathEnum "!insertmacro _ProcessPathEnumCall"
    !endif
!macroend

!macro _ProcessPathEnumCall user_func outVar
    Push $0
    GetFunctionAddress $0 `${user_func}`
    Push `$0`
    Push `` ; dummy searchterm, bypasses WordFind
    Call ProcessPath${_PATH_FUNC_TYPE}
    Exch
    Pop $0
    Pop ${outVar}
!macroend

!macro PathFunc
    !if ${_PATH_FUNC_TYPE} == "Search"
        !include WordFunc.nsh
        !insertmacro WordFind
    !endif
    Function ProcessPath${_PATH_FUNC_TYPE}
; ...
            !ifdef WordFind
                ; search path
                StrCmp $0 `` +2 ; Enum: bypass WordFind and enumerate all processes
                    ${WordFind} "$8" "$0" "E*" $2 ; error if searchterm not found
            !endif

I actually did test the compile results of this code (using different scenarios for user code). Smile It's interesting that the compiler knows to "zero out" unreferenced functions, but it doesn't bother to reclaim the space.

Thanks for listening to my nonsense again. -hea

P.S. So, any last-minute changes before your well-deserved spot on the NSIS Wiki of Fame? Wink

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Nicely worked out. I

Nicely worked out. I changed two things, and I'll upload something soon.

1. I changed your !warning to an !error to abort the compile. It wouldn't work right if compilation continued.

2. I changed StrCmp $0 `` +2 to an ${Unless} | ${EndUnless} block. You can't use relative jumps before a macro...it just jumps some number of lines into the macro and totally screws the script when run (no errors during compile though).

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
D'oh!
  1. Hmmm. I seem to recall that the generated code was the same for the Enum call, and that it simply didn't use the declared Enum function.
  2. D'oh! I fired my code reviewer the other day after that embarrassing ${_PATH_FUNC_SEARCH} incident. Of course, I haven't found a replacement yet.

I guess that's what I get for staying up and piddling with stuff like this in the middle of the night. Smile

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
1. If you let it compile

1. If you let it compile out of order, then the ProcessPath${_PATH_FUNC_TYPE} function is inserted twice, once for Enum and once for Search. Since we did all this work to avoid that very situation, I decided to make the user do it the right way Smile

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Thanks to haustin, first off.

Changed the logic to save space when using both the ProcessPathEnum and ProcessPathSearch functions in the same script. If doing so, you must

!insertmacro ProcessPathSearch

BEFORE

!insertmacro ProcessPathEnum

Compiler will error out with a message if you do not.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
No, thank you.

Thanks to haustin, first off.

You, sir, did all the heavy lifting... I just came along and suggested straightening a couple of boxes.

It's the result of this kind of back-and-forth exchange that defines the difference between bona fide Open Source Software and just Available Source Software.

Thanks for contributing something quite useful to the OSS community.

-hea

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
${WinWait}

Functionality described here.

If I were to try it, I'd probably define a Function WinWait with three arguments (processNameOrPID, timeout, bRequireVisible) and macros ${WinWait} and ${WinWaitVisible} to call it.

So, whatcha think? It would be nearly identical to your existing ${CloseProcess} plus your existing timeout logic. And, you'd need to Push 0 in the callback to stop enumerating windows once you find a match.

Very exciting. Smile

-hea

[Link fixed by moderator SL]

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Link's not working. But I

Link's not working. But I can guess (based from AutoIt) that it waits for a window to exist. This is usually done by title or class, so process name or PID is not used (that's covered by ProcessWait).

This functionality is actually built into NSIS with the FindWindow command. I could put it in a function though and add a timeout option. This would be way more efficient than using the System Dll.

Now WinWaitVisible....I'd have to figure out how to test if the found window was visible or not. Easy to do in AutoIt, but I don't know the function offhand for retrieving window states. Basically you'd call that, then test if VISIBLE is among the attributes.

EDIT - okay I saw the link after it was fixed by a mod. I could do what I said above, with a WinWait command or WinWaitVisible (once I know how to test for that). But as it relates to that thread, it seems like a very specific task to add exactly that function to this library.

They would have to enumerate all windows created by a process, which could be A LOT, then test them all in a loop to wait for when one becomes visible. Problem with that is you don't know how many or when the windows are spawned. So if you enumerate too early, you could miss the one you need.

This might be better accomplished in the development phase where the dev could find out which window is the correct one to wait for, then use the simplified WinWait command discussed above to wait for just that one.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Hmmm.

Link's not working.

I know I fat-fingered a quote on the href, but I thought I had fixed it. (That's why I usually check my links before posting... :-))

The WinWait functionality as I described it is actually how it is implemented in AutoHotKey, including the ability to match by PID (the reason for doing so is to guarantee that you're not accidentally picking up another window that matches the class or window title).

Now WinWaitVisible....I'd have to figure out how to test if the found window was visible or not.

IsWindowVisible() Note that "visible" simply means that the program says it's OK to draw it -- it says nothing about whether the user can actually see it (e.g., "behind opaque window" or minimized).

They would have to enumerate all windows created by a process, which could be A LOT, then test them all in a loop to wait for when one becomes visible. Problem with that is you don't know how many or when the windows are spawned. So if you enumerate too early, you could miss the one you need.

Actually, the EnumWindows() would be inside a loop, and it enumerates only top-level windows, limiting its output. AutoHotKey's looping delay is 200ms by default, but that's surely a bit aggressive for System's pseudo-callback implementation.

As I mentioned, your existing CloseProcess has the exact logic necessary to perform the task. It simply needs to be put in an outer timed loop like ProcessWait. Instead of SendMessage, the callback would stop enumeration and trigger a break from the timed outer loop, leaving you with the PID and HWnd. If bRequireVisible, then the IsWindowVisible() call would be made on matching windows before deciding that it was indeed a match.

This might be better accomplished in the development phase where the dev could find out which window is the correct one to wait for, then use the simplified WinWait command discussed above to wait for just that one.

If the process you're WinWait'ing encounters an error or other unusual condition, it will quite likely create a window with a totally different class and title (e.g., a pop-up). So, it wouldn't be useful to wait for the normal scenario to play out. The whole point is to become aware of any window that a particular process creates (otherwise, the NSIS FindWindow command is sufficient without a wrapper).

-hea

[Link fixed by moderator SL - quotes at each end of the address are your friends ;)]

edit: Yeah, so is not being up for over 24 hours... Time to go to bed. -hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
I'm starting a new thread

I'm starting a new thread for a possible WindowFunc header.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Updated ${CloseProcess} to use NSIS internal FindWindow command to enumerate window handles. This eliminates 2 System calls, so should increase performance of this command.

Also fixed one little error in the PathFunc macro (${EndIf} that should have been an ${EndUnless}).

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Renamed some functions for consistency -

${GetProcPath} -> ${GetProcessPath}
${GetProcName} -> ${GetProcessName}

${ProcessPathEnum} -> ${EnumProcessPaths}
${ProcessPathSearch} -${SearchProcessPaths}

Updated PathFunc logic so compile will not error out if these conditions are true -

1. !insertmacro EnumProcessPaths comes BEFORE !insertmacro SearchProcessPaths

AND

2. !insertmacro WordFind is already included

@haustin
I tested all scenarios I could think of, and it seems to work. File sizes are consistent with what should be defined/included.

BTW, in hindsight I think SearchProcessPaths is unnecessary. It's actually something that should be handled by the user in 'userfunc'.

haustin
Offline
Last seen: 13 years 3 months ago
Joined: 2007-09-19 17:59
Re:

BTW, in hindsight I think SearchProcessPaths is unnecessary. It's actually something that should be handled by the user in 'userfunc'.

Yep. I thought you were keeping it to make it easier for the user.

-hea

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Removed ${SearchProcessPaths} macro. This should be left up to the user to manipulate the paths how he/she wants in the user defined function.

OliverK
OliverK's picture
Offline
Last seen: 3 years 5 months ago
Developer
Joined: 2007-03-27 15:21
Am I reading the comments for

Am I reading the comments for ${GetProcessPath} correct? Could I use this to check if say, geany.exe already exists, and if it the process path is equal to $ProgramDirectory\bin\geany.exe?

*Edit*
ALso, what's the difference between ProcessWait and ProcessWait2

Too many lonely hearts in the real world
Too many bridges you can burn
Too many tables you can't turn
Don't wanna live my life in the real world

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
1) Yes. It will return the

1) Yes. It will return the full path to the running executable (by name or PID) if it exists, otherwise it returns 0.

2) ProcessWait polls for the existence of a process every 500ms and uses internal System::Calls. It uses slightly more CPU than ProcessWait2. ProcessWait2 polls every 250ms and uses the FindProcDLL plugin.

OliverK
OliverK's picture
Offline
Last seen: 3 years 5 months ago
Developer
Joined: 2007-03-27 15:21
1)Neat, I'm gonna implement

1)Neat, I'm gonna implement that in Geany, I think it will make my launcher do a NICE trick Smile

2)Cool, but I can't get it to work.

		${ProcessWait2} "$WatchEXE" "2" $0
		MessageBox MB_Ok '$0'
			StrCmp $0 '-1' 0 CloseProgram
				MessageBox MB_OK "Operation Timed Out"
				Abort

$WatchEXE (menu.exe) is set earlier. It will return my process id, then it will automatically go to close the program. The process is running.

Is this what it should do? I'm confused Sad

Too many lonely hearts in the real world
Too many bridges you can burn
Too many tables you can't turn
Don't wanna live my life in the real world

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Just to be clear...

Just to be clear... ProcessWait(2) will wait for the given process NAME to EXIST. ProcessWaitClose will wait until the given process NAME or PID CLOSES or DOES NOT exist.

Your timeout value is in miliseconds, meaning the function is only going to wait for that long before it returns, regardless of success.

Now... what functionality exactly are you trying to achieve?

OliverK
OliverK's picture
Offline
Last seen: 3 years 5 months ago
Developer
Joined: 2007-03-27 15:21
Just to be clear...

Just to be clear... ProcessWait(2) will wait for the given process NAME to EXIST. ProcessWaitClose will wait until the given process NAME or PID CLOSES or DOES NOT exist.

Well, that would explain why its not working right Blum

Your timeout value is in miliseconds, meaning the function is only going to wait for that long before it returns, regardless of success.

Thanks

Now... what functionality exactly are you trying to achieve?

I should be able to make new document open in geany (even if geany is currently running) by running geany with a document name. However, this triggers a whole bunch of options and things in the launcher script.

So, If I check for Geany.exe, and it exists, then I check if its running from $PRogramDirectory or not. If it is, I just launch geany and exit Biggrin
-Or-
Do you mean autoclose? I just need it to wait for the menu to close before doing my loop to close programs.

*EDIT*
It keeps timing out. Am I missing something? http://oliverkrystal.pastebin.com/m60761119 Thanks.

*EDIT2* Yup, I missed something Smile But should my script really be using 3k or memory? Blum

Too many lonely hearts in the real world
Too many bridges you can burn
Too many tables you can't turn
Don't wanna live my life in the real world

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
For geany, just do something

For geany, just do something like this:

${GetProcessPath} "explorer.exe" $0
${If} $0 != 0
	; check path here
	MessageBox MB_OK "Explorer path:$\r$\n$0"
${Else}
	MessageBox MB_OK "Process does not exist."
${EndIf}

For AutoClose, you don't want a timeout value in ProcessWaitClose. You want it to wait indefinitely:

${ProcessWaitClose} "$WatchEXE" "-1" $0

Memory is whatever it is... the System plugin might use that much. 3k really isn't that much.

Mark Sikkema
Offline
Last seen: 13 years 2 months ago
Developer
Joined: 2009-07-20 14:55
Child processes

Would there be a way to enum child processes ?

Winamp Portable browser has a behavior to open up iexplorer.exe as a new window.
The problem is that Winamp Portable and it's child processes have a redirected userprofile, so it would probably be better for the launcher to enum the child processes and close them.

Thanx, hopefully this safes me re-inventing the wheel (again) !

Formerly Gringoloco
Windows XP Pro sp3 x32

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Currently, no. This is

Currently, no. This is actually kind of a PITA. It requires enumerating all system processes via a different method (the Toolhelp32 API), then getting each process's parent PID from the snapshot structure.

Mark Sikkema
Offline
Last seen: 13 years 2 months ago
Developer
Joined: 2009-07-20 14:55
PITA==easy ???

I assume ?

I googled a bit and see what you mean, just not having much time lately.

I'm to busy getting 'newtextreplace' plugin working properly for all unicode formats !

I have to have a look into this some other time !

Formerly Gringoloco
Windows XP Pro sp3 x32

Steve Lamerton
Steve Lamerton's picture
Offline
Last seen: 11 years 2 months ago
Developer
Joined: 2005-12-10 15:22
This

is probably no help to you guys but Toucan has the code for this in C++ at the moment, you can see it in the KillConime function of this file.

[Link updated to go straight to line 271 for ease of use - Chris]

Mark Sikkema
Offline
Last seen: 13 years 2 months ago
Developer
Joined: 2009-07-20 14:55
Probably this is a lot of help, to me !

I had a quick look into it! But as I said, first I have to 'finish' my other tasks.

But after that, I probably contact you about this code, if I need some info about.

Thanx

Formerly Gringoloco
Windows XP Pro sp3 x32

Steve Lamerton
Steve Lamerton's picture
Offline
Last seen: 11 years 2 months ago
Developer
Joined: 2005-12-10 15:22
Sure,

email address is on my profile page, feel free to fire any questions that way if you want!

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Yeah, code is easy. NSIS

Yeah, code is easy. NSIS implementation is hard though. NSIS System plugin work with structures is messy.

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
FYI your wcsncmp() code is

FYI your wcsncmp() code is not right:

1) The last parameter is the number of characters to compare, not bytes.
2) sizeof(pe.szExeFile) returns the number of bytes in the character array PLUS the terminating NULL
3) you are using unicode strings (based on your use of wcsncmp), so sizeof() is returning double the number of characters.

I think you should be using '(sizeof(pe.szExeFile) / 2) - 1' for your count parameter.

That said... a fair bit of that code is still over my head Wink Keep up the good work!

Steve Lamerton
Steve Lamerton's picture
Offline
Last seen: 11 years 2 months ago
Developer
Joined: 2005-12-10 15:22
You

are of course correct sir! I have updated it using a method that is probably better: wcsnlen, thanks for pointing it out Smile

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
PITA = Pain In The Ass

PITA = Pain In The Ass

Mark Sikkema
Offline
Last seen: 13 years 2 months ago
Developer
Joined: 2009-07-20 14:55
Thanx for that !

As I found some script, I beleave of you, on the internet, I allready had a go on this.

But strangely enough, the iexplore.exe 's being started by Winamp aren't registered as child processes. Although they inherit WinampPortable's environment strings?!?!

I had ago on reading environment strings of remote processes, through the system plugin, but no luck so far. I'm affraid I got to do it in C !

Formerly Gringoloco
Windows XP Pro sp3 x32

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Added ${GetProcessParent} and other internal changes.

It also now uses CreateToolhelp32Snapshot where possible so functions should be more successful on Vista+ when retrieving process names and using ProcessExists with a process name. Success rate will be unchanged for process paths, as this still requires sufficient rights for OpenProcess to succeed.

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
PortableApps.com Launcher

Eric,

I've put this in the PortableApps.com Launcher in place of FindProcDLL, but just need to clarify something. What license did you intend to release this under? Assuming that you've released it for the NSIS community, it should be zlib. Are you happy with that? GPL will be slightly trickier to integrate into the launcher for potential closed-source linking reasons so it'd also be nice if you'd go zlib.

I've also got a few ideas. For ProcessWait, you should create the handle once and then more or less wait for it to become invalid, rather than finding the process and opening a handle to it each time; this would be more efficient. Also there should be a LogicLib extension so that you could do things like ${If} ${ProcessExists} $0. The ability to search for processes by full path would be good too. (Your process handles are trying to get more access than they need, they only need the limited access thing for most things, see MSDN for reference of course). I think I can do all these.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Feel free to use whatever

Feel free to use whatever license suits the platform. I'm not familiar with zlib, but if it works for the platform, go for it.

Regarding the other stuff:
1) ProcessWait waits for a process to exist. ProcessWaitClose waits for a process to end. And in that function yes, a handle is opened only once.

2) How would I go about implementing a logiclib extension? Is there a sort of "SDK" for that? Or are you just saying some functions should return BOOL values directly? Can you even return a value directly from a macro?

3) EnumProcessPaths is what you want to search paths, although the searching is left to the user. The function itself will call a user function (like a callback) with each running process path it can retrieve.

4) All of my OpenProcess calls use the minimum access rights that the associated functions require. In the different places I use OpenProcess, it is called with either PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, SYNCHRONIZE, or PROCESS_TERMINATE. Those are the basic access rights for the functions I'm using to succeed.

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
"Subject"

The zlib license is a nice short permissive open source license. The Wikipedia page on it covers it quite nicely.

Your points:

  1. Ah, whoops, I was looking at the wrong bit of code then. Now I look at ProcessWaitClose, that really is a lot more efficient than our current FindProcDLL implementation. Now I can change the way we do that... great.
  2. I've looked more closely at it now and see that although you could use PROCESS_QUERY_LIMITED_INFORMATION and QueryFullProcessImageName - but they're both Vista+ which is no good. The LIMITED variant is thus not going to help unless we start using different DLL calls for different OS versions, which is potentially hazardous.

Now for new stuff.

I've redone some of your code to make it more efficient, by using Utils.nsh's ${CallArtificialFunction} (as used by the core *Func.nsh). This also means that you don't need the !insertmacro ProcessFunction before using any block of code. For backwards compatibility, all those macros are however left in - blank - in the same way as with *Func.nsh.

Also, I've started implementing ${ProcessExists} for LogicLib for you (and renamed the old ProcessExists to GetProcessPID which seems much more accurate in my opinion).

For backwards-incompatibility notification, I'm leaving in the ProcessExists macro so people using the old version will be told with an error that "ProcessExists has been renamed to GetProcessPID".

The only reason I'm not done yet is that inside a LogicLib comparison macro you can't use LogicLib - so I'm trimming down the ProcFuncs macro to just the bare essentials and working on that, de-LogicLibbing it.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
${ProcessExists} for use with LogicLib

I've now implemented my LogicLib operator. The source code of my modifications to ProcFunc.nsh is available here (the version number 2.1 was a fairly arbitrary choice!). Sample usage of the macro is available in the Launcher.

Thanks for the ProcessWaitClose macro, it's far more efficient than the poll-every-second FindProcDLL method, and should also be more reliable.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Those are some nice updates.

Those are some nice updates. I haven't looked everything over, as it's a lot to absorb in a few minutes, but I have one correction for you.

Line 789 should jump to 'endloop' if Process32FirstW fails because the snapshot handle must still be closed.

I'll have to have a look at Util.nsh to see how it handles the conditional code inclusion.

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
CallArtificialFunction

CallArtificialFunction is a really well thought out function; as the fact that your function is needed to be declared is discovered in the middle of a non-global section, it puts it in its own function with a global label - so then it's Call .WhateverItIs. Thus it doesn't include the code unless it's used, same as the older approach which they used to use and you used also. It's all round a much nicer solution for end-user use merely due to the fact that you don't need the !insertmacro in the header for each individual function you want to use.

Thanks for that fix. I also optimised the IntCmp slightly by turning IntCmp $4 0 endloop loop loop into IntCmp $4 0 endloop | Goto loop and turned goto into Goto (ghastly having lower-case, isn't it... don't know what possessed me).

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Yeah, I had a look at the

Yeah, I had a look at the Util.nsh header, neat idea.

Thanks for all the work you put into this!

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
@Chris I updated the header

@Chris

I updated the header to fix a possible stack corruption in the ProcessWait2 function, and I changed all functions to use the System plugin private stack to save registers instead of all the Exch / Push / Pop on the main stack. I ran some timer tests and the performance difference is nil, but it makes the top and bottom of the functions much easier to read (at the expense of the possibility of some lazy register usage).

I uploaded the updated version to my original link, so you can take a look and update your repository.

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
Thanks

I've merged it in and committed it.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Hmm, I'm not seeing the

Hmm, I'm not seeing the changes in your repository...

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
Haven't pushed it

Quite so, I haven't pushed it yet. It's committed in my local repository though Smile

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
Pushed

I've pushed it now.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Bruce Pascoe
Offline
Last seen: 12 years 11 months ago
Joined: 2006-01-15 16:14
...

Yeah, distributed version control is hard to wrap your mind around when you're used to centralized ones like CVS and SVN. I picked it up pretty quick, but that's probably just because I'm a really fast learner. Smile

wraithdu
Offline
Last seen: 11 years 6 months ago
Developer
Joined: 2007-06-27 20:22
Update

Fixed some things for NSISu.

@Chris
After you've tested this, you can commit it to your repository.

quala
Offline
Last seen: 7 years 3 months ago
Joined: 2011-02-03 09:07
This File is not Work! (Win7

This File is not Work! (Win7 64) - makensis.ese Crashed!
Please Give me work File! Please.. ))

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
It does work

It does work; this is in PAL. Why exactly are you trying to use ProcFunc.nsh directly anyway? If it's for PortableApps.com stuff, you should use the PortableApps.com Launcher.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

quala
Offline
Last seen: 7 years 3 months ago
Joined: 2011-02-03 09:07
Ups, take my apologies. This

Ups, take my apologies.
This file will not work in clean NSIS yes?
In Native NSIS it will work?

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 9 years 5 months ago
Joined: 2007-04-15 21:08
Works fine

As I said, it works fine - it's used in PAL. But why are you trying to use it?

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

quala
Offline
Last seen: 7 years 3 months ago
Joined: 2011-02-03 09:07
I need were a functions of

I need were a functions of management process under NSIS. I have found in internet this code. Try to force his(its) work at local installer. Honestly dialect I not quite understand what is a "PortableApps.com Launcher")) Excuse me. The English know not much well - use translator. see I something do not understand.

I it is necessary in usual installer type of the archiver 7z - control the process.

In general that beside me it works - only under determined condition. As it is woke;waked it is impossible given macros turn around the macros or function.

Pages

Log in or register to post comments