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).

Removed "command syntax". That is, a "command" is now just a function or method call without parentheses (at the start of a line). That means:

The translation from v1-command to function is generally as follows (but some functions have been changed, as documented further below):

Loop also accepts expressions. The initial Files, Parse, Read or Reg keyword should not be quoted. Currently the keyword can be followed by a comma, but it is not required as this is not a parameter. OTB is supported.

All control flow statements which take parameters (currently excluding the two-word Loop statements) support parentheses around their parameter list, without any space between the name and parenthesis. For example, return(var). However, these are not functions; for instance, x := return(y) is not valid. If and While already supported this.

Goto, gosub, break and continue require an unquoted label name, similar to v1 (goto label jumps to label:). To jump to or call a label dynamically, use parentheses immediately after the name: goto(expression) or gosub(expression). However, these are not functions and cannot be used mid-expression. Parentheses can be used with break or continue, but in that case the parameter must be a single literal number or quoted string.


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.

Reserved words: Declaration keywords and names of control flow statements cannot be used as variable, function or class names. This includes Local, Global, Static, If, Else, Loop, 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.

Reserved words: Names of expression operator keywords cannot be used as variable, function or class names. This includes Or, And, Not, New, Is, In and Contains. This is primarily for consistency: in v1, and := 1 was allowed on its own line but (and := 1) would not work.

The words listed above are permitted as method or window group names. Method names in calls are always preceded by ., which prevents the word from being interpreted as an operator. By contrast, keywords are never interpreted as variable or function names within an expression. For example, new(x) is equivalent to (new x).

The names Object, Array and Map are used by predefined classes, but can be shadowed within a function by explicitly declaring a local variable or parameter, or using force-local mode.

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.

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.


Quoted literal strings can be written with "double" or 'single' quote marks, but must begin and end with the same mark. 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. (This example might not be applicable anymore since dynamic calls now throw exceptions on failure.)

!= is now always case-insensitive, like =, and !== has been added as the counterpart of ==.

<> has been removed.

Scientific notation can be used without a decimal point (but produces a floating-point number anyway). Scientific notation is also supported when numeric strings are converted to integers (for example, "1e3" is interpreted as 1000 instead of 1).

Double-derefs and dynamic function calls now permit almost any expression (not just variables) as the source of the variable/function name. For example, Fn%n+1%() and %(%triple%)% are valid.

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 ++ and word -- are no longer expressions, since word can be a user-defined function (and ++/- may be followed by an expression which produces a variable reference). To write a standalone post-increment or post-decrement expression, either omit the space between the variable and the operator, or wrap the variable or expression in parentheses.

word ? x : y is still a ternary expression, but more complex cases starting with a word, such as word1 word2 ? x : y, are always interpreted as function calls to word1 (even if no such function exists). To write a standalone ternary expression with a complex condition, enclose the condition 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.

&int (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).

&"str" (address-of): It is now possible to take the address of a literal string or temporary string, such as the result of concatenation or a string returned by a function or built-in variable. See the documentation for caveats.

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.

The *deref operator has been removed. Use NumGet instead.

The ~ (bitwise-NOT) operator now always treats its input as a 64-bit signed integer; it no longer treats values between 0 and 4294967295 as unsigned 32-bit.

Added "fat arrow" functions. The expression Fn(Parameters) => Expression defines a function named Fn (which can be blank) and returns a Func or Closure object. When called, the function evaluates Expression and returns the result. When used inside another function, Expression can refer to the outer function's variables (this can also be done with a normal function definition).

The fat arrow syntax can also be used to define methods and property getters/setters (in which case the method/property definition itself isn't an expression, but its body just returns an expression).

Literal numbers are now fully supported on the left-hand side of member access (dot). For example, 0.1 is a number but 0.min and 0.1.min access the min property which can be handled by the default meta-functions. 1..2 or 1.0.2 is the number 1.0 followed by the property 2. Example use might be to implement units of measurement, literal version numbers or ranges.

Objects (Misc)

See also: Objects

There is now a distinction between properties accessed with . and data (items, array or map elements) accessed with []. For example, dictionary["Count"] can return the definition of "Count" while dictionary.Count returns the number of words contained within. User-defined objects can utilize this by defining an __Item[] indexed property.

When the name of a property or method is not known in advance, it can (and must) be accessed by using percent signs. For example, obj.%varname%() is the v2 equivalent of obj[varname](). The use of [] is reserved for data (such as array elements).

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

this.Method() calls Fn.Call(this) instead of Fn[this]() (where Fn is the function object which implements the method). Function objects should implement a Call method instead of the __Call meta-function.

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).

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, up to the stored length of the string (not its capacity). Historically, data was written beyond the value's length when dealing with binary data or structs; now, a Buffer object should be used instead.

Assignment expressions such as x.y := z now always yield the value of z, regardless of how x.y is implemented. The return value of a property setter is now ignored. __Set is currently still required to explicitly return to signal that the operation has been handled, but the value is ignored. Previously:

x.y(z) := v is now a syntax error. It was previously equivalent to x.y[z] := v. In general, x.y(z) (method call) and x.y[z] (parameterized property) are two different operations, although they may be equivalent if x is a COM object (due to limitations of the COM interface).

When an object is called via IDispatch (the COM interface), any uncaught exceptions which cannot be passed back to the caller will cause an error dialog. (The caller may or may not show an additional error dialog without any specific details.) This also applies to event handlers being called due to the use of ComObjConnect.


Functions can no longer be dynamically called with more parameters than they formally accept (but variadic functions can still accept any number of parameters).

Variadic functions are not affected by the above restriction, but normally will create an array each time they are called to hold the surplus parameters. If this array is not needed, the parameter name can now be omitted to prevent it from being created:

AcceptsOneOrMoreArgs(first, *) {

This can be used for callbacks where the additional parameters are not needed.

Variadic function calls now permit any enumerable object, where previously they required a standard Object with sequential numeric keys. If the enumerator returns more than one value per iteration, only the first value is used. For example, Array(mymap*) creates an array containing the keys of mymap.

Nested Functions

One function may be defined inside another. A nested function (if not force-local) can access the local and static variables of a function which encloses it.

Passing the name of a nested function to Func() (or another built-in function such as SetTimer) may create a Closure object, which binds the nested function to the outer function's current set of local variables. This has the same properties as a Func object.

The new "fat arrow" => operator can also be used to create nested functions.

Greater detail can be found in the documentation.


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

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

#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. It is not possible for local labels to be called externally (even by built-in functions). Nested functions can be used instead, allowing full use of local variables.

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. Additionally, an exception is thrown if obj is not an object or there is a problem retrieving or calling its enumerator.

Escaping a comma no longer has any meaning. Previously if used in an expression within a command's parameter and not within parentheses, it forced the comma to be interpreted as the multi-statement operator rather than as a delimiter between parameters. It only worked this way for commands, not functions or variable declarations.

The escape sequence `s is now allowed wherever `t is supported. It was previously only allowed by #IfWin and (Join.

*/ can now be placed at the end of a line to end a multi-line comment, to resolve a common point of confusion relating to how /* */ works in other languages. Due to the risk of ambiguity (e.g. with a hotstring ending in */), any */ which is not preceded by /* is no longer ignored (reversing a change made in AHK_L revision 54).

Integer constants and numeric strings outside of the supported range (of 64-bit signed integers) now overflow/wrap around, instead of being capped at the min/max value. This is consistent with math operators, so 9223372036854775807+1 == 9223372036854775808 (but both produce -9223372036854775808). This facilitates bitwise operations on 64-bit values.

For numeric strings, there are fewer cases where whitespace characters other than space and tab are allowed to precede the number. The general rule (in both v1 and v2) is that only space and tab are permitted, but in some cases other whitespace characters are tolerated due to C runtime library conventions.

If a script file contains lines exceeding the limit of 16383 characters, an error is displayed instead of silently splitting the line.

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 are automatically escaped (i.e. they are interpreted as literal characters) if the continuation section starts inside a quoted string. This avoids the need to escape quote marks in multi-line strings (if the starting and ending quotes are outside the continuation section) while still allowing multi-line expressions to contain quoted strings.

If the line above the continuation section ends with a name character and the section does not start inside a quoted string, a single space is automatically inserted to separate the name from the contents of the continuation section. This allows a continuation section to be used for a multi-line expression following return, function call statements, etc. It also ensures variable names are not joined with other tokens (or names), causing invalid expressions.

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).

The ` and % options have been removed, since there is no longer any need to escape these characters.

A new method of line continuation is supported in expressions and function/property definitions which utilizes the fact that each (/[/{ must be matched with a corresponding )/]/}. In other words, if a line contains an unclosed (/[/{, it will be joined with subsequent lines until the number of opening and closing symbols balances out. Brace { at the end of a line is considered to be one-true-brace (rather than the start of an object literal) if there are no other unclosed symbols and the brace is not immediately preceded by an operator.

Known limitation: If the { which starts a multi-line object literal is not preceded by an operator (such as if it is being passed as a parameter), it cannot be the last character on the line unless there is another unclosed (/[/{.

Continuation Lines

is, in and contains are usable for line continuation, though in and contains are still reserved/not yet implemented as operators.

and, or, is, in and contains act as line continuation operators even if followed by an assignment or other binary operator, since these are no longer valid variable names. By contrast, v1 had exceptions for and/or followed by any of: <>=/|^,:


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 float formatting has changed). Storing a number in a variable or returning it from a UDF retains its pure numeric status.

The default format specifier for floating-point numbers is now .17g (was 0.6f), which is more compact and more accurate in many cases. The default cannot be changed, but Format can be used to get different formatting.

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, or the specific class of an object.

Float(v), Integer(v) and String(v) convert v to the respective type, or throw an exception if the conversion cannot be performed (e.g. Integer("1z")). String calls v.ToString() if v is an object. (Ideally this would be done for any implicit conversion from object to string, but the current implementation makes this difficult.)


Objects now use a more structured class-prototype approach, separating class/static members from instance members. Many of the built-in methods and Obj functions have been moved, renamed, changed or removed.

The mixed Object type has been split into Object, Array and Map (associative array). Currently only these three classes and Class are predefined (and accessible as super-global variables).

Object is now the root class for all user-defined and built-in objects (this excludes COM objects). Therefore, x is Object is true if x is an AutoHotkey object. Members added to Object.Prototype are inherited by all AutoHotkey objects.

The operator is now expects a class, so x is y checks for y.Prototype in the base object chain. To check for y itself, call x.HasBase(y).

User-defined classes can also explicitly extend Object, Array or Map, with Object being the default base class if none is specified.

GetCapacity and SetCapacity were removed.

Properties and Methods

Properties and methods are now separate, like variables and functions:

The Object class defines new methods for dealing with properties and methods: DefineMethod, DefineProp, DeleteMethod, DeleteProp, GetMethod, GetOwnPropDesc, HasMethod, HasOwnMethod, HasOwnProp, HasProp, OwnMethods, OwnProps.

Because Object, Array and Map are now separate types, array elements are also separate from properties.

All built-in methods and properties (including base) are defined the same way as if user-defined. This ensures consistent behaviour and permits both built-in and user-defined members to be detected, retrieved or redefined.

If a property does not accept parameters, they are automatically passed to the object returned by the property (or it throws).

Multi-dimension array hacks were removed. x.y[z]:=1 no longer creates an object in x.y, and x[y,z] is an error unless x.__item handles two parameters (or x.__item.__item does, etc.).

If a property defines get but not set, assigning a value throws instead of overriding the property.


Meta-functions were greatly simplified; they act like normal methods:

Method and property parameters are passed as an Array. This optimizes for chained base/superclass calls and (in combination with MaxParams validation) encourages authors to handle the args. For __set, the value being assigned is passed separately.

this.__call(name, args)
this.__get(name, args)
this.__set(name, args, value)

Defined properties and methods take precedence over meta-functions, regardless of whether they were defined in a base object.

The static method __New is called for each class on startup, if defined by that class or inherited from a superclass. Currently classes are initialized in the order they are defined, so superclasses are not guaranteed to be initialized before subclasses.


class Array extends Object

An Array object contains a list or sequence of values, with index 1 being the first element.

When assigning or retrieving an array element, the absolute value of the index must be between 1 and the Length of the array, otherwise an exception is thrown. An array can be resized by inserting or removing elements with the appropriate method, or by assigning Length.

Currently brackets are required when accessing elements; i.e. a.1 refers to a property and a[1] refers to an element.

Negative values can be used to index in reverse.

Usage of Clone, Delete, InsertAt, Pop, Push and RemoveAt is basically unchanged. HasKey was renamed to Has. Length is now a property. The Capacity property was added.

Arrays can be constructed with Array(values*) or*).

For-loop usage is for val in arr or for idx, val in arr, where idx = A_Index by default. That is, elements lacking a value are still enumerated, and the index is not returned if only one variable is passed.


A Map object is an associative array with capabilities similar to the v1 Object, but less ambiguity.

Currently Float keys are still converted to strings.

Brackets are required when accessing elements; i.e. a.b refers to a property and a["b"] refers to an element. Unlike in v1, a property or method cannot be accidentally disabled by assigning an array element.

An exception is thrown if one attempts to retrieve the value of an element which does not exist.

Use Map(Key, Value, ...) to create a map from a list of key-value pairs. currently does not accept parameters.


Changed enumerator model to permit function objects as enumerators.

Since array elements, properties and methods are now separate, enumerating properties and methods requires explicitly creating an enumerator by calling OwnMethods or OwnProps.




Modified Commands/Functions

About the section title: there are no commands in v2, just functions. The title refers to both versions.

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

ClipWait now returns 0 if the wait period expires, otherwise 1. ErrorLevel is not set.

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.

Control: Several changes have been made to the Control parameter used by the Control functions, SendMessage and PostMessage:

ControlGetFocus now returns the control's HWND instead of its ClassNN, and sets ErrorLevel to 0 rather than 1 if it successfully determined that the window has no focused control.

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.)

ControlMove, ControlSend and ControlSetText now use parameter order consistent with the other Control functions; i.e. Control, WinTitle, WinText, ExcludeTitle, ExcludeText are always grouped together (at the end of the parameter list), to aide memorisation.

DllCall: If a type parameter is a variable, that variable's content is always used, never its name. In other words, unquoted type names are no longer supported - type names must be enclosed in quote marks.

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).

DllCall now accepts an object for any Ptr parameter; the object must have a Ptr property. For buffers allocated by the script, the new Buffer object is preferred over a variable.

FileAppend defaults to no end-of-line translations, consistent with FileRead and FileOpen. FileAppend and FileRead both have a separate Options parameter which replaces the option prefixes and may include an optional encoding name (superseding FileRead's *Pnnn option). FileAppend, FileRead and FileOpen use "`n" to enable end-of-line translations. FileAppend and FileRead support an option "RAW" to disable codepage conversion (read/write binary data); FileRead returns a Buffer object in this case. This replaces *c (see ClipboardAll in the documentation). FileAppend may accept a Buffer-like object, in which case no conversions are performed.

File.RawRead and File.RawWrite can now be given a variable containing an address (as a pure integer), where previously the variable's cached numeric string would have been used or overwritten, producing inconsistent results.

For buffers allocated by the script, the new Buffer object is preferred over a variable; any object can be used, but must have Ptr and Size properties.

Note: A future update may remove support for directly using a variable's string buffer for other purposes. Scripts should be updated to use BufferAlloc().

Already deprecated: If File.RawRead is given a variable, Bytes is optional and defaults to the variable's capacity. The variable's length is updated with the length of the data in characters (Ceil(bytes_read / 2)).

File.RawWrite can accept a string (or variable containing a string), in which case Bytes defaults to the size of the string in bytes. The string may contain binary zero.

File.ReadLine now always supports `r, `n and `r`n as line endings. Line endings are still returned to the script as-is by Read() if EOL translation is not enabled.

FileEncoding now allows code pages to be specified by number without the CP prefix.

FileExist now ignores the . and .. implied in every directory listing, so FileExist("dir\*") is now false instead of true when dir exists but is empty.

FileSelectFile (now named FileSelect) 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.

FileSetAttrib now overwrites attributes when no +, - or ^ prefix is present, instead of doing nothing. For example, FileSetAttrib(FileGetAttrib(file2), file1) copies the attributes of file2 to file1 (adding any that file2 have and removing any that it does not have).

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. If Fn is a nested function (not possible in v1), it may return a new Closure.

GetKeyState now always returns 0 or 1.

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. 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's Label parameter now supports any kind of expression which returns an object, and does not require a variable reference. If a label name is used, it must be global (not defined inside a function).

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.

#If has been optimized so that simple calls to WinActive() or WinExist() can be evaluated directly by the hook thread (as #IfWin was in v1, and Hotkey("IfWin... still is). This improves performance and reduces the risk of problems when the script is busy/unresponsive. This optimization applies to expressions which contain a single call to WinActive() or WinExist() with up to two parameters, where each parameter is a simple quoted string and the result is optionally inverted with ! or not. For example, #If WinActive("Chrome") or #If !WinExist("Popup"). In these cases, the first expression with any given combination of criteria can be identified by either the expression or the window criteria. For example, Hotkey('If', '!WinExist("Popup")') and Hotkey("IfWinNotExist", "Popup") refer to the same hotkey variants.

ImageSearch returns true if the image was found, false if it was not found, or throws an exception if the search could not be conducted. ErrorLevel is not set.

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). Previously, Input x started collecting input while Input on its own only terminated any input in progress. Since there is no longer an OutputVar parameter, Input now always starts collecting input while InputEnd terminates input. The original Input call sets ErrorLevel to "Stopped" if InputEnd was called. Input now treats Shift+Backspace the same as Backspace, instead of transcribing it to `b.

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.

IsFunc(Fn) now throws an exception if given an object. It should be used only to validate function names.

KeyWait now returns 0 if the wait period expires, otherwise 1. ErrorLevel is not set.

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. For buffers allocated by the script, the new Buffer object is preferred over a variable; any object can be used, but must have Ptr and Size properties.

Note: A future update may remove support for directly using a variable's string buffer for other purposes. Scripts should be updated to use BufferAlloc().

NumPut gains an additional mode where multiple numbers can be written in one call, with the type string preceding each number. For example: NumPut("ptr", a, "int", b, "int", c, addrOrBuffer, offset).

Note: The old modes of NumPut might be removed. That is, NumPut(num, addr, offset, type), NumPut(num, addr, type) and NumPut(num, addr). The new syntax shown above may become mandatory.

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 functions. Both functions throw an exception if a problem occurs, and no longer set ErrorLevel. PixelSearch returns true if the color was found. PixelSearch now defaults to the Fast mode and has an option for the Slow mode.

PostMessage: See SendMessage.

Random, , NewSeed was previously used to reseed the random number generator. Since Random no longer has an OutputVar parameter to omit, this is now done by calling RandomSeed instead.

RegExMatch options O and P were removed; O (object) mode is now mandatory. The RegExMatch object now supports enumeration (for-loop).

RegisterCallback was renamed to CallbackCreate and changed to better utilize closures:

Registry functions (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.

RegWrite's parameters were reordered to put Value first, like IniWrite (but this doesn't affect the single-parameter mode, where Value was the only parameter).

When KeyName is omitted and the current loop reg item is a subkey, RegDelete, RegRead and RegWrite now operate on values within that subkey; i.e. KeyName defaults to A_LoopRegKey "\" A_LoopRegName in that case (note that A_LoopRegKey was merged with A_LoopRegSubKey). Previously they behaved as follows:

RegDelete, RegRead and RegWrite now allow ValueName to be specified when KeyName is omitted:

Otherwise, RegDelete with a blank or omitted ValueName now deletes the key's default value (not the key itself), for consistency with RegWrite, RegRead and A_LoopRegName. The phrase "AHK_DEFAULT" no longer has any special meaning. To delete a key, use RegDeleteKey (new).

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

Run and RunWait no longer recognize the UseErrorLevel option. Use try/catch instead. A_LastError is set unconditionally, and can be inspected after an exception is caught/suppressed. RunWait returns the exit code. ErrorLevel is not set.

SendMessage and PostMessage now require wParam and lParam to be integers or objects with a Ptr property; an exception is thrown if they are given a non-numeric string or float. Previously a string was passed by address if the expression began with ", but other strings were coerced to integers. Passing &var no longer updates the variable's length (use VarSetCapacity(var, -1)).

SendMessage now sets ErrorLevel to 0 or 1 indicating only whether an error occurred, and returns the message reply (or an empty string on failure).

SetTimer's first parameter now supports any kind of expression which returns an object, and does not require a variable reference. Label names are no longer supported.

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

SoundGet now returns 1 or 0 for boolean settings instead of "On" or "Off", making it easier to invert the value (not/!).

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.

StrGet/StrPut: The Address parameter can be an object with the Ptr and Size properties, such as the new Buffer object. The read/write is automatically limited by Size (which is in bytes). If Length is also specified, it must not exceed Size (multiplied by 2 for UTF-16).

StrPut's return value is now in bytes, so it can be passed directly to BufferAlloc() or VarSetCapacity().

Suspend: Making a hotkey or hotstring's first line a call to Suspend no longer automatically makes it exempt from suspension. Instead, use #SuspendExempt. The "Permit" parameter value is no longer valid.

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).

VarSetCapacity(var, -1) now detects if var is not null-terminated, and terminates the program with an error message if so, as safe execution cannot be guaranteed.

WinSetTitle and WinMove now use parameter order consistent with other Win functions; i.e. WinTitle, WinText, ExcludeTitle, ExcludeText are always grouped together (at the end of the parameter list), to aide memorisation.

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).

WinWait, WinWaitClose, WinWaitActive and WinWaitNotActive return non-zero if the wait finished (timeout did not expire) and ErrorLevel is not set. WinWait and WinWaitActive return the HWND of the found window. WinWaitClose now sets the Last Found Window, so if WinWaitClose times out, it returns false and WinExist() returns the last window it found.


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.

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

The following functions return a pure integer instead of a hexadecimal string:

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

Loop Sub-commands

The sub-command keyword must be written literally; it should not be enclosed in quote marks and cannot be a variable or expression. All other parameters are expressions. All loop sub-commands now support OTB.


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]

A_LoopRegKey now contains the root key and subkey, and A_LoopRegSubKey was removed.


OutputVar := InputBox([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.

The title will be blank if the Title parameter is an empty string. It defaults to A_ScriptName only when completely omitted, consistent with optional parameters of user-defined functions.


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

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 is the name of the button, without spaces. These are the same strings that were used with IfMsgBox in v1.

The title will be blank if the Title parameter is an empty string. It defaults to A_ScriptName only when completely omitted, consistent with optional parameters of user-defined functions.


Sub-commands of Control, ControlGet, Drive, DriveGet, WinGet, WinSet and Process have been replaced with individual functions, and the main commands have been removed. Names and usage have been changed for several of the functions. The new usage is shown below:

; Where ... means optional Control, WinTitle, etc.

Boolean := ControlGetChecked(...)
Boolean := ControlGetEnabled(...)
Boolean := ControlGetVisible(...)
Integer := ControlGetTab(...)
String  := ControlGetChoice(...)
String  := ControlGetList([Options, ...])
Integer := ControlGetLineCount(...)
Integer := ControlGetCurrentLine(...)
Integer := ControlGetCurrentCol(...)
String  := ControlGetLine(N [, ...])
String  := ControlGetSelected(...)
Integer := ControlGetStyle(...)
Integer := ControlGetExStyle(...)
Integer := ControlGetHwnd(...)

           ControlSetChecked(TrueFalseOnOffToggle [, ...])
           ControlSetEnabled(TrueFalseOnOffToggle [, ...])
           ControlSetStyle(Value [, ...])
           ControlSetExStyle(Value [, ...])
           ControlSetTab(Index [, ...])
           ControlChoose(Index [, ...])
Index   := ControlChooseString(String [, ...])
           ControlEditPaste(String [, ...])

Index   := ControlFindItem(String [, ...])
Index   := ControlAddItem(String [, ...])
           ControlDeleteItem(Index [, ...])

           DriveEject([Drive, Retract := false])
           DriveSetLabel(Drive [, Label])

String  := DriveGetList([Type])
String  := DriveGetFilesystem(Drive)
String  := DriveGetLabel(Drive)
String  := DriveGetSerial(Drive)
String  := DriveGetType(Path)
String  := DriveGetStatus(Path)
String  := DriveGetStatusCD(Drive)
Integer := DriveGetCapacity(Path)
Integer := DriveGetSpaceFree(Path)

; Where ... means optional WinTitle, etc.

Integer := WinGetID(...)
Integer := WinGetIDLast(...)
Integer := WinGetPID(...)
String  := WinGetProcessName(...)
String  := WinGetProcessPath(...)
Integer := WinGetCount(...)
Array   := WinGetList(...)
Integer := WinGetMinMax(...)
Array   := WinGetControls(...)
Array   := WinGetControlsHwnd(...)
Integer := WinGetTransparent(...)
String  := WinGetTransColor(...)
Integer := WinGetStyle(...)
Integer := WinGetExStyle(...)

           WinSetTransparent(N [, ...])
           WinSetTransColor("Color [N]" [, ...]),
           WinSetAlwaysOnTop([Value := "Toggle", ...])
           WinSetStyle(Value [, ...])
           WinSetExStyle(Value [, ...])
           WinSetEnabled(Value [, ...])
           WinSetRegion(Value [, ...])


; These four currently all have OutputVars:
PID     := ProcessExist([PID_or_Name])
PID     := ProcessClose(PID_or_Name)
PID     := ProcessWait(PID_or_Name [, Timeout])
PID     := ProcessWaitClose(PID_or_Name [, Timeout])

           ProcessSetPriority(Priority [, PID_or_Name])

ProcessExist, ProcessClose, ProcessWait and ProcessWaitClose no longer set ErrorLevel; instead, they return the PID.

HWNDs and styles are always returned as pure integers, not hexadecimal strings.

ControlChoose allows 0 to deselect the current item/all items.

ControlGetTab returns 0 if no tab is selected (rare but valid).

WinGetList, WinGetControls and WinGetControlsHwnd return arrays, not newline-delimited lists.

Abbreviated aliases such as Topmost, Trans, FS and Cap were removed. Use the full function name or write your own wrapper function with the name of your choice.

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. However, some of the other functions only set ErrorLevel.

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

BufferAlloc(Size) creates and returns a Buffer object encapsulating block of Size bytes of memory, which is initially zero-filled. Buffer.Ptr returns the address and Buffer.Size returns or sets the size in bytes (reallocating the block of memory). Any object with Ptr and Size properties can be passed to NumPut, NumGet, StrPut, StrGet, File.RawRead, File.RawWrite and FileAppend. Any object with a Ptr property can be passed to SendMessage, PostMessage, and DllCall parameters with Ptr type.

CaretGetPos([X, Y]) retrieves the current coordinates of the caret (text insertion point). This ensures the X and Y coordinates always match up, and there is no caching to cause unexpected behaviour (such as A_CaretX/Y returning a value that's not in the current CoordMode).

ClipboardAll([Data, Size]) creates an object containing everything on the clipboard (optionally accepting data previously retrieved from the clipboard instead of using the clipboard's current contents). The methods of reading and writing clipboard file data are different. The data format is the same, except that the data size is always 32-bit, so that the data is portable between 32-bit and 64-bit builds. See the v2 documentation for details.

ControlGetClassNN returns the ClassNN of the specified control.

ControlSendText, equivalent to ControlSendRaw but using {Text} mode instead of {Raw} mode.

DirExist(Path), with usage similar to FileExist. Note that InStr(FileExist(Pattern), "D") only tells you whether the first matching file is a folder, not whether a folder exists.

Float(v): see Types.

Integer(v): see Types.

MenuCreate()/MenuBarCreate() returns a new Menu/MenuBar object, which has the following members corresponding to v1 Menu sub-commands. Methods: Add, AddStandard, Check, Delete, Disable, Enable, Insert, Rename, SetColor, SetIcon, Show, ToggleCheck, ToggleEnable, Uncheck. Properties: ClickCount, Default, Handle (replaces MenuGetHandle). A_TrayMenu also returns a Menu object. There is no UseErrorLevel mode, no global menu names, and no explicitly deleting the menu itself (that happens when all references are released; Menu.Delete() is equivalent to v1 DeleteAll). Labels are not supported, only functions and objects. Menu.AddStandard() adds the standard menu items and allows them to be individually modified as with custom items. Unlike v1, the Win32 menu is destroyed only when the object is deleted.

MenuFromHandle(Handle) returns the Menu object corresponding to a Win32 menu handle, if it was created by AutoHotkey.

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

SendText, equivalent to SendRaw but using {Text} mode instead of {Raw} mode.

StrCompare(str1, str2 [, CaseSensitive := false]) returns -1 (str1 is less than str2), 0 (equal) or 1 (greater than).

String(v): see Types.

SysGetIPAddresses() returns an array of IP addresses, equivalent to the A_IPAddress variables which have been removed. Each reference to A_IPAddress%N% retrieved all addresses but returned only one, so retrieving multiple addresses took exponentially longer than necessary. The returned array can have zero or more elements.

TraySetIcon([FileName, IconNumber, Freeze]) replaces Menu Tray, Icon.

WinGetClientPos([X, Y, W, H, WinTitle, ...]) retrieves the position and size of the window's client area, in screen coordinates.

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:

Built-in Objects

File objects now strictly require property syntax when invoking properties and method syntax when invoking methods. For example, File.Pos(n) is not valid. An exception is thrown if there are too few or too many parameters, or if a read-only property is assigned a value.

File.Tell() was removed.

Func.IsByRef() now works with built-in functions.


Gui, GuiControl and GuiControlGet were replaced with GuiCreate() and Gui/GuiControl objects, which are generally more flexible, more consistent, and arguably easier to use.

For details of the usage, see the new v2 documentation.

A GUI is typically not referenced by name/number (although it can still be named with the Gui.Name property). Instead, a GUI object (and window) is created explicitly by calling GuiCreate(), which returns a Gui object. This object has methods and properties which replace the Gui sub-commands. Gui.Add() returns a GuiControl object, which has methods and properties which replace the GuiControl and GuiControlGet commands. One can store this object in a variable, or use Gui.Control["Name"] or GuiCtrlFromHwnd(hwnd) to access retrieve the object. It is also passed as a parameter whenever an event handler (the replacement of a g-label) is called.

The usage of these methods and properties is not 1:1. Many parts have been revised to be more consistent and flexible, and to fix bugs or limitations.

There are no "default" GUIs, as the target Gui or control object is always specified. LV/TV/SB functions were replaced with methods (of the control object), making it much easier to use multiple ListViews/TreeViews.

There are no built-in variables containing information about events. The information is passed as parameters to the function/method which handles the event, including the source Gui or control.

Controls can still be named and be referenced by name, but it's just a name (used with Gui.Control["Name"] and Gui.Submit()), not an associated variable, so there is no need to declare or create a global or static variable. The value is never stored in a variable automatically, but is accessible via GuiCtrl.Value. Gui.Submit() returns a new associative array using the control names as keys.

The vName option now just sets the control's name to Name.

The +HwndVarName option has been removed in favour of GuiCtrl.Hwnd.

There are no more "g-labels" or labels/functions which automatically handle GUI events. The script must register for each event of interest by calling the OnEvent method of the Gui or GuiControl. For example, rather than checking if (A_GuiEvent = "I" && InStr(ErrorLevel, "F", true)) in a g-label, the script would register a handler for the ItemFocus event: MyLV.OnEvent("ItemFocus", "MyFunction"). MyFunction would be called only for the ItemFocus event. It is not necessary to apply AltSubmit to enable additional events.

In general, arrays can be used in place of a list in string form (with the current Gui delimiter, set by Gui +Delimiter). The string form is still supported, but never returned to the script (e.g. multi-select ListBox returns an array of selected items).

Gui sub-commands

Gui New → GuiCreate(). Passing an empty title (not omitting it) now results in an empty title, not the default title.

Gui Add → Gui.Add() or Gui.AddControlType(); e.g. Gui.Add("Edit") or Gui.AddEdit().

Gui Show → Gui.Show(), but it has no Title parameter. The title can be specified as a parameter of GuiCreate() or via the Gui.Title property.

Gui Submit → Gui.Submit(). It works like before, except that Submit() creates and returns a new object which contains all of the "associated variables".

Gui Destroy → Gui.Destroy(). The object still exists (until the script releases it) but cannot be used. A new GUI must be created (if needed). The window is also destroyed when the object is deleted, but the object is "kept alive" while the window is visible.

Gui Font → Gui.SetFont(). It is also possible to set a control's font directly, with GuiCtrl.SetFont().

Gui Color → Gui.BackColor sets/returns the background color. ControlColor (the second parameter) is not supported, but all controls which previously supported it can have a background set by the +Background option instead. Unlike Gui Color, Gui.BackColor does not affect Progress controls or disabled/read-only Edit, DDL, ComboBox or TreeView (with -Theme) controls.

Gui Margin → Gui.MarginX and Gui.MarginY properties.

Gui Menu → Gui.MenuBar sets/returns a MenuBar object, created with MenuBarCreate().

Gui Cancel/Hide/Minimize/Maximize/Restore → Gui methods of the same name.

Gui Flash → Gui.Flash(), but use false instead of Off.

Gui Tab → TabControl.UseTab(). Defaults to matching a prefix of the tab name as before. Pass true for the second parameter to match the whole tab name, but unlike the v1 "Exact" mode, it is case-insensitive.


The Size event passes 0, -1 or 1 (consistent with WinGetMinmax) instead of 0, 1 or 2.

The ContextMenu event can be registered for each control, or for the whole GUI.

The DropFiles event swaps the FileArray and Ctrl parameters, to be consistent with ContextMenu.

The ContextMenu and DropFiles events use client coordinates instead of window coordinates (Client is also the default CoordMode in v2).

The following control events were removed, but detecting them is a simple case of passing the appropriate numeric notification code (defined in the Windows SDK) to GuiCtrl.OnNotify(): K, D, d, A, S, s, M, C, E and MonthCal's 1 and 2.

Every supported event has a name, not a letter/number. See the v2 documentation for details.

Control events do not pass the event name as a parameter (GUI events never did).

Custom's N and Normal events were replaced with GuiCtrl.OnNotify() and GuiCtrl.OnCommand(), which can be used with any control.

Link's Click event passes (Ctrl, ID or Index, HREF) instead of (Ctrl, Index, HREF or ID), and does not automatically execute HREF if a Click callback is registered.

ListView's Click, DoubleClick and ContextMenu (when triggered by a right-click) events now report the item which was clicked (or 0 if none) instead of the focused item.

ListView's I event was split into multiple named events, except for the f (de-focus) event, which was excluded because it is implied by F (ItemFocus).

ListView's e (ItemEdit) event is ignored if the user cancels.

Slider's Change event is raised more consistently than the v1 g-label; i.e. it no longer ignores changes made by the mouse wheel by default. See the documentation for details.

The BS_NOTIFY style is now added automatically as needed for Button, CheckBox and Radio controls. It is no longer applied by default to Radio controls.

Focus (formerly F) and LoseFocus (formerly f) are supported by more (but not all) control types.

Setting an Edit control's text with Edit.Value or Edit.Text does not trigger the control's Change event, whereas GuiControl would trigger the control's g-label.

LV/TV.Add/Modify now suppress item-change events, so such events should only be raised by user action or SendMessage.


+HwndOutputVar → Gui.Hwnd or GuiCtrl.Hwnd
Gui GuiName: Default

Control Options

+/-Background is interpreted and supported more consistently. All controls which supported Gui Color now support +BackgroundColor and +BackgroundDefault (synonymous with -Background), not just ListView/TreeView/StatusBar/Progress.

Gui.Add defaults to y+m/x+m instead of yp/xp when xp/yp or xp+0/yp+0 is used. In other words, the control is placed below/to the right of the previous control instead of at exactly the same position. If a non-zero offset is used, the behaviour is the same as in v1. To use exactly the same position, specify xp yp together.

x+m and y+m can be followed by an additional offset, such as x+m+10 (x+m10 is also valid, but less readable).


Empty sub-command

GuiControlGet's empty sub-command had two modes: the default mode, and text mode, where the fourth parameter was the word Text. If a control type had no single "value", GuiControlGet defaulted to returning the result of GetWindowText (which isn't always visible text). Some controls had no visible text, or did not support retrieving it, so completely ignored the fourth parameter. By contrast, GuiCtrl.Text returns display text, hidden text (the same text returned by ControlGetText) or nothing at all.

The table below shows the closest equivalent property or function for each mode of GuiControlGet and control type.

ActiveX.Value.TextText is hidden. See below.
ComboBox.TextControlGetText()Use Value instead of Text if AltSubmit was used (but Value returns 0 if Text does not match a list item). Text performs case-correction, whereas ControlGetText returns the Edit field's content.
DDL.TextUse Value instead of Text if AltSubmit was used.
ListBox.TextControlGetText()Use Value instead of Text if AltSubmit was used. Text returns the selected item's text, whereas ControlGetText returns hidden text. See below.
ListView.TextText is hidden.
Tab.TextControlGetText()Use Value instead of Text if AltSubmit was used. Text returns the selected tab's text, whereas ControlGetText returns hidden text.
TreeView.TextText is hidden.

ListBox: For multi-select ListBox, Text and Value return an array instead of a pipe-delimited list.

ActiveX: GuiCtrl.Value returns the same object each time, whereas GuiControlGet created a new wrapper object each time. Consequently, it is no longer necessary to retain a reference to an ActiveX object for the purpose of keeping a ComObjConnect connection alive.

Other sub-commands

Pos → GuiCtrl.Pos; returns an associative array instead of a pseudo-array.

Focus → Gui.FocusedCtrl; returns a GuiControl object instead of the ClassNN.

FocusV → Gui.FocusedCtrl.Name

Hwnd → GuiCtrl.Hwnd; returns a pure integer, not a hexadecimal string.

Enabled/Visible/Name → GuiCtrl properties of the same name.


(Blank) and Text sub-commands

The table below shows the closest equivalent property or function for each mode of GuiControl and control type.

ActiveXN/ACommand had no effect.
ListViewN/ACommand had no effect.
Progress.ValueUse the += operator instead of the + prefix.
Slider.ValueUse the += operator instead of the + prefix.
StatusBar.Text or SB.SetText()
TreeViewN/ACommand had no effect.
UpDown.ValueUse the += operator instead of the + prefix.

Other sub-commands

Move → GuiCtrl.Move(...)

MoveDraw → GuiCtrl.Move(..., true)

Focus → GuiCtrl.Focus()

Enable/Disable → set GuiCtrl.Enabled

Hide/Show → set GuiCtrl.Visible

Choose → GuiCtrl.Choose(n), where in is a pure integer. The |n or ||n mode is not supported (use ControlChoose instead, if needed).

ChooseString → GuiCtrl.Choose(s), where s is not a pure integer. The |n or ||n mode is not supported. If the string matches multiple items in a multi-select ListBox, Choose() selects them all, not just the first.

Font → GuiCtrl.SetFont()

+/-Option → GuiCtrl.Opt("+/-Option")

Other Changes

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).

The default margins and control sizes (particularly for Button controls) may differ slightly from v1 when DPI > 100%.

Error Handling


A load-time error is raised for more syntax errors than in v1, such as:

An exception is thrown when any of the following failures occur (instead of ignoring the failure or producing an empty string):

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.


Functions (including those formerly known as 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 functions in a TRY block no longer affects this behaviour, so behaviour is more predictable.

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.

Math functions throw an exception 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.

Threads start out with an uninterruptible timeout of 17ms instead of 15ms. 15 was too low since the system tick count updates in steps of 15 or 16 minimum; i.e. if the tick count updated at exactly the wrong moment, the thread could become interruptible even though virtually no time had passed.

Threads which start out uninterruptible now remain so until at least one line has executed, even if the uninterruptible timeout expires first (such as if the system suspends the process immediately after the thread starts in order to give CPU time to another process).

#MaxThreads and #MaxThreadsPerHotkey no longer make exceptions for any subroutine whose first line is one of the following commands: ExitApp, Pause, Edit, Reload, KeyHistory, ListLines, ListVars, or ListHotkeys.

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).

RegEx callout functions can now be variadic.

Scripts read from stdin (e.g. with AutoHotkey.exe *) no longer include the initial working directory in A_ScriptFullPath or the main window's title, but it is used as A_ScriptDir and to locate the local Lib folder.

Hotstrings now treat Shift+Backspace the same as Backspace, instead of transcribing it to `b within the hotstring buffer.

Hotstrings use the first pair of colons (::) as a delimiter rather than the last when multiple pairs of colons are present. In other words, colons (when adjacent to another colon) must be escaped in the trigger text in v2, whereas in v1 they must be escaped in the replacement. Note that with an odd number of consecutive colons, the previous behaviour did not consider the final colon as part of a pair. For example, there is no change in behaviour for ::1:::2 (1:2), but ::3::::4 is now 3::4 rather than 3::4.

Hotstrings no longer escape colons in pairs, which means it is now possible to escape a single colon at the end of the hotstring trigger. For example, ::5`:::6 is now 5:6 rather than an error, and ::7`::::8 is now 7::8 rather than 7::8. It is best to escape every literal colon in these cases to avoid confusion (but a single isolated colon need not be escaped).

Hotstrings with continuation sections now default to Text mode instead of Raw mode.

Hotkeys now mask the Win/Alt key on release only if it is logically down and the hotkey requires the Win/Alt key (with #/! or a custom prefix). That is, hotkeys which do not require the Win/Alt key no longer mask Win/Alt-up when the Win/Alt key is physically down. This allows hotkeys which send {Blind}{LWin up} to activate the Start menu (which was already possible if using a remapped key such as AppsKey::RWin).


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

If one of the following occurs 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