# Run / RunWait

运行外部程序. 与 Run 不同, RunWait 将等到程序完成后才继续.

    Run Target , WorkingDir, Options, &OutputVarPID
    ExitCode := RunWait(Target , WorkingDir, Options, &OutputVarPID)

## 参数

Target

  类型: 字符串

  要运行的文档, URL, 可执行文件(.exe, .com, .bat, 等等), 快捷方式(.lnk) 或系统动词(请参阅备注). 如果 Target 是一个本地文件, 并且没有指定路径, 则首先搜索 A_WorkingDir. 在没有找到匹配的文件时, 如果该文件被集成在系统中("已知的"), 例如包含在 PATH 文件夹中的一个文件夹中, 系统将搜索并启动该文件.

  要传递参数, 请让它们紧跟在程序或文档名的后面, 如下所示:

      Run 'MyProgram.exe Param1 Param2'

  如果程序/文档名称或参数包含空格, 则把它们括在双引号中是最可靠的(虽然它在某些情况下没有括在双引号中也能工作), 如下所示:

      Run '"My Program.exe" "param with spaces"'

WorkingDir

  类型: 字符串

  如果未设置或省略, 则使用脚本自己的工作目录(A_WorkingDir). 否则, 请指定 Target 的工作目录.

Options

  类型: 字符串

  如果未设置或省略, 将正常启动 Target. 否则, 请指定以下一个或多个选项:

  Max: 最大化运行

  Min: 最小化运行

  Hide: 隐藏运行(不能和上面任意一个选项组合使用)

  注意: 一些程序(例如 Calc.exe) 不会遵循请求的启动状态, 因此对于它们 Max/Min/Hide 没有效果.

&OutputVarPID

  类型: VarRef

  如果未设置或省略, 不使用输出变量. 否则, 请指定用于存储新启动的程序的唯一进程 ID(PID) 的变量引用. 如果无法确定 PID, 该变量将被置为空, 这经常发生在运行系统动词, 文档或快捷方式而不是直接的可执行文件时. RunWait 也支持此参数, 不过它的 OutputVarPID 必须在另一个线程中检查(否则, PID 将是无效的, 因为在 RunWait 后面的线程执行时, 进程已经终止了).

  在 Run 函数检索到 PID 之后, 由进程创建的窗口可能还不存在. 要等待至少一个窗口被创建, 请使用 `WinWait "ahk_pid " OutputVarPID`.

## 返回值

类型: 整数

与 Run 不同, RunWait 会等待 Target 关闭或退出, 此时返回值被设置为程序的退出代码(32 位的有符号整数). 一些程序即使仍在运行, 但会出现立即返回; 这是因为这些程序启动了另一个进程.

## 错误处理

如果无法启动 Target, 则会抛出异常(即, 显示一个错误窗口) 并退出当前线程, 除非错误被 Try/Catch 语句捕获. 例如:

    try
        Run "NonExistingFile"
    catch
        MsgBox "File does not exist."

内置变量 A_LastError 被设置为操作系统的 GetLastError() 函数的结果.

## 备注

当通过 ComSpec(cmd.exe) 运行程序时 -- 也许是因为你需要重定向程序的输入或输出 -- 如果可执行文件的路径或名称中包含空格, 那么整个字符串应该用一对外置引号来括起. 在下面的例子中, 外部的引号用黄色高亮显示:

    Run A_ComSpec ' /c ""C:\My Utility.exe" "param 1" "second param" >"C:\My File.txt""'

如果 Target 为准确路径时可轻微提升性能, 例如使用 `Run 'C:\Windows\Notepad.exe "C:\My Documents\Test.txt"'` 代替 `Run "C:\My Documents\Test.txt"`.

Run 还可以打开指定的 CLSID 文件夹. 例如:

    Run "::{20d04fe0-3aea-1069-a2d8-08002b30309d}"  ; 打开 "我的电脑" 文件夹.
    Run "::{645ff040-5081-101b-9f08-00aa002f954e}"  ; 打开回收站.

系统动词对应于资源管理器中文件的右键菜单中可用的操作. 如果启动文件时没有指定动词, 则使用该文件类型的默认动词(通常为 "打开"). 如果指定了, 则动词后应该跟着目标文件的名称. 目前支持下列动词:

Verb(动词) | 描述
--- | ---
*verb | 任何系统定义或自定义的动词. 例如: `Run "*Compile " A_ScriptFullPath`. *RunAs 动词将代替 以管理员身份运行 右键菜单.
properties | 显示指定文件的资源管理器属性窗口. 例如: `Run 'properties "C:\My File.txt"'` 注意: 当脚本终止时属性窗口会自动关闭. 要避免这种情况, 请使用 WinWait 等待窗口出现, 然后使用 WinWaitClose 等待用户关闭它.
find | 在指定的文件夹中打开资源管理器的搜索组件或查找文件窗口的实例. 例如: `Run "find D:\"`
explore | 打开指定的文件夹的资源管理器实例. 例如: `Run "explore " A_ProgramFiles`.
edit | 打开指定的文件进行编辑. 如果指定的文件类型没有关联 "edit" 动作时, 它不起作用. 例如: `Run 'edit "C:\My File.txt"'`
open | 打开指定的文件(一般不需要, 因为这对于大多数文件类型这是默认的动作). 例如: `Run 'open "My File.txt"'`.
print | 使用关联的应用程序打印指定的文件, 如果有. 例如: `Run 'print "My File.txt"'`

当 RunWait 处于等待状态时, 可以通过热键, 自定义菜单项或计时器启动新的线程.

## 以管理员身份运行

对于可执行文件, *RunAs 动词相当于文件右键菜单的 以管理员身份运行. 例如, 以下代码尝试以管理员权限重启当前脚本:

    full_command_line := DllCall("GetCommandLine", "str")

    if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
    {
        try
        {
            if A_IsCompiled
                Run '*RunAs "' A_ScriptFullPath '" /restart'
            else
                Run '*RunAs "' A_AhkPath '" /restart "' A_ScriptFullPath '"'
        }
        ExitApp
    }

    MsgBox "A_IsAdmin: " A_IsAdmin "`nCommand line: " full_command_line

如果用户取消了 User Account Control (UAC) 对话框, 或者由于其他原因启动失败, 脚本就会直接退出.

如果在调用 ExitApp 之前启动新的脚本实例, 使用 /restart 可以确保不显示单一实例的提示弹窗.

如果禁用了 UAC, *RunAs 将不会提权启动进程. 在命令行中检查 `/restart` 可以确保在这种情况下脚本不会进入失控循环. 注意 `/restart` 是一个内置的开关, 所以其不包括在命令行参数数组中.

可以根据脚本的需要修改上面的例子:

- 如果脚本的确需要管理员权限, 在 *RunAs 未能提升脚本权限的情况下(例如, 因为 UAC 被禁用), 第二次检查 A_IsAdmin.
- 在用户取消了 UAC 提示的情况下, 为了保持脚本的运行, 可以将 ExitApp 移到 try 块中.
- 在重新启动失败的情况下, 为了保持脚本的运行(比如脚本文件已被重命名或删除), 移除 ExitApp 并且使用 RunWait 代替 Run. 若是成功, `/restart` 使新实例终止旧实例. 若是失败, 新实例的退出及 RunWait 返回.

AutoHotkey 的安装程序为 .ahk 文件注册 RunAs 动词, 这允许 `Run "*RunAs script.ahk"` 以管理员权限启动脚本.

## 相关

RunAs, 进程函数, Exit, CLSID 列表, DllCall

## 示例

Run 能够从任何目录下启动 Windows 系统程序. 注意, 可执行文件的扩展名如 .exe 可以省略.

    Run "notepad"

Run 能够启动 URLs:

以下是在用户的默认网页浏览器中打开一个互联网地址.

    Run "https://www.google.com"

以下打开默认的电子邮件应用程序并填写收件人.

    Run "mailto:someone@somedomain.com"

下面的操作和上面一样, 加上主题和正文.

    Run "mailto:someone@somedomain.com?subject=This is the subject line&body=This is the message body's text."

在最大化应用程序中打开文档, 并在失败时显示一个自定义的错误信息.

    try Run("ReadMe.doc", , "Max")
    if A_LastError
        MsgBox "The document could not be launched."

在最小化状态下运行 dir 命令, 并将输出结果存储在文本文件中. 之后, 文本文件及其属性对话框将被打开.

    RunWait A_ComSpec " /c dir C:\ >>C:\DirTest.txt", , "Min"
    Run "C:\DirTest.txt"
    Run "properties C:\DirTest.txt"
    Persistent ; 保持脚本不退出, 否则属性对话框将关闭.

Run 能够启动 CLSID:

下面打开回收站.

    Run "::{645ff040-5081-101b-9f08-00aa002f954e}"

下面打开 "我的电脑" 目录.

    Run "::{20d04fe0-3aea-1069-a2d8-08002b30309d}"

要连续运行多个命令, 请每个命令之间使用 "&&".

    Run A_ComSpec "/c dir /b > C:\list.txt && type C:\list.txt && pause"

以下自定义函数可以用来运行一个命令并检索其输出, 或者一次性运行多个命令并检索其输出. 有关 WshShell 对象, 请参阅 Microsoft Docs.

    MsgBox RunWaitOne("dir " A_ScriptDir)

    MsgBox RunWaitMany("
    (
    echo Put your commands here,
    echo each one will be run,
    echo and you'll get the output.
    )")

    RunWaitOne(command) {
        shell := ComObject("WScript.Shell")
        ; 通过 cmd.exe 执行单条命令
        exec := shell.Exec(A_ComSpec " /C " command)
        ; 读取并返回命令的输出
        return exec.StdOut.ReadAll()
    }

    RunWaitMany(commands) {
        shell := ComObject("WScript.Shell")
        ; 打开 cmd.exe 禁用命令回显
        exec := shell.Exec(A_ComSpec " /Q /K echo off")
        ; 发送并执行命令, 使用新行分隔
        exec.StdIn.WriteLine(commands "`nexit")  ; 总是在最后退出!
        ; 读取并返回所有命令的输出
        return exec.StdOut.ReadAll()
    }

在新的 AutoHotkey 进程中执行给定的代码.

    ExecScript(Script, Wait:=true)
    {
        shell := ComObject("WScript.Shell")
        exec := shell.Exec("AutoHotkey.exe /ErrorStdOut *")
        exec.StdIn.Write(script)
        exec.StdIn.Close()
        if Wait
            return exec.StdOut.ReadAll()
    }

    ; 示例:
    ib := InputBox("Enter an expression to evaluate as a new script.",,, 'Ord("*")')
    if ib.result = "Cancel"
        return
    result := ExecScript('FileAppend ' ib.value ', "*"')
    MsgBox "Result: " result

dftg