Что нового

Модификация RunWait: выполнить файл, и ждать пока не завершится сам процесс и...

sforce5

Олл фо ЛулзЪ
Сообщения
160
Репутация
41
Не хватило лимита дописать до конца в заголовок несколько букв ))

В общем проблема, нужен не совсем RunWait, нужна модификация этой функции которая бы ждала как запускаемый файл, так и все порождаемые процессы запущенным процессом

Во как сложно, еле объяснил :smile:
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Если нужно проверить дочерние процессы только 1-го уровня, то это не так сложно (см. пример). А вот в случае проверки не только непосредственно дочерних процессов, но и порожденных в свою очередь ими дочерних процессов и т.д., здесь все будет выглядеть намного сложнее (а вообще оно нужно?).

Код:
#Include <WinAPIEx.au3>

Func _RunWait($sPath)

	Local $PID, $List

	$PID = Run($sPath)
	If @error Then
		Return SetError(1, 0, 0)
	EndIf
	While 1
		Sleep(500)
		$List = ProcessList()
		If @error Then
			Return SetError(1, 0, 0)
		EndIf
		For $i = 1 To $List[0][0]
			If ($List[$i][1] = $PID) Or (_WinAPI_GetParentProcess($List[$i][1]) = $PID) Then
				ContinueLoop 2
			EndIf
		Next
		ExitLoop
	WEnd
	Return 1
EndFunc   ;==>_RunWait
 

amel27

Продвинутый
Сообщения
146
Репутация
55
Yashied сказал(а):
в случае проверки не только непосредственно дочерних процессов, но и порожденных в свою очередь ими дочерних процессов и т.д., здесь все будет выглядеть намного сложнее

можно через WMI:

Код:
_RunWaitEx(@ComSpec)

Func _RunWaitEx($sCMD, $sWorkDir = "", $iShow = @SW_MAXIMIZE, $iOpt = 0)
	Local $oWMI = ObjGet("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
	Local $oSink = ObjCreate("WbemScripting.SWbemSink"), $iSink=1

	ObjEvent($oSink, "_RunWaitEx_")
	$oWMI.ExecNotificationQueryAsync ($oSink, _
	"SELECT * FROM __InstanceOperationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'")

	Assign("$a_RunWaitEx", "", 2)
	Global $a_RunWaitEx[2][2]=[[1,""], [Run($sCMD, $sWorkDir, $iShow, $iOpt),$sCMD]]
	If $a_RunWaitEx[1][0]=0 Then Return SetError(1,0,0)

	Do
		If $a_RunWaitEx[$iSink][0] >0 Then
			ProcessWaitClose($a_RunWaitEx[$iSink][0])
			Sleep(1000)
		EndIf
		$iSink +=1
	Until $iSink > $a_RunWaitEx[0][0]
EndFunc

Func _RunWaitEx_OnObjectReady($oOutParams, $oAsyncContext)
	Local $MOD = $oOutParams.Path_.Class
	Local $PID = $oOutParams.TargetInstance.ProcessId
	Local $PID_Parent = $oOutParams.TargetInstance.ParentProcessId
	Local $CMD = $oOutParams.TargetInstance.CommandLine

	If IsDeclared("$a_RunWaitEx") =0 Then Return
	If $MOD = "__InstanceModificationEvent" Then Return

	For $i=1 To $a_RunWaitEx[0][0]
		If $a_RunWaitEx[$i][0] >0 Then
			If $MOD = "__InstanceCreationEvent" And $a_RunWaitEx[$i][0] = $PID_Parent Then
				$a_RunWaitEx[0][0]+=1
				ReDim $a_RunWaitEx[$a_RunWaitEx[0][0]+1][2]
				$a_RunWaitEx[$a_RunWaitEx[0][0]][0]=$PID
				$a_RunWaitEx[$a_RunWaitEx[0][0]][1]=$CMD

				ConsoleWrite('Started child process: '& $CMD &@CRLF)
			ElseIf $MOD = "__InstanceDeletionEvent" And $a_RunWaitEx[$i][0] = $PID Then
				$a_RunWaitEx[$i][0]=0
				ConsoleWrite('Finished process: '& $a_RunWaitEx[$i][1] &@CRLF)
			EndIf
		EndIf
	Next
EndFunc
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Не работает (так как хотелось бы).

:(

Вот два примера: "test1.exe" запускает "test2.exe", а последний, в свою очередь, запускает "calc.exe". Так вот, "calc.exe" не отслеживается. Здесь сложность еще в том, что за время задержки Sleep(), может запуститься какой-нибудь дочерний процесс, сам что-нибудь запустить, а затем выйти. В этом случае будет совсем все грустно.

Код:
_RunWaitEx('test1.exe')


test1.exe
Код:
Run('test2.exe')
MsgBox(0, '', '1')


test2.exe
Код:
Run('calc.exe')
MsgBox(0, '', '2')
 

amel27

Продвинутый
Сообщения
146
Репутация
55
Yashied сказал(а):
Не работает (так как хотелось бы)

да не, проблема не в SLEEP - ф-ции отрабатывают асинхронно независимо от длительности паузы - можно хоть 10 сек поставить, просто скрипт будет долго "висеть" до выхода

посмотрел - проблема в том, что при малом интервале между запусками события могут приходить не в той последовательности, в которой запускались процессы, т.е. дочерний уже пришел, а родительского еще нет в массиве... значит, надо ставить таймаут в самой ф-ции, после которого уже не ждать появления родителя и считать процесс "чужим"... завтра поправлю
 

amel27

Продвинутый
Сообщения
146
Репутация
55
если попытаться обойтись без таймеров, то где-то так:

Код:
; $a_RunWait[][0] - PID
; $a_RunWait[][1] - PID_Parent
; $a_RunWait[][2] - Name
; $a_RunWait[][3] - Command Line
; $a_RunWait[][4] - Exists/TimeOut

Func _RunWait($sCMD, $sWorkDir = "", $iShow = @SW_MAXIMIZE, $iOpt = 0)
	Local $oWMI = ObjGet("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
	Local $oWMI_Create = ObjCreate("WbemScripting.SWbemSink"), $i=1
	Local $oWMI_Delete = ObjCreate("WbemScripting.SWbemSink")

	ObjEvent($oWMI_Create, "__RunWait_Create_")
	ObjEvent($oWMI_Delete, "__RunWait_Delete_")
	$oWMI.ExecNotificationQueryAsync ($oWMI_Create, _
	"SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'")
	$oWMI.ExecNotificationQueryAsync ($oWMI_Delete, _
	"SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'")

	Assign("$a_RunWait", "", 2)
	Global $a_RunWait[2][5]=[[0]]
	$a_RunWait[0][1] = Run($sCMD, $sWorkDir, $iShow, $iOpt)
	If $a_RunWait[0][1]=0 Then Return SetError(1, 0, 0)
	Sleep(1000) ; пауза для обработки событий
	
	While __RunWait_Test()
		For $i=1 To $a_RunWait[0][0]
			If $a_RunWait[$i][4]==True Then
				ProcessWaitClose($a_RunWait[$i][0])
				ExitLoop
			EndIf
		Next
		Sleep(1000)
		__RunWait_Scan()
	WEnd
EndFunc

; UDF обработки события создания процесса
; ----------------------------------------
Func __RunWait_Create_OnObjectReady($oOutParams, $oAsyncContext)
	__RunWait_Event($oOutParams, 4)
EndFunc ; ==> __RunWait_Create_OnObjectReady

; UDF обработки события удаления процесса
; ----------------------------------------
Func __RunWait_Delete_OnObjectReady($oOutParams, $oAsyncContext)
	__RunWait_Event($oOutParams,-4)
EndFunc ; ==> __RunWait_Delete_OnObjectReady

; UDF фиксации принятого события в массиве
; ----------------------------------------
Func __RunWait_Event($oOutParams, $iTimeOut)
	Local $iParentId = $oOutParams.TargetInstance.ParentProcessId
	If $iParentId=0 Then Return       ; Если нет родителя - выход
	If IsDeclared("$a_RunWait")=0 Then Return

	$a_RunWait[0][0]+=1
	ReDim $a_RunWait[$a_RunWait[0][0]+1][5]

	$a_RunWait[$a_RunWait[0][0]][0] = $oOutParams.TargetInstance.ProcessId
	$a_RunWait[$a_RunWait[0][0]][1] = $iParentId
	$a_RunWait[$a_RunWait[0][0]][2] = $oOutParams.TargetInstance.Name
	$a_RunWait[$a_RunWait[0][0]][3] = $oOutParams.TargetInstance.CommandLine
	$a_RunWait[$a_RunWait[0][0]][4] = $iTimeOut

	__RunWait_Scan()
EndFunc ; ==> __RunWait_Event

; UDF обработки массива событий
; ----------------------------------------
Func __RunWait_Scan()
	For $i=1 To $a_RunWait[0][0]     ; только события
		If IsInt($a_RunWait[$i][4])=0 Then ContinueLoop
		Local $i4Bak = $a_RunWait[$i][4]
		; событие создания/удаления главного  процесса
		If $a_RunWait[$i][0]=$a_RunWait[0][1] Then
			$a_RunWait[$i][4] = True ; событие -> процесс -> Start
		EndIf
		For $j=1 To $a_RunWait[0][0] ; поиск связанных записей
			If $a_RunWait[$i][1]=$a_RunWait[$j][0] Then ; найден "родитель"
				$a_RunWait[$i][4] = True ; событие -> процесс -> Start
			EndIf
			If $i4Bak <0 And ($a_RunWait[$i][0]=$a_RunWait[$j][0]) Then 
				$a_RunWait[$i][4] = False ; событие -> процесс -> Stop
				$a_RunWait[$j][4] = False ; закрыт по всем дублям
			EndIf
		Next
		If IsInt($a_RunWait[$i][4]) And $i4Bak>0 Then $a_RunWait[$i][4]-=1
		If IsInt($a_RunWait[$i][4]) And $i4Bak<0 Then $a_RunWait[$i][4]+=1
	Next
EndFunc ; ==> __RunWait_Scan

; UDF поиска в массиве "живых" событий
; ----------------------------------------
Func __RunWait_Test()
	For $i=1 To $a_RunWait[0][0]
		If $a_RunWait[$i][4]==True Or (IsInt($a_RunWait[$i][4]) And $a_RunWait[$i][4]) Then Return True
	Next
	Return False
EndFunc


Код:
#include <array.au3>
_RunWait("test1.exe /Switch")
_ArrayDisplay($a_RunWait)


P.S. Увы, метод WMI не является универсальным - процессы, запущенные и сразу закрытые (в пределах 1 сек) могут быть потеряны - для WMI это минимальная величина чувствительности.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Логично предположить, что, так как Windows сохраняет PID родительского процесса, даже, если этот (родительский) процесс уже давно не существует, то по идеи, любым созданным в системе процессам не должен присваиваться этот PID, до тех пор, пока хоть один процесс в системе хранит информацию о нем. Из этого следует, что Windows где-то хранит таблицу (дерево) процессов. Вот только вопрос где и как ее получить?
 

amel27

Продвинутый
Сообщения
146
Репутация
55
Yashied сказал(а):
Windows где-то хранит таблицу (дерево) процессов

не думаю, это не Unix... есть другая идея:

Код:
#include <WinAPI.au3>

_RunWait("test1.exe")
ConsoleWrite("==> "& @error &@CRLF)

Func _RunWait($sCMD)
	Local Const $tagJobObjectInfoClass1 = "int64 TotalUserTime;int64 TotalKernelTime;int64 ThisPeriodTotalUserTime;" & _
	"int64 ThisPeriodTotalKernelTime;dword TotalPageFaultCount;dword TotalProcesses;dword ActiveProcesses;dword TotalTerminatedProcesses"

	Local $aRet = DllCall("kernel32.dll", "hwnd", "CreateJobObject", "ptr", 0, "ptr", 0)
	If IsArray($aRet)=0 Or $aRet[0]=0 Then Return SetError(1, 0, False)
	Local $hJob = $aRet[0], $hProcess, $hThread
	
	Local $tJI = DllStructCreate($tagJobObjectInfoClass1), $pJI = DllStructGetPtr($tJI)
	Local $tPI = DllStructCreate($tagPROCESS_INFORMATION), $pPI = DllStructGetPtr($tPI)
	Local $tSI = DllStructCreate($tagSTARTUPINFO), $pSI = DllStructGetPtr($tSI)
    DllStructSetData($tSI, 'Size', DllStructGetSize($tSI))

	If Not _WinAPI_CreateProcess("", $sCMD, 0, 0, False, 4, 0, 0, $pSI, $pPI) Then Return SetError(2, _WinAPI_CloseHandle($hJob), False)
    $hProcess = DllStructGetData($tPI, 'hProcess')
	$hThread  = DllStructGetData($tPI, 'hThread')
	
	DllCall("kernel32.dll", "BOOL", "AssignProcessToJobObject", "hwnd", $hJob, "hwnd", $hProcess)
	DllCall("kernel32.dll", "dword", "ResumeThread", "hwnd", $hThread)
	_WinAPI_CloseHandle($hThread)

	Do
		DllCall("kernel32.dll", "BOOL", "QueryInformationJobObject", "hwnd", $hJob, _
			"int", 1, "ptr", $pJI, "dword", DllStructGetSize($tJI), "ptr", 0)
		Sleep(1000)
	Until DllStructGetData($tJI, "ActiveProcesses") =0
	
	_WinAPI_CloseHandle($hProcess)
	_WinAPI_CloseHandle($hJob)
	
	Return True
EndFunc
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Замечательная идея, как-то я ро Job'ы и не вспомнил.
 
Верх