1

Тема: AutoHotkey: проверка возможности запуска файла

Чтобы было понятно, что и зачем я здесь предлагаю, я начну "пораньше". А именно со справки к командам Run и RunWait из которой следует, что запускать можно:

Справка в переводе YMP'а пишет:

документ, URL, исполняемый файл (.exe, .com, .bat, и т.п.), ярлык (.lnk) или "системный глагол". Если задаётся имя локального файла без указания пути, вначале производится поиск в папке, определяемой встроенной переменной A_WorkingDir. Если файл там не найден, система будет искать его в папках, включённых в системный путь (переменная окружения Path).

(Спасибо YMP'у за то огромное количество качественных переводов, которые он сделал).

Справка немногословна, поэтому я опишу чуть подробнее, как именно происходит запуск файла, если не указан путь к нему. Итак:

1. Сначала запускаемый файл ищется в рабочей папке скрипта и если там не найден, то дальнейший поиск перепоручается системе
2. Сначала система ищет одноимённый псевдоним в реестре по адресу HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths (см. детали по ссылке ниже)
3. Затем она ищет нужный файл в папке Windows\system32
4. Затем ищет его в папке Windows (без подпапок)
5. И наконец, файл ищется в папках, указанных в переменной окружения Path

Если файл в итоге найден - он запускается, нет - облом. Естественно.

Примечание 1: В MSDN указан немного иной порядок, но:
во-первых, именно в таком порядке запускает файлы моя система WinXP SP2 (а, скажем, в Win98 порядок другой)
во-вторых, для рассматриваемых целей не важно где именно будет найден файл, главное - может он быть запущен или нет

К чему все эти подробности? А к тому, что иногда нужно знать существует ли запускаемый файл и, соответственно, будет ли он запущен командой Run. Особенно это полезно при запуске консольных утилит через comspec, когда ErrorLevel не информативен. Вот посмотрите:

RunWait, %comspec% /c bootcfg.exe /? > c:\bootcfg.txt,, Hide ; запрос и сохранение справки по утилите bootcfg
MsgBox, ErrorLevel = %ErrorLevel% ; ErrorLevel = 0, всё нормально

RunWait, %comspec% /c bootcfg.exe /help > c:\bootcfg.txt,, Hide ; несуществующий параметр help
MsgBox, ErrorLevel = %ErrorLevel% ; ErrorLevel = 1

RunWait, %comspec% /c bootcfg1.exe /? > c:\bootcfg.txt,, Hide ; несуществующий файл bootcfg1.exe
MsgBox, ErrorLevel = %ErrorLevel% ; ErrorLevel = 1

RunWait, %comspec% /c "bootcfg.exe /? > z:\bootcfg.txt",, Hide ; несуществующий файл z:\bootcfg.txt
MsgBox, ErrorLevel = %ErrorLevel% ; ErrorLevel = 1

RunWait, %comspec% /c bootcfg.exe /? > c:\bootcfg.txt,, Hide ; файл bootcfg.txt с атрибутом только для чтения
MsgBox, ErrorLevel = %ErrorLevel% ; ErrorLevel = 1

Т.е. неясно таки был запущен файл или всё же нет. Проблема особенно актуальна, если вы хотите запустить некий стандартный файл (ну, скажем, ftp.exe), а его на этой системе нет (ох, ручки мои шаловливые).
Для проверки существования файла в AutoHotkey есть команда IfExist и функция FileExist, но они не годятся, т.к.
- во-первых, неизвестно где лежит проверяемый файл
- во-вторых, в реестре то они не ищут

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

Вот функция:

; ============================================================================================================
FileExistInShell( Target_File ) ; функция проверки возможности запуска файла
{
    ; В качестве параметра функция принимает имя файла (с расширением!)
    ; Если файл найден, то функция возвращает путь к нему.
    ; Обычно имеет смысл проверять исполнимые файлы, но это не обязательно :)

    ; Сначала ищем в App Paths
    Loop, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths, 2, 1 ; ищем путь в реестре
    {
        If ( A_LoopRegName != Target_File ) ; если текущий раздел не тот, что ищем
            Continue

        RegRead, Target_Path, %A_LoopRegKey%, %A_LoopRegSubKey%\%A_LoopRegName% ; читаем парам. "по умолчанию"
        IfExist, %Target_Path% ; если файл, указанный в реестре существует
            Return, %Target_Path% ; возвращаем путь к файлу
        Else ; если вдруг файл по указанному пути не существует
            Break ; больше в реестре не ищем
    }

    ; В App Paths не нашли, ищем в системных папках
    IfExist, %A_WinDir%\System32\%Target_File% ; если нашли файл
        Return, A_WinDir "\System32\" Target_File ; возвращаем путь к файлу

    IfExist, %A_WinDir%\%Target_File% ; если нашли файл
        Return, A_WinDir "\" Target_File ; возвращаем путь к файлу

    ; Напоследок ищем в переменной окружения Path
    EnvGet, EnvPath, Path ; получаем переменную окружения Path
    StringSplit, EnvPathArray, EnvPath, `; ; разбиваем на части в массив
    Loop, %EnvPathArray0% ; проверяем полученные пути
    {
        Target_Path := % EnvPathArray%A_Index% "\" Target_File ; составляем путь
        IfExist, %Target_Path% ; если нашли файл
            Return, %Target_Path% ; возвращаем путь к файлу
    }
    Return ; выходим из функции (не нашли файл)
}
; ============================================================================================================

Несколько примеров применения:

Показываем путь, по которому будет запущен WinRAR.exe

MsgBox, % FileExistInShell( "WinRAR.exe" )

Показываем путь, по которому будет запущен win.ini

MsgBox, % FileExistInShell( "win.ini" )

Показываем путь, по которому будет запущен notepad.exe

fTest = notepad.exe
If FileExistInShell( fTest )
    MsgBox, % "Файл будет запущен из:`n"  FileExistInShell( fTest )
Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).