1 (изменено: Androgen, 2007-07-21 22:01:01)

Тема: AutoHotkey: получение дополнительной инфы о процессах

Средства для работы с процессами в AutoHotkey довольно удобны, а синтаксис прост и логичен (что в AHK бывает далеко не всегда). И всё же кое-чего не хватает. Вот скажем, нужно вам перезапустить к-либо процесс. Убить-то вы его убили, а вот как запустить, если путь к исполнимому файлу вам заранее неизвестен? Или вы думаете, что известен, но на этом компе он (путь) другой? Как узнать путь к exe-шнику процесса? Или даже так: и путь вам известен, и на этом компе он верный, но процесс был запущен с некими параметрами командной строки. И если вы этот процесс перезапустите без них (параметров), то получите совсем не тот результат, на который рассчитываете.
А вот другая ситуация: я тут недавно столкнулся с тем, что в справке к WinRar'у не описаны параметры для распаковки сразу нескольких архивов за один раз. Но сам-то WinRar из контекстного меню это делает! Как? Ну, узнать это просто: даем РАРу команду из контекстного меню распаковать несколько архивов. Архивы берем побольше, чтобы РАР работал подольше . Он начинает распаковывать, а мы не торопясь открываем Process Explorer ( http://www.sysinternals.com/Utilities/P … lorer.html ) и смотрим, какую же командную строку создал WinRar сам себе. Всё, теперь мы обладатели недокументированной фичи WinRar'а и можем распаковывать сразу толпу архивов одним экземпляром РАРа. Классно. Но что, если прога отрабатывает свои действия так быстро, что мы не успеваем метнуться в Process Explorer? Или мы хотим делать эти действия не "руками", а скриптом?
Вот для таких целей я и предлагаю пару функций. Первая функция позволяет узнать путь к экзешнику процесса, а вторая - получить целиком командную строку для процесса. Эти функции я выдрал из мега скрипта shimanovhttp://www.autohotkey.com/forum/viewtopic.php?t=9000. Я их слегка модернизировал-упростил и прокомментировал. Функции непростые, поэтому, если найдете ошибки - дайте знать.
На самом деле, область применения этих функций значительно шире, чем может показаться на первый взгляд, поэтому присмотритесь.
Исправлено 11.10.2006 до версии 1.1

;*******************************************************************************
; AutoHotkey Version:   1.0.44.09+
; Автор:                Androgen Belkin
; Имя скрипта:          GetExtProccessInfo().ahk (v.1.1)
;*******************************************************************************
; Функции для:
; 1. Получения пути к исполнимому файлу процесса
; 2. Получения командной строки для процесса
; Известные ограничения:
; а) не удается получить доступ к некоторым (немногим) системным процессам,
;    в этом случае функции возвращают пустую строку
; б) функции, возможно, не будут работать в линейке WIN 9x, но я не проверял
;*******************************************************************************

;-------------------------------------------------------------------------------------------------------------
GetModuleFileNameEx( p_pid ) ; функция получения пути к исполнимому файлу процесса (по его PID'у)
{
    h_process := DllCall( "OpenProcess", "UInt", 0x10|0x400, "Int", false, "UInt", p_pid ) ; открываем хэндл для PID процесса...
    ; ...с нужными правами (они понадобятся для GetModuleFileNameExA) и без наследования хэндла
    ; права: 0x10 = PROCESS_VM_READ, 0x400 = PROCESS_QUERY_INFORMATION
    If ( ErrorLevel OR h_process = 0 ) ; если почему-либо не удалось открыть хэндл (или он открыт для несуществующего PID'а)...
        Goto, Return_1 ; перейти к закрытию хэндла и выйти из функции

    VarSetCapacity( pPath, 259 ) ; обеспечиваем достаточную вместимость переменной (в символах, путь длиннее не бывает)
    DllCall( "psapi.dll\GetModuleFileNameExA", "UInt", h_process, "UInt", 0, "Str", pPath, "UInt", 259 ) ; получаем путь...
    ; ...для указанного хэндла процесса. GetModuleFileNameExA (ANSI), GetModuleFileNameExW (Unicode)

    Return_1: ; метка перехода к закрытию хэндла
    DllCall( "CloseHandle", h_process ) ; закрываем хэндл
    Return, pPath ; возвращаем путь к файлу процесса
} ; конец функции
;-------------------------------------------------------------------------------------------------------------

;-------------------------------------------------------------------------------------------------------------
GetRemoteCommandLine( p_pid_target ) ; функция получения командной строки для процесса (по его PID'у)
{
    hp_target := DllCall( "OpenProcess", "UInt", 0x10, "Int", false, "UInt", p_pid_target ) ; открываем хэндл для PID процесса...
    ; ...с правами для чтения 0x10 = PROCESS_VM_READ (нужны для ReadProcessMemory) и без наследования хэндла
    If ( ErrorLevel OR hp_target = 0 ) ; если почему-либо не удалось открыть хэндл (или он открыт для несуществующего PID'а)...
        Goto, Return_2 ; перейти к закрытию хэндла и выйти из функции

    hm_kernel32 := DllCall( "GetModuleHandle", "Str", "kernel32.dll" ) ; узнаем хэндл библиотеки kernel32.dll
    pGetCommandLineA := DllCall( "GetProcAddress", "UInt", hm_kernel32, "Str", "GetCommandLineA" ) ; получаем указатель...
    ; ...на адрес функции GetCommandLineA (ANSI), содержащейся в kernel32.dll

    buffer_size = 6 ; количество байт для переменной
    VarSetCapacity( buffer, buffer_size ) ; обеспечиваем достаточную вместимость переменной
    DllCall( "ReadProcessMemory", "UInt", hp_target, "UInt", pGetCommandLineA, "UInt", &buffer, "UInt", buffer_size, "UInt", 0 )
    ; для хэндла PID'а получаем перенаправление функцией GetCommandLineA на указатель на адрес командной строки...
    ; ...и кладем его по адресу "buffer" в памяти

    Loop, 4 ; преобразовываем указатель на адрес командной строки в целое число
        ppCommandLine += ( ( *( &buffer+A_Index ) ) << ( 8*( A_Index-1 ) ) ) ; создаем целое число, складывая его байты

    buffer_size = 4 ; количество байт для переменной
    VarSetCapacity( buffer, buffer_size, 0 ) ; обеспечиваем достаточную вместимость переменной, и очищаем её
    DllCall( "ReadProcessMemory", "UInt", hp_target, "UInt", ppCommandLine, "UInt", &buffer, "UInt", buffer_size, "UInt", 0 )
    ; для хэндла PID'а читаем адрес командной строки, и кладем его по адресу "buffer" в памяти

    Loop, 4 ; преобразовываем адрес в целое число
        pCommandLine += ( ( *( &buffer+A_Index-1 ) ) << ( 8*( A_Index-1 ) ) ) ; создаем целое число, складывая его байты

    ; Адрес командной строки получен, теперь извлекаем его содержимое
    buffer_size = 520 ; столько символов будем читать из командной строки (мне кажется, длиннее быть не может)
    VarSetCapacity( result, buffer_size, 1 ) ; обеспечиваем достаточную вместимость переменной, делаем её побайтной
    DllCall( "ReadProcessMemory", "UInt", hp_target, "UInt", pCommandLine, "UInt", &result, "UInt", buffer_size, "UInt", 0 )
    ; для хэндла PID'а читаем командную строку, и кладем её по адресу "result" в памяти (просто помещаем значение в переменную)

    Return_2: ; метка перехода к закрытию хэндла
    DllCall( "CloseHandle", "UInt", hp_target ) ; закрываем хэндл
    Return, result ; возвращаем полную командную строку для PID'а процесса
} ; конец функции
;-------------------------------------------------------------------------------------------------------------

Пара примеров применения:

; Откройте какой-нибудь файл в Блокноте, и запустите пример.
; Заголовок окна Блокнота изменится и будет содержать полный путь к открытому в нем файлу.
#Include C:\GetExtProccessInfo().ahk ; путь к скрипту с функциями

ID_Notepad := WinExist( "ahk_class Notepad" ) ; получаем ID Блокнота
If NOT ID_Notepad ; если Блокнот не найден...
{
    MsgBox, Блокнот не запущен!
    ExitApp ; конец скрипта
}
WinGet, PID_Target, PID, ahk_id %ID_Notepad% ; получаем PID найденного окна Блокнота
Exe_Path := GetModuleFileNameEx( PID_Target ) ; вызываем функцию получения пути процесса Блокнота
If NOT Exe_Path ; если что-то не так...
{
    MsgBox, Процесс не существует, или к нему нет доступа
    ExitApp ; конец скрипта
}
Com_Line := GetRemoteCommandLine( PID_Target ) ; вызываем функцию получения ком. строки Блокнота
If NOT Com_Line ; если что-то не так...
{
    MsgBox, Процесс не существует, или к нему нет доступа
    ExitApp ; конец скрипта
}
StringReplace, File_Path, Com_Line, %Exe_Path% ; удаляем из строки путь к Блокноту
StringReplace, File_Path, File_Path, ",, All ; удаляем из строки все кавычки, если они есть
File_Path = %File_Path% ; избавляемся от возможных пробелов в начале строки
WinSetTitle, ahk_id %ID_Notepad%,, %File_Path% - Блокнот ; вписываем путь в заголовок окна Блокнота
WinActivate ; активируем окно Блокнота
; А вот пример получения информации о некоторых процессах
#Include C:\GetExtProccessInfo().ahk ; путь к скрипту с функциями

; Process, Exist, AutoHotkey.exe
Process, Exist, svchost.exe
; Process, Exist, Totalcmd.exe
PID_Target := ErrorLevel ; в ErrorLevel содержится PID процесса
If NOT PID_Target
{
    MsgBox, Процесс не существует
    ExitApp
}
Exe_Path := GetModuleFileNameEx( PID_Target ) ; вызываем функцию получения пути процесса
If NOT Exe_Path ; если что-то не так...
{
    MsgBox, Процесс не существует, или к нему нет доступа
    ExitApp ; конец скрипта
}
Com_Line := GetRemoteCommandLine( PID_Target ) ; вызываем функцию получения ком. строки процесса
If NOT Com_Line ; если что-то не так...
{
    MsgBox, Процесс не существует, или к нему нет доступа
    ExitApp ; конец скрипта
}
MsgBox, Exe_Path: %Exe_Path%`nCom_Line: %Com_Line%

Исправил одну строчку:

success := DllCall( "ReadProcessMemory", "UInt", hp_target, "UInt", pCommandLine, "UInt", &result, "UInt", buffer_size, "UInt", 0 )

на

DllCall( "ReadProcessMemory", "UInt", hp_target, "UInt", pCommandLine, "UInt", &result, "UInt", buffer_size, "UInt", 0 )

Это просто неподчищенный результат тестов. На работу скрипта это никак не влияет, но я всё же решил её исправить, чтобы не путала: скрипт и так не из простых.

Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).

2

Re: AutoHotkey: получение дополнительной инфы о процессах

Исправлено имя переменной Path на pPath (и текущая версия скрипта - 1.1).
Основания: если в скрипе, вызывающем функцию GetModuleFileNameEx, отсутствовала директива #NoEnv, функция возвращала значение переменной окружения %Path%.
Исправления внесены в пост #1.

Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).

3

Re: AutoHotkey: получение дополнительной инфы о процессах

Исправлена пара недосмотров. Спасибо YMP'у, его справедливые замечания можно посмотреть здесь

Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).