Changes from v1.1 to v2.0


Legacy Syntax Removed

Removed literal assignments: var = value

Removed all legacy If statements, leaving only if expression, which never requires parentheses (but allows them, as in any expression).


Variable names cannot start with a digit and cannot contain the following characters which were previously allowed: @ # $. Only letters, numbers, underscore and non-ASCII characters are allowed.

Names of control flow statements cannot be used as variable, function, class or window group names. This includes If, Else, Loop, LoopFiles, LoopRead, LoopReg, LoopParse, For, While, Until, Break, Continue, Goto, Gosub, Return, Try, Catch, Finally and Throw. The purpose of this is primarily to detect errors such as if (ex) break.

Derefs require an ending percent sign as in v1 (but not early v2 alphas).

Text and output var args and quoted strings can contain not only %variables%, but arbitrary expressions between percent signs. Strings and expressions can be nested to any depth. %(%varname%)% is valid, and would perform a double-deref within a string, or triple-deref in an expression.

Double-derefs are now consistent with variables resolved at load-time. That is, a dynamic variable reference will resolve to a (non-super) global only if the function is assume-global or the variable is declared as global in the function. (Important: this also applies to the vVariable option of Gui.)

Double-derefs which fail because they are empty or too long display an error message and cause the thread to abort, instead of silently producing an empty string. This makes them more consistent with input/output vars. Using characters which are illegal in var names already caused an error message, and still does.

Declaring a local variable in a function does not make the function assume-global.

#MustDeclare [On|Off] enables or disables must-declare mode. If used in a func lib file, it affects only that file and any files it explicitly #includes. However, it inherits its default setting from the main script. It is positional, but cannot be used inside a function (due to the following point). Functions default to either assume-local or must-declare depending on the setting at the point the function is defined, but this can be overridden by using Global, Local or Static without a variable name.

Var varname declares a global variable without making it super-global (visible in all functions by default). These declarations are only necessary when #MustDeclare is enabled.


Quoted literal strings can be written with "double" or 'single' quote marks, but must begin and end with the same mark. Both allow nested expressions such as "Next year is %A_Year+1%". Literal quote marks are written by preceding the mark with an escape character - `" or `' - or by using the opposite type of quote mark: '"42" is the answer'. Doubling the quote marks has no special meaning, and causes an error since auto-concat requires a space.

The operators &&, ||, and and or yield whichever value determined the result, similar to JavaScript and Lua. For example, "" or "default" yields "default" instead of 1. Scripts which require a pure boolean value (0 or 1) can use something like !!(x or y) or (x or y) ? 1 : 0.

Auto-concat now requires at least one space or tab in all cases (the v1 documentation says there "should be" a space).

The result of a multi-statement expression such as x(), y() is the last (right-most) sub-expression instead of the first (left-most) sub-expression. In both v1 and v2, the sub-expressions are evaluated in left to right order.

Equals after a comma is no longer assignment: y=z in x:=y, y=z is an ineffectual comparison instead of an assignment.

:= += -= *= /= ++ -- have consistent behaviour regardless of whether they are used on their own or combined with other operators, such as with x := y, y += 2. For instance, x := %y%() no longer assigns an empty string to x if the dynamic call fails.

Scientific notation can be used without a decimal point (but produces a floating-point number anyway).

The expressions funcName[""]() and funcName.() no longer call a function by name. Omitting the method name as in .() now causes a load-time error message. %funcName%() should be used instead.

var := with no r-value is treated as an error at load-time. In v1 it was equivalent to var := "", but silently failed if combined with another expression - for example: x :=, y :=.

Where a literal string is followed by an ambiguous unary/binary operator, an error is reported at load-time. For instance, "x" &y is probably supposed to auto-concatenate "x" with the address of y, but technically it is an invalid bitwise-and operation.

In an expression, the word new is no longer treated as a variable under any circumstances. More complex expressions are supported; e.g. new (getClass())(params) or new new {...}(inner_prms)(outer_prms).

Class var initializers - which take the form of assignments in the class body but outside of any method body - no longer trigger the __Set meta-function.

word ? x : y, word ++ and word -- are no longer expressions, since word can be a user-defined command. To write a standalone ternary, post-increment or post-decrement expression, either omit the space between the variable and the operator, or wrap the variable or expression in parentheses.

New operator is replaces if var is type. Since type is a string, it must be enclosed in quote marks (or a variable). The same type names are supported as before, plus object and byref. Additionally x is y, where y is an object, checks if x derives from y, directly or indirectly.

Keywords contains and in are reserved for future use.

Address-of: Since pure numbers and strings are now distinct types, &(var := 123) now returns the address of an int64 instead of the address of a string. For consistency, VarSetCapacity returns 6 (the size of int64 minus a null terminator).

String length is now cached during expression evaluation. This improves performance and allows strings to contain binary zero. In particular:

Many commands and functions still expect null-terminated strings, so will only "see" up to the first binary zero.


Keys are stored differently than on v1 in a few cases:

When calling a function via an object, the object itself is passed as the first parameter regardless of where the function was stored (in the object or one of its bases). In v1 this was not done if the function was stored by name directly in the object.

Where Fn is a function or object, %Fn%() calls Fn.Call() instead of Fn[""](). Functions no longer support the nameless method.

Where Fn = this.Method, this.Method() calls Fn.Call(this) instead of Fn[this](). Function objects should implement a Call method instead of the __Call meta-function.

Where x is an Object, defining x.base[key] prevents x.base.base.__Set() from being called when an assignment like x[key] := val is performed. This is for consistency with __Get and __Call.

new Obj() now fails to create the object if the __New meta-method is defined and it could not be called (e.g. because it requires parameters and none were supplied).

All built-in methods of Objects and SafeArrays no longer support the _ (underscore) prefix, except _NewEnum, which now requires it (for compatibility with COM objects). ObjNewEnum(x) is still equivalent to x._NewEnum() for Objects.

The methods Insert and Remove have been removed. They were superseded by InsertAt, RemoveAt, Push, Pop, Delete and ObjRawSet in v1.1.21.

Objects created within an expression or returned from a function are now held until expression evaluation is complete, and then released. This improves performance slightly and allows temporary objects to be used for memory management within an expression, without fear of the objects being freed prematurely.

Objects can contain string values (but not keys) which contain binary zero. Cloning an object preserves binary data in strings, but no longer preserves data beyond the data length (FIXME: provide a way to set the length).


:= must be used in place of = when initializing a declared variable or optional parameter.

If Expression, allows a same-line action (command or expression) following the comma.

Return %var% now does a double-deref; previously it was equivalent to Return var.

Command args are never expressions by default, except with control flow statements such as Return. The % (percent followed by space) prefix can still be used to force an expression.

#Include is relative to the directory containing the current file by default.

Labels defined in a function have local scope; they are visible only inside that function and do not conflict with labels defined elsewhere. Timers and Gui subroutines can be local, but must be set from within the function.

For k, v in obj now "localizes" k and v. When the loop breaks or completes, k and v are restored to the values they had before the loop started.

Escaping a comma in an expression causes it to be interpreted as the multi-statement operator rather than a delimiter. This was already the case for commands, but not variable declarations. For instance, local x:=1, y:=2 declares two variables, but escaping the comma would cause it to be interpreted as local x:=(1, y:=2). Note that continuation sections without the , option automatically escape commas.

Continuation Sections

Smart LTrim: The default behaviour is to count the number of leading spaces or tabs on the first line below the continuation section options, and remove that many spaces or tabs from each line thereafter. If the first line mixes spaces and tabs, only the first type of character is treated as indentation. If any line is indented less than the first line or with the wrong characters, all leading whitespace on that line is left as is.

Quote marks (single and double) are literal by default; this can be disabled with the Q option.

Newline characters (`n) in expressions are treated as spaces. This allows multi-line expressions to be written using a continuation section with default options (i.e. omitting Join).


All commands can now be called as functions, except for control flow statements such as Return. The translation rules are:

Incomplete: As commands and functions are still internally distinct, defining a function with the same name as a command currently overrides the command only for the function() style of calling; i.e. the command style still calls the original command.

Experimental: Return, Until, Loop, LoopFiles, LoopReg, LoopRead and LoopParse also support an expression-like syntax. That is, the entire parameter list can be enclosed in a single set of parentheses to force expressions for every parameter. However, these commands are not functions; for instance, x := return(y) is not valid.

Function, Args

All functions can now be called as commands, including library functions which haven't been manually #included. The translation rules are currently:

Methods can also be called, with the following usage:


In general, v2 produces more consistent results with any code that depends on the type of a value.

In v1, a variable can contain both a string and a cached binary number, which is updated whenever the variable is used as a number. Since this cached binary number is the only means of detecting the type of value, caching performed internally by expressions like var+1 or abs(var) effectively changes the "type" of var as a side-effect. v2 disables this caching, so that str := "123" is always a string and int := 123 is always an integer. Consequently, str needs to be converted every time it is used as a number (instead of just the first time).

The built-in "variables" true, false, A_PtrSize, A_IsUnicode, A_Index and A_EventInfo always return pure integers, not strings. They sometimes return strings in v1 due to certain optimizations which have been superseded in v2.

All literal numbers are converted to pure binary numbers at load time and their string representation is discarded. For example, MsgBox % 0x1 is equivalent to MsgBox 1, while MsgBox % 1.0000 is equivalent to MsgBox 1.0 (because the default float formatting has changed). Storing a number in a variable or returning it from a UDF retains its pure numeric status.

Quoted literal strings and strings produced by concatenating with quoted literal strings are no longer unconditionally considered non-numeric. Instead, they are treated the same as strings stored in variables or returned from functions. This has the following implications:

The way that objects interpret different types of keys has changed. See the Objects section of this document for details.

Relational operators such as =, < and >= work a little differently. If both operands are numeric and at least one operand is pure, they are compared numerically. Otherwise, they are compared alphabetically. So for example, 54 and "530" are compared numerically, while "54" and "530" are compared alphabetically. Additionally, strings stored in variables are treated no differently from literal strings.

Type(Value) returns one of the following strings: String, Integer, Float, Object or the specific class of a built-in object type, such as FileObject or ComObject.


Removed commands:

Renamed commands:

Removed undocumented AutoIt2 commands (also removed in v1.1.09):

Modified Commands/Functions

Chr(0) returns a string of length 1, containing a binary zero. This is a result of improved support for binary zero in strings.

ComObject(pdsp), ComObject(9, pdsp) and ComObject(13, pdsp) no longer call AddRef by default; they default to "taking ownership" of the pointer. Most v1 scripts which use ComObjEnwrap(pdsp) (within the first few pages of Google results) used it incorrectly; i.e. they did not release their own copy of the pointer. For v2, the script must call ObjAddRef(pdsp) before ComObject(pdsp) if it does not "own" the reference (i.e. because the pointer will be released automatically, either when the wrapper object is released or immediately as a side-effect of querying for IDispatch within ComObject()). The flags parameter now only affects SafeArrays.

ControlMove, ControlGetPos and ControlClick now use client coordinates (like GuiControl) instead of window coordinates. Client coordinates are relative to the top-left of the client area, which excludes the window's title bar and borders. (Controls are rendered only inside the client area.)

DllCall: AStr is now input-only. Since the buffer is only ever as large as the input string, it was never useful for output parameters. This applies to WStr instead of AStr if compiled for ANSI (but v2 is currently Unicode-only).

DriveGet had an undocumented sub-command for setting the drive label. This has been removed. Use Drive, Label instead.

FileOpen enables end-of-line translations by default; use the * option character to disable this.

FileSetAttrib and FileSetTime: the OperateOnFolders? and Recurse? parameters have been replaced with a single Mode parameter identical to that of the Loop File command. For example, FileSetAttrib, +a, *.zip, RF (Recursively operate on Files only).

File.ReadLine() no longer includes the line ending in the return value.

Func(Fn) returns an empty string (instead of 0) if the function does not exist, and returns Fn itself if it is a function reference, instead of 0.

GetKeyState is now only a function, not a command. The function always returns 0 or 1, even when called using command syntax.

GroupAdd: Removed the Label parameter and related functionality. This was an unintuitive way to detect when GroupActivate fails to find any matching windows; ErrorLevel should be used instead.

Hotkey no longer defaults to the script's bottommost #If/#IfWin. Hotkey/hotstring threads default to the same criterion as the hotkey, so Hotkey, %A_ThisHotkey%, Off turns off the current hotkey even if it is context-sensitive. All other threads default to the last setting used by the auto-execute section, which itself defaults to no criterion (global hotkeys).

Hotkey now always gives special treatment to the following values and never treats them as label/function names: On, Off, Toggle, AltTab, ShiftAltTab, AltTabAndMenu, AltTabMenuDismiss. The old behaviour was to use the label/function if it existed, but only if the Label parameter did not contain a variable reference or expression.

Hotkey, If now recognizes expressions which use the and or or operators. This did not work in v1 as these operators were replaced with && or || at load time.

Hotkey, If.. never sets ErrorLevel. An exception is thrown on failure.

IniRead defaults to an empty string when the Default parameter is omitted, instead of the word ERROR. Additionally, ErrorLevel is set if an error occurs.

Input now always returns end keys a-z in lower-case, consistent with other characters (on non-US keyboard layouts).

InputBox has been given a syntax overhaul to make it easier to use (with fewer parameters). See InputBox for usage.

InStr's CaseSensitive parameter is now evaluated according to the usual boolean rules. In v1, non-numeric values were converted to 0, which is false. In v2, non-numeric values other than "" are considered true.

MsgBox has had its syntax changed to prioritise its most commonly used parameters and improve ease of use. "Smart" comma handling has been removed; that is, it now handles commas the same as other functions. See MsgBox for usage.

NumPut/NumGet: If a variable containing a pure number is passed to the VarOrAddress parameter, the variable's value is used rather than the address of the variable itself.

Object() and {} no longer cause __Set to be invoked for each key-value pair. Object(obj) no longer calls AddRef and returns the object's address. If required, use ObjAddRef(addr := &obj) instead.

OnExit (the command) has been removed; use the OnExit() function which was added in v1.1.20 instead. A_ExitReason has also been removed; its value is available as a parameter of the OnExit callback function.

OnClipboardChange: (the label) is no longer called automatically if it exists. Use the OnClipboardChange() function which was added in v1.1.20 instead.

OnMessage has been changed to treat function names the same way that function references are treated in v1.1.20. That is, OnMessage(x, "MyFunc") registers MyFunc for message x while still allowing other functions to monitor message x. The function must be unregistered using OnMessage(x, "MyFunc", 0), not OnMessage(x, ""), which is now an error. OnMessage(x) is also now an error. On failure, OnMessage throws an exception.

PixelSearch and PixelGetColor use RGB values instead of BGR, for consistency with other commands.

RegExMatch options O and P were removed; O (object) mode is now mandatory.

Registry commands (RegRead, RegWrite, RegDelete): the new syntax added in v1.1.21+ is now the only syntax. Root key and subkey are combined. Instead of RootKey, Key, write RootKey\Key. To connect to a remote registry, use \\ComputerName\RootKey\Key instead of \\ComputerName:RootKey, Key.

RegDelete with a blank or omitted ValueName now deletes the key's default value, for consistency with RegWrite, RegRead and A_LoopRegName. Keys can be deleted with the new RegDeleteKey command.

RegRead had an undocumented 5-parameter mode, where the value type was specified after the output variable. This has been removed.

SendMessage sets ErrorLevel to ERROR instead of FAIL when it fails, for consistency with Run.

Sort: the VarName parameter has been split into separate input/output parameters, for flexibility. Usage is now Sort, OutputVar, InputText [, Options] or Output := Sort(Input [, Options]).

StrGet: If Length is negative, its absolute value indicates the exact number of characters to convert, including any binary zeros that the string might contain -- in other words, the result is always a string of exactly that length. If Length is positive, the converted string ends at the first binary zero as in v1.

SysGet now only has numeric sub-commands; its other sub-commands have been split into functions. See Sub-Commands for details.

TrayTip's usage has changed to TrayTip [, Text, Title, Options]. Options is a string of zero or more case-insensitive options delimited by a space or tab. The options are Iconx, Icon!, Iconi, Mute and/or any numeric value as before. TrayTip now shows even if Text is omitted (which is now harder to do by accident than in v1). The Seconds parameter no long exists (it had no effect on Windows Vista or later).

WinMove no longer has special handling for the literal word DEFAULT. Omit the parameter or specify an empty string instead (this works in both v1 and v2).


A negative StartingPos for InStr, SubStr, RegExMatch and RegExReplace is interpreted as a position from the end, starting at 1. Position -1 is the right-most character (in v1 this was position 0), and position 0 is invalid.

Loop Parse, SplitPath, StrUpper, StrLower and StrReplace now accept normal text as input instead of a variable name, so to pass a variable, use %InputVar% or % InputVar. The "input variable" concept is no longer used by any command.

Commands which accept On/Off/Toggle now also accept 1/0/-1 (which is more convenient in an expresssion).

The following commands/functions return a HWND as a pure integer instead of as a hexadecimal string:

A_ScriptHwnd returns a string due to a technical limitation, but in decimal to be more consistent.

Loop Sub-commands


Loop, FilePattern [, IncludeFolders?, Recurse?]
Loop, RootKey [, Key, IncludeSubkeys?, Recurse?]

Use the following (added in v1.1.21) instead:

Loop, Files, FilePattern [, Mode]
Loop, Reg, RootKey\Key [, Mode]

Additionally, Loop Parse now accepts a normal string instead of an InputVar.

Loop, Parse, InputString [, Delimiters, OmitChars]

Currently a compact form (LoopFiles, LoopReg, LoopRead and LoopParse) is also supported, but this may be removed. (If you value it, speak up.)

Expression syntax can be used by writing the Loop command like a function call: LoopRead(inputFile, outputFile). OTB is supported with this syntax. Currently there must not be a space between "Loop" and the sub-command name due to ambiguity.


InputBox, OutputVar [, Text, Title, Options, Default]

The Options parameter accepts a string of zero or more case-insensitive options delimited by a space or tab, similar to Gui control options. For example, this includes all supported options: x0 y0 w100 h100 T10.0 Password*. T is timeout and Password has the same usage as the equivalent Edit control option.


MsgBox [, Text, Title, Options, OutputVar]
Result := MsgBox([Text, Title, Options, OutputVar])

The Options parameter accepts a string of zero or more case-insensitive options delimited by a space or tab, similar to Gui control options.

The return value, which is also stored in OutputVar, is the name of the button, without spaces. These are the same strings that were used with IfMsgBox in v1.

OutputVar is provided to allow the use of command syntax (with buttons other than OK), while still allowing the most often used parameter (Text) to be the first parameter. Note that OutputVar is entirely useless when Options is omitted.


Sub-commands of WinGet, WinSet and Process have been replaced with individual functions, and the main commands have been removed. Excluding the obsolete Cmd/Attribute parameter, usage is the same as before except for the following sub-commands:

When called using function syntax, all WinSet' functions return 1 on success, 0 on failure, while ErrorLevel is set to 0 on success, 1 on failure.

The following functions were formerly sub-commands of SysGet:

Exists  := MonitorGet(N, Left, Top, Right, Bottom)
Exists  := MonitorGetWorkArea(N, Left, Top, Right, Bottom)
Count   := MonitorGetCount()
Primary := MonitorGetPrimary()
Name    := MonitorGetName(N)

New Commands/Functions

DirExist(Path), with usage similar to FileExist.

RegDeleteKey, RootKey\SubKey deletes a registry key. (RegDelete now only deletes values, except when omitting all parameters in a registry loop.)

Built-in Variables

A_OSVersion always returns a string in the format, such as 6.1.7601 for Windows 7 SP1. A_OSType has been removed as only NT-based systems are supported.




The following built-in variables can be assigned values:

Error Handling


An exception is thrown when any of the following failures occur (instead of ignoring the failure):

Some of the conditions above are detected in v1, but not mid-expression; for instance, A_AhkPath := x is detected in v1 but y := x, A_AhkPath := x is only detected in v2.

The operators +=, -=, -- and ++ treat an empty variable as 0 (for += and -=, this only applies to the left-hand side). In v1, this was true only for standalone use of the operator, not when used mid-expression or with multi-statement comma.


Commands generally use ErrorLevel to report failure, and sometimes throw an exception (e.g. for invalid parameters). This is similar to v1, though behaviour has changed in some specific cases. However, wrapping commands in a TRY block no longer affects this behaviour, so behaviour is more predictable.

Commands and built-in functions throw an exception when invalid parameters are detected (but not all invalid parameters are detected), or when a memory allocation fails.

DllCall throws exceptions instead of setting ErrorLevel.

RegExMatch and RegExReplace never set ErrorLevel, instead throwing an exception if there is an error. This includes syntax errors in the regex pattern and PCRE execution errors.

TV/LV/SB functions throw an exception if the default GUI doesn't exist or it has no TreeView/ListView/StatusBar control.

Math functions return the empty string "" if any of their inputs are non-numeric, or if the operation is invalid (such as division by zero).


Command-line args are stored in an array and assigned to the super-global var A_Args instead of a pseudo-array of numbered global vars. If no args were passed to the script, A_Args contains an empty array. The expression A_Args.Length() returns the number of args and A_Args[1] returns the first arg.

Mouse wheel hotkeys set A_EventInfo to the wheel delta as reported by the mouse driver instead of dividing by 120. Generally it is a multiple of 120, but some mouse hardware/drivers may report wheel movement at a higher resolution.

ErrorLevel can safely be assigned an object without risk of it being lost when the thread is interrupted. In v1, the thread-specific nature of ErrorLevel was implemented by saving the previous thread's ErrorLevel as a string when a new thread starts and restoring it when the new thread terminates.

ErrorLevel is reset to 0 for each new thread. In v1, it retained the value from the previous thread, which is also the same value it is reset to when the new thread finishes.

FileSelectFile had two multi-select modes, accessible via options 4 and M. Option 4 and the corresponding mode have been removed; they had been undocumented for some time.

RegEx newline matching defaults to (*ANYCRLF) and (*BSR_ANYCRLF); `r and `n are recognized in addition to `r`n. The `a option implicitly enables (*BSR_UNICODE).


Progress Gui controls no longer have the PBS_SMOOTH style by default, so they are now styled according to the system visual style (e.g. Luna in XP or Aero in Vista/7).

GuiSize and GuiDropFiles labels no longer set ErrorLevel. Use A_EventInfo instead.


Scripts are "persistent" while at least one of the following conditions is satisfied:

If the last script thread finishes or a Gui is closed or destroyed and none of the above conditions are satisfied, the script terminates.

By contrast, v1 scripts are "persistent" when at least one of the following is true:

Default Settings