Что нового

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

sforce5

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

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

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

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 713
Если нужно проверить дочерние процессы только 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 713
Не работает (так как хотелось бы).

:(

Вот два примера: "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 713
Логично предположить, что, так как 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 713
Замечательная идея, как-то я ро Job'ы и не вспомнил.
 
Верх