Вы находитесь на странице: 1из 188

File, Directory and Disk

1
TextStream class
 
#define TEXT_IO_BLOCK 8192

#ifndef CP_UTF16
#define CP_UTF16 1200 // the codepage of UTF-16LE
#endif

#define TEOF ((TCHAR)EOF)

// VS2005 and later come with Unicode stream IO in the C runtime library, but it doesn ' t work very well.
// For example, it can ' t read the files encoded in ”system codepage” by using
// wide-char version of the functions such as fgetws(). The characters were not translated to
// UTF-16 properly. Although we can create some workarounds for it, but that will make
// the codes even hard to maintain.
class TextStream
{
public:
enum {
// open modes
READ
, WRITE
, APPEND
, UPDATE
, USEHANDLE = 0x10000000 // Used by BIF_FileOpen/FileObject. High value avoids conflict with the flags
,→ below, which can ' t change because it would break scripts.
, ACCESS_MODE_MASK = READ|WRITE|APPEND|UPDATE|USEHANDLE

// EOL translations
, EOL_CRLF = 0x00000004 // read: CRLF to LF. write: LF to CRLF.
, EOL_ORPHAN_CR = 0x00000008 // read: CR to LF (when the next character isn ' t LF)

// write byte order mark when open for write


, BOM_UTF8 = 0x00000010
, BOM_UTF16 = 0x00000020

// shared accesses
, SHARE_READ = 0x00000100
, SHARE_WRITE = 0x00000200
, SHARE_DELETE = 0x00000400
, SHARE_ALL = SHARE_READ|SHARE_WRITE|SHARE_DELETE
};

TextStream()
: mFlags(0), mCodePage(-1), mLength(0), mBuffer(NULL), mPos(NULL), mLastRead(0)
{
SetCodePage(CP_ACP);
}
virtual ˜TextStream()
{
if (mBuffer)
free(mBuffer);
//if (mLocale)
// _free_locale(mLocale);
// Close() isn ' t called here, it will rise a ”pure virtual function call” exception.
}

bool Open(LPCTSTR aFileSpec, DWORD aFlags, UINT aCodePage = CP_ACP);


void Close()
{

2
FlushWriteBuffer();
_Close();
}

DWORD Write(LPCTSTR aBuf, DWORD aBufLen = 0);


DWORD Write(LPCVOID aBuf, DWORD aBufLen);
DWORD Read(LPTSTR aBuf, DWORD aBufLen, int aNumLines = 0);
DWORD Read(LPVOID aBuf, DWORD aBufLen);

DWORD ReadLine(LPTSTR aBuf, DWORD aBufLen)


{
return Read(aBuf, aBufLen, 1);
}

INT_PTR FormatV(LPCTSTR fmt, va_list ap)


{
CString str;
str.FormatV(fmt, ap);
return Write(str, (DWORD)str.GetLength()) / sizeof(TCHAR);
}
INT_PTR Format(LPCTSTR fmt, ...)
{
va_list ap;
va_start(ap, fmt);
return FormatV(fmt, ap);
}

bool AtEOF()
// Returns true if there is no more data in the read buffer
// *and* the file pointer is at the end of the file.
{
if (mPos && mPos < mBuffer + mLength)
return false;
__int64 pos = _Tell();
return (pos < 0 || pos >= _Length());
}

void SetCodePage(UINT aCodePage)


{
if (aCodePage == CP_ACP)
aCodePage = g_ACP; // Required by _create_locale.
//if (!IsValidCodePage(aCodePage)) // Returns FALSE for UTF-16 and possibly other valid code pages, so
,→ leave it up to the user to pass a valid codepage.
//return;

if (mCodePage != aCodePage)
{
// Resist temptation to do the following as a way to avoid having an odd number of bytes in
// the buffer, since it breaks non-seeking devices and actually isn ' t sufficient for cases
// where the caller uses raw I/O in addition to text I/O (e.g. read one byte then read text).
//RollbackFilePointer();

mCodePage = aCodePage;
if (!GetCPInfo(aCodePage, &mCodePageInfo))
mCodePageInfo.LeadByte[0] = NULL;
}
}
UINT GetCodePage() { return mCodePage; }
DWORD GetFlags() { return mFlags; }

3
protected:
// IO abstraction
virtual bool _Open(LPCTSTR aFileSpec, DWORD &aFlags) = 0;
virtual void _Close() = 0;
virtual DWORD _Read(LPVOID aBuffer, DWORD aBufSize) = 0;
virtual DWORD _Write(LPCVOID aBuffer, DWORD aBufSize) = 0;
virtual bool _Seek(__int64 aDistance, int aOrigin) = 0;
virtual __int64 _Tell() const = 0;
virtual __int64 _Length() const = 0;

void RollbackFilePointer()
{
if (mPos) // Buffered reading was used.
{
// Discard the buffer and rollback the file pointer.
ptrdiff_t offset = (mPos - mBuffer) - mLength; // should be a value <= 0
_Seek(offset, SEEK_CUR);
// Callers expect the buffer to be cleared (e.g. to be reused for buffered writing), so if
// _Seek fails, the data is simply discarded. This can probably only happen for non-seeking
// devices such as pipes or the console, which won ' t typically be both read from and written to:
mPos = NULL;
mLength = 0;
}
}

void FlushWriteBuffer()
{
if (mLength && !mPos)
{
// Flush write buffer.
_Write(mBuffer, mLength);
mLength = 0;
}
mLastWriteChar = 0;
}

bool PrepareToWrite()
{
if (!mBuffer)
mBuffer = (BYTE *) malloc(TEXT_IO_BLOCK);
else if (mPos) // Buffered reading was used.
RollbackFilePointer();
return mBuffer != NULL;
}

bool PrepareToRead()
{
FlushWriteBuffer();
return true;
}

template<typename TCHR>
DWORD WriteTranslateCRLF(TCHR *aBuf, DWORD aBufLen); // Used by TextStream::Write(LPCSTR,DWORD).

// Functions for populating the read buffer.


DWORD Read(DWORD aReadSize = TEXT_IO_BLOCK)
{
ASSERT(aReadSize);
if (!mBuffer) {
mBuffer = (BYTE *) malloc(TEXT_IO_BLOCK);

4
if (!mBuffer)
return 0;
}
if (mLength + aReadSize > TEXT_IO_BLOCK)
aReadSize = TEXT_IO_BLOCK - mLength;
DWORD dwRead = _Read(mBuffer + mLength, aReadSize);
if (dwRead)
mLength += dwRead;
return mLastRead = dwRead; // The amount read *this time*.
}
bool ReadAtLeast(DWORD aReadSize)
{
if (!mPos)
Read(TEXT_IO_BLOCK);
else if (mPos > mBuffer + mLength - aReadSize) {
ASSERT( (DWORD)(mPos - mBuffer) <= mLength );
mLength -= (DWORD)(mPos - mBuffer);
memmove(mBuffer, mPos, mLength);
Read(TEXT_IO_BLOCK);
}
else
return true;
mPos = mBuffer;
return (mLength >= aReadSize);
}

__declspec(noinline) bool IsLeadByte(BYTE b) // noinline benchmarks slightly faster.


{
for (int i = 0; i < _countof(mCodePageInfo.LeadByte) && mCodePageInfo.LeadByte[i]; i += 2)
if (b >= mCodePageInfo.LeadByte[i] && b <= mCodePageInfo.LeadByte[i+1])
return true;
return false;
}

DWORD mFlags;
DWORD mLength; // The length of available data in the buffer, in bytes.
DWORD mLastRead;
UINT mCodePage;
CPINFO mCodePageInfo;

TCHAR mLastWriteChar;

union // Pointer to the next character to read in mBuffer.


{
LPBYTE mPos;
LPSTR mPosA;
LPWSTR mPosW;
};
union // Used by buffered/translated IO to hold raw file data.
{
LPBYTE mBuffer;
LPSTR mBufferA;
LPWSTR mBufferW;
};
};

class TextFile : public TextStream


{

5
public:
TextFile() : mFile(INVALID_HANDLE_VALUE) {}
virtual ˜TextFile() { FlushWriteBuffer(); _Close(); }

// Text IO methods from TextStream.


using TextStream::Read;
using TextStream::Write;

// These methods are exported to provide binary file IO.


bool Seek(__int64 aDistance, int aOrigin)
{
RollbackFilePointer();
FlushWriteBuffer();
return _Seek(aDistance, aOrigin);
}
__int64 Tell()
{
__int64 pos = _Tell();
return (pos == -1) ? -1 : pos + (mPos ? mPos - (mBuffer + mLength) : (ptrdiff_t)mLength);
}
__int64 Length()
{
__int64 len = _Length();
if (!mPos && mLength) // mBuffer contains data to write.
{
// Not len+mLength, since we might not be writing at the end of the file.
// Return the current position plus the amount of buffered data, except
// when that falls short of the current actual length of the file.
__int64 buf_end = _Tell() + mLength;
if (buf_end > len)
return buf_end;
}
return len;
}
__int64 Length(__int64 aLength)
{
// Truncating the file may mean discarding some of the data in the buffer:
// data read from a position after the new end-of-file, or data which should
// have already been written after the new end-of-file, but has been buffered.
// Calculating how much data should be discarded doesn ' t seem worthwhile, so
// just flush the buffer:
RollbackFilePointer();
FlushWriteBuffer();
// Since the buffer was just flushed, Tell() vs _Tell() doesn ' t matter here.
// Otherwise, using Tell() followed by _Seek() below would advance the pointer
// by the amount of buffered data, when the intention is to not move it at all.
__int64 pos = _Tell();
if (!_Seek(aLength, SEEK_SET) || !SetEndOfFile(mFile))
return -1;
// Make sure we do not extend the file again.
_Seek(min(aLength, pos), SEEK_SET);
return _Length();
}
HANDLE Handle() { RollbackFilePointer(); FlushWriteBuffer(); return mFile; }
protected:
virtual bool _Open(LPCTSTR aFileSpec, DWORD &aFlags);
virtual void _Close();
virtual DWORD _Read(LPVOID aBuffer, DWORD aBufSize);
virtual DWORD _Write(LPCVOID aBuffer, DWORD aBufSize);
virtual bool _Seek(__int64 aDistance, int aOrigin);

6
virtual __int64 _Tell() const;
virtual __int64 _Length() const;

private:
HANDLE mFile;
};

//
// TextStream
//
bool TextStream::Open(LPCTSTR aFileSpec, DWORD aFlags, UINT aCodePage)
{
mLength = 0; // Set the default value here so _Open() can change it.
if (!_Open(aFileSpec, aFlags))
return false;

SetCodePage(aCodePage);
mFlags = aFlags;
mLastWriteChar = 0;

int mode = aFlags & ACCESS_MODE_MASK;


if (mode == USEHANDLE)
return true;
if (mode != TextStream::WRITE) {
// Detect UTF-8 and UTF-16LE BOMs
if (mLength < 3)
Read(TEXT_IO_BLOCK); // TEXT_IO_BLOCK vs 3 for consistency and average-case performance.
mPos = mBuffer;
if (mLength >= 2) {
if (mBuffer[0] == 0xFF && mBuffer[1] == 0xFE) {
mPosW += 1;
SetCodePage(CP_UTF16);
}
else if (mBuffer[0] == 0xEF && mBuffer[1] == 0xBB) {
if (mLength >= 3 && mBuffer[2] == 0xBF) {
mPosA += 3;
SetCodePage(CP_UTF8);
}
}
}
}
if (mode == TextStream::WRITE || (mode == TextStream::APPEND || mode == TextStream::UPDATE) && _Length()
,→ == 0) {
if (aFlags & BOM_UTF8)
_Write(”\xEF\xBB\xBF”, 3);
else if (aFlags & BOM_UTF16)
_Write(”\xFF\xFE”, 2);
}
else if (mode == TextStream::APPEND)
{
mPos = NULL; // Without this, RollbackFilePointer() gets called later on and
mLength = 0; // if the file had no UTF-8 BOM we end up in the wrong position.
_Seek(0, SEEK_END);
}

return true;
}

7
DWORD TextStream::Read(LPTSTR aBuf, DWORD aBufLen, int aNumLines)
{
if (!PrepareToRead())
return 0;

DWORD target_used = 0;
LPBYTE src, src_end;
TCHAR dst[UorA(2,4)];
int src_size; // Size of source character, in bytes.
int dst_size; // Number of code units in destination character.

UINT codepage = mCodePage; // For performance.


int chr_size = (codepage == CP_UTF16) ? sizeof(WCHAR) : sizeof(CHAR);

// This is set each iteration based on how many bytes we *need* to have in the buffer.
// Avoid setting it higher than necessary since that can cause undesired effects with
// non-file handles - such as a console waiting for a second line of input when the
// first line is waiting in our buffer.
int next_size = chr_size;

while (target_used < aBufLen)


{
// Ensure the buffer contains at least one CHAR/WCHAR, or all bytes of the next
// character as determined by a previous iteration of the loop. Note that Read()
// only occurs if the buffer contains less than next_size bytes, and that this
// check does not occur frequently due to buffering and the inner loop below.
if (!ReadAtLeast(next_size) && !mLength)
break;

// Reset to default (see comments above).


next_size = chr_size;

// The following macro is used when there is insufficient data in the buffer,
// to determine if more data can possibly be read in. Using mLastRead should be
// faster than AtEOF(), and more reliable with console/pipe handles.
#define LAST_READ_HIT_EOF (mLastRead == 0)

// Because we copy mPos into a temporary variable here and update mPos at the end of
// each outer loop iteration, it is very important that ReadAtLeast() not be called
// after this point.
src = mPos;
src_end = mBuffer + mLength; // Maint: mLength is in bytes.

// Ensure there are an even number of bytes in the buffer if we are reading UTF-16.
// This can happen (for instance) when dealing with binary files which also contain
// UTF-16 strings, or if a UTF-16 file is missing its last byte.
if (codepage == CP_UTF16 && ((src_end - src) & 1))
{
// Try to defer processing of the odd byte until the next byte is read.
--src_end;
// If it ' s the only byte remaining, the safest thing to do is probably to drop it
// from the stream and output an invalid char so that the error can be detected:
if (src_end == src)
{
mPos = NULL;
mLength = 0;
aBuf[target_used++] = INVALID_CHAR;
break;
}
}

8
for ( ; src < src_end && target_used < aBufLen; src += src_size)
{
if (codepage == CP_UTF16)
{
src_size = sizeof(WCHAR); // Set default (currently never overridden).
LPWSTR cp = (LPWSTR)src;
if (*cp == ' \r ' )
{
if (cp + 2 <= (LPWSTR)src_end)
{
if (cp[1] == ' \n ' )
{
// There ' s an \n following this \r, but is \r\n considered EOL?
if ( !(mFlags & EOL_CRLF) )
// This \r isn ' t being translated, so just write it out.
aBuf[target_used++] = ' \r ' ;
continue;
}
}
else if (!LAST_READ_HIT_EOF)
{
// There ' s not enough data in the buffer to determine if this is \r\n.
// Let the next iteration handle this char after reading more data.
next_size = 2 * sizeof(WCHAR);
break;
}
// Since above didn ' t break or continue, this is an orphan \r.
}
// There doesn ' t seem to be much need to give surrogate pairs special handling,
// so the following is disabled for now. Some ”brute force” tests on Windows 7
// showed that none of the ANSI code pages are capable of representing any of
// the supplementary characters. Even if we pass the full pair in a single call,
// the result is the same as with two separate calls: ”??”.
/*if (*cp >= 0xD800 && *cp <= 0xDBFF) // High surrogate.
{
if (src + 3 >= src_end && !LAST_READ_HIT_EOF)
{
// There should be a low surrogate following this, but since there ' s
// not enough data in the buffer we need to postpone processing it.
break;
}
// Rather than discarding unpaired high/low surrogate code units, let them
// through as though this is UCS-2, not UTF-16. The following check is not
// necessary since low surrogates can ' t be misinterpreted as \r or \n:
//if (cp[1] >= 0xDC00 && cp[1] <= 0xDFFF)
}*/
#ifdef UNICODE
*dst = *cp;
dst_size = 1;
#else
dst_size = WideCharToMultiByte(CP_ACP, 0, cp, 1, dst, _countof(dst), NULL, NULL);
#endif
}
else
{
src_size = 1; // Set default.
if (*src < 0x80)
{
if (*src == ' \r ' )

9
{
if (src + 1 < src_end)
{
if (src[1] == ' \n ' )
{
// There ' s an \n following this \r, but is \r\n considered EOL?
if ( !(mFlags & EOL_CRLF) )
// This \r isn ' t being translated, so just write it out.
aBuf[target_used++] = ' \r ' ;
continue;
}
}
else if (!LAST_READ_HIT_EOF)
{
// There ' s not enough data in the buffer to determine if this is \r\n.
// Let the next iteration handle this char after reading more data.
next_size = 2;
break;
}
// Since above didn ' t break or continue, this is an orphan \r.
}
// No conversion needed for ASCII chars.
*dst = *(LPSTR)src;
dst_size = 1;
}
else
{
if (codepage == CP_UTF8)
{
if ((*src & 0xE0) == 0xC0)
src_size = 2;
else if ((*src & 0xF0) == 0xE0)
src_size = 3;
else if ((*src & 0xF8) == 0xF0)
src_size = 4;
else { // Invalid in current UTF-8 standard.
aBuf[target_used++] = INVALID_CHAR;
continue;
}
}
else if (IsLeadByte(*src))
src_size = 2;
// Otherwise, leave it at the default set above: 1.

// Ensure that the expected number of bytes are available:


if (src + src_size > src_end)
{
if (LAST_READ_HIT_EOF)
{
mLength = 0; // Discard all remaining data, since it appears to be invalid.
src = NULL; // mPos is set to this outside the inner loop.
aBuf[target_used++] = INVALID_CHAR;
}
else
{
next_size = src_size;
// Let the next iteration handle this char after reading more data.
// If no more data is read, LAST_READ_HIT_EOF will be true and the
// next iteration will produce INVALID_CHAR.
}

10
break;
}
#ifdef UNICODE
dst_size = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPSTR)src, src_size, dst,
,→ _countof(dst));
#else
if (codepage == g_ACP)
{
// This char doesn ' t require any conversion.
*dst = *(LPSTR)src;
if (src_size > 1) // Can only be 1 or 2 in this case.
dst[1] = src[1];
dst_size = src_size;
}
else
{
// Convert this single- or multi-byte char to Unicode.
int wide_size;
WCHAR wide_char[2];
wide_size = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, (LPSTR)src, src_size,
,→ wide_char, _countof(wide_char));
if (wide_size)
{
// Convert from Unicode to the system ANSI code page.
dst_size = WideCharToMultiByte(CP_ACP, 0, wide_char, wide_size, dst, _countof(dst)
,→ , NULL, NULL);
}
else
{
src_size = 1; // Seems best to drop only this byte, even if it appeared to be a
,→ lead byte.
dst_size = 0; // Allow the check below to handle it.
}
}
#endif
} // end (*src >= 0x80)
}

if (dst_size == 1)
{
// \r\n has already been handled above, even if !(mFlags & EOL_CRLF), so \r at
// this point can only be \r on its own:
if (*dst == ' \r ' && (mFlags & EOL_ORPHAN_CR))
*dst = ' \n ' ;
if (*dst == ' \n ' )
{
if (--aNumLines == 0)
{
// Our caller asked for a specific number of lines, which we now have.
aBuf[target_used++] = ' \n ' ;
mPos = src + src_size;
if (target_used < aBufLen)
aBuf[target_used] = ' \0 ' ;
return target_used;
}
}

// If we got to this point, dst contains a single TCHAR:


aBuf[target_used++] = *dst;
}

11
else if (dst_size) // Multi-byte/surrogate pair.
{
if (target_used + dst_size > aBufLen)
{
// This multi-byte char/surrogate pair won ' t fit, so leave it in the file buffer.
mPos = src;
aBuf[target_used] = ' \0 ' ;
return target_used;
}
tmemcpy(aBuf + target_used, dst, dst_size);
target_used += dst_size;
}
else
{
aBuf[target_used++] = INVALID_CHAR;
}
} // end for-loop which processes buffered data.
if (src == src_end)
{
// Reset the buffer so that Read() can read a full block.
mLength = 0;
mPos = NULL;
}
else
mPos = src;
} // end for-loop which repopulates the buffer.
if (target_used < aBufLen)
aBuf[target_used] = ' \0 ' ;
// Otherwise, caller is responsible for reserving one char and null-terminating if needed.
return target_used;
}

DWORD TextStream::Read(LPVOID aBuf, DWORD aBufLen)


{
if (!PrepareToRead() || !aBufLen)
return 0;

DWORD target_used = 0;

if (mPos)
{
DWORD data_in_buffer = (DWORD)(mBuffer + mLength - mPos);
if (data_in_buffer >= aBufLen)
{
// The requested amount of data already exists in our buffer, so copy it over.
memcpy(aBuf, mPos, aBufLen);
if (data_in_buffer == aBufLen)
{
mPos = NULL; // No more data in buffer.
mLength = 0; //
}
else
mPos += aBufLen;
return aBufLen;
}

// Consume all buffered data. If there is none (i.e. mPos was somehow pointing at the
// end of the buffer), it is crucial that we clear the buffer for the next section.

12
memcpy(aBuf, mPos, data_in_buffer);
target_used = data_in_buffer;
mLength = 0;
mPos = NULL;
}

LPBYTE target = (LPBYTE)aBuf + target_used;


DWORD target_remaining = aBufLen - target_used;

if (target_remaining < TEXT_IO_BLOCK)


{
Read(TEXT_IO_BLOCK);

if (mLength <= target_remaining)


{
// All of the data read above will fit in the caller ' s buffer.
memcpy(target, mBuffer, mLength);
target_used += mLength;
mLength = 0;
// Since no data remains in the buffer, mPos can remain set to NULL.
// UPDATE: If (mPos == mBuffer + mLength), it was not set to NULL above.
mPos = NULL;
}
else
{
// Surplus data was read
memcpy(target, mBuffer, target_remaining);
target_used += target_remaining;
mPos = mBuffer + target_remaining;
}
}
else
{
// The remaining data to be read exceeds the capacity of our buffer, so bypass it.
target_used += _Read(target, target_remaining);
}

return target_used;
}

DWORD TextStream::Write(LPCTSTR aBuf, DWORD aBufLen)


// Returns the number of bytes aBuf took after performing applicable EOL and
// code page translations. Since data is buffered, this is generally not the
// amount actually written to file. Returns 0 on apparent critical failure,
// even if *some* data was written into the buffer and/or to file.
{
if (!PrepareToWrite())
return 0;

if (aBufLen == 0)
{
aBufLen = (DWORD)_tcslen(aBuf);
if (aBufLen == 0) // Below may rely on this having been checked.
return 0;
}

DWORD bytes_flushed = 0; // Number of buffered bytes flushed to file; used to calculate our return value.

13
LPCTSTR src;
LPCTSTR src_end;
int src_size;

union {
LPBYTE dst;
LPSTR dstA;
LPWSTR dstW;
};
dst = mBuffer + mLength;

// Allow enough space in the buffer for any one of the following:
// a 4-byte UTF-8 sequence
// a UTF-16 surrogate pair
// a carriage-return/newline pair
LPBYTE dst_end = mBuffer + TEXT_IO_BLOCK - 4;

for (src = aBuf, src_end = aBuf + aBufLen; ; )


{
// The following section speeds up writing of ASCII characters by copying as many as
// possible in each iteration of the outer loop, avoiding certain checks that would
// otherwise be made once for each char. This relies on the fact that ASCII chars
// have the same binary value (but not necessarily width) in every supported encoding.
// EOL logic is also handled here, for performance. An alternative approach which is
// tidier and performs almost as well is to add (*src != ' \n ' ) to each loop ' s condition
// and handle it after the loop terminates.
if (mCodePage != CP_UTF16)
{
for ( ; src < src_end && !(*src & ˜0x7F) && dst < dst_end; ++src)
{
if (*src == ' \n ' && (mFlags & EOL_CRLF) && ((src == aBuf) ? mLastWriteChar : src[-1]) != ' \r ' )
*dstA++ = ' \r ' ;
*dstA++ = (CHAR)*src;
}
}
else
{
#ifdef UNICODE
for ( ; src < src_end && dst < dst_end; ++src)
#else
for ( ; src < src_end && !(*src & ˜0x7F) && dst < dst_end; ++src) // No conversion needed for
,→ ASCII chars.
#endif
{
if (*src == ' \n ' && (mFlags & EOL_CRLF) && ((src == aBuf) ? mLastWriteChar : src[-1]) != ' \r ' )
*dstW++ = ' \r ' ;
*dstW++ = (WCHAR)*src;
}
}

if (dst >= dst_end)


{
DWORD len = (DWORD)(dst - mBuffer);
if (_Write(mBuffer, len) < len)
{
// The following isn ' t done since there ' s no way for the caller to know
// how much of aBuf was successfully translated or written to file, or
// even how many bytes to expect due to EOL and code page translations:
//if (written)
//{

14
// // Since a later call might succeed, remove this data from the buffer
// // to prevent it from being written twice. Note that some or all of
// // this data might ' ve been buffered by a previous call.
// memmove(mBuffer, mBuffer + written, mLength - written);
// mLength -= written;
//}
// Instead, dump the contents of the buffer along with the remainder of aBuf,
// then return 0 to indicate a critical failure.
mLength = 0;
return 0;
}
bytes_flushed += len;
dst = mBuffer;
continue; // If *src is ASCII, we want to use the high-performance mode (above).
}

if (src == src_end)
break;

#ifdef UNICODE
if (*src >= 0xD800 && *src <= 0xDBFF // i.e. this is a UTF-16 high surrogate.
&& src + 1 < src_end // If this is at the end of the string, there is no low surrogate.
&& src[1] >= 0xDC00 && src[1] <= 0xDFFF) // This is a complete surrogate pair.
#else
if (IsLeadByteACP((BYTE)*src) && src + 1 < src_end) // src[1] is the second byte of this char.
#endif
src_size = 2;
else
src_size = 1;

#ifdef UNICODE
ASSERT(mCodePage != CP_UTF16); // An optimization above already handled UTF-16.
dstA += WideCharToMultiByte(mCodePage, 0, src, src_size, dstA, 4, NULL, NULL);
src += src_size;
#else
if (mCodePage == g_ACP)
{
*dst++ = (BYTE)*src++;
if (src_size == 2)
*dst++ = (BYTE)*src++;
}
else
{
WCHAR wc;
if (MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, src, src_size, &wc, 1))
{
if (mCodePage == CP_UTF16)
*dstW++ = wc;
else
dstA += WideCharToMultiByte(mCodePage, 0, &wc, 1, (LPSTR)dst, 4, NULL, NULL);
}
src += src_size;
}
#endif
}

mLastWriteChar = src_end[-1]; // So if this is \r and the next char is \n, don ' t make it \r\r\n.

DWORD initial_length = mLength;


mLength = (DWORD)(dst - mBuffer);

15
return bytes_flushed + mLength - initial_length; // Doing it this way should perform better and result in
,→ smaller code than counting each byte put into the buffer.
}

DWORD TextStream::Write(LPCVOID aBuf, DWORD aBufLen)


{
if (!PrepareToWrite())
return 0;

if (aBufLen < TEXT_IO_BLOCK - mLength) // There would be room for at least 1 byte after appending data.
{
// Buffer the data.
memcpy(mBuffer + mLength, aBuf, aBufLen);
mLength += aBufLen;
return aBufLen;
}
else
{
// data is bigger than the remaining space in the buffer. If (len < TEXT_IO_BLOCK*2 - mLength), we
// could copy the first part of data into the buffer, flush it, then write the remainder into the
// buffer to await more text to be buffered. However, the need for a memcpy combined with the added
// code size and complexity mean it probably isn ' t worth doing.
if (mLength)
{
_Write(mBuffer, mLength);
mLength = 0;
}
return _Write(aBuf, aBufLen);
}
}

//
// TextFile
//
bool TextFile::_Open(LPCTSTR aFileSpec, DWORD &aFlags)
{
_Close();
DWORD dwDesiredAccess, dwShareMode, dwCreationDisposition;
switch (aFlags & ACCESS_MODE_MASK) {
case READ:
dwDesiredAccess = GENERIC_READ;
dwCreationDisposition = OPEN_EXISTING;
break;
case WRITE:
dwDesiredAccess = GENERIC_WRITE;
dwCreationDisposition = CREATE_ALWAYS;
break;
case APPEND:
case UPDATE:
dwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
dwCreationDisposition = OPEN_ALWAYS;
break;
case USEHANDLE:
if (!GetFileType((HANDLE)aFileSpec))
return false;
mFile = (HANDLE)aFileSpec;

16
return true;
}
dwShareMode = ((aFlags >> 8) & (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE));

if (*aFileSpec == ' * ' )


{
// v1.1.17: Allow FileOpen(”*”, ”r|w”) to open stdin/stdout/stderr (”**” for stderr).
// Can also be used to read script text from stdin, by passing ”*” as the filename.
DWORD nStdHandle = 0;
switch (aFlags & ACCESS_MODE_MASK)
{
case APPEND:
// Allow FileAppend to write to stdout/stderr via TextStream.
aFlags = (aFlags & ˜ACCESS_MODE_MASK) | READ;
case WRITE:
if (!aFileSpec[1])
nStdHandle = STD_OUTPUT_HANDLE;
else if (aFileSpec[1] == ' * ' && !aFileSpec[2])
nStdHandle = STD_ERROR_HANDLE;
break;
case READ:
if (!aFileSpec[1])
nStdHandle = STD_INPUT_HANDLE;
break;
}
if (nStdHandle) // It was * or ** and not something invalid like *Somefile.
{
HANDLE hstd = GetStdHandle(nStdHandle);
if (hstd == NULL)// || !DuplicateHandle(GetCurrentProcess(), hstd, GetCurrentProcess(), &hstd, 0,
,→ FALSE, DUPLICATE_SAME_ACCESS))
return false;
aFlags = (aFlags & ˜ACCESS_MODE_MASK) | USEHANDLE; // Avoid calling CloseHandle(), since we don ' t
,→ own it.
mFile = hstd; // Only now that we know it ' s not NULL.
return true;
}
// For any case not handled above, such as WRITE|READ combined or *** or *Somefile,
// it should be detected as an error below by CreateFile() failing (or if not, it ' s
// somehow valid and should not be treated as an error).
}

// FILE_FLAG_SEQUENTIAL_SCAN is set, as sequential accesses are quite common for text files handling.
mFile = CreateFile(aFileSpec, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition,
(aFlags & (EOL_CRLF | EOL_ORPHAN_CR)) ? FILE_FLAG_SEQUENTIAL_SCAN : 0, NULL);

return mFile != INVALID_HANDLE_VALUE;


}

void TextFile::_Close()
{
if (mFile != INVALID_HANDLE_VALUE) {
if ((mFlags & ACCESS_MODE_MASK) != USEHANDLE)
CloseHandle(mFile);
mFile = INVALID_HANDLE_VALUE;
}
}

DWORD TextFile::_Read(LPVOID aBuffer, DWORD aBufSize)


{
DWORD dwRead = 0;

17
ReadFile(mFile, aBuffer, aBufSize, &dwRead, NULL);
return dwRead;
}

DWORD TextFile::_Write(LPCVOID aBuffer, DWORD aBufSize)


{
DWORD dwWritten = 0;
WriteFile(mFile, aBuffer, aBufSize, &dwWritten, NULL);
return dwWritten;
}

bool TextFile::_Seek(__int64 aDistance, int aOrigin)


{
return !!SetFilePointerEx(mFile, *((PLARGE_INTEGER) &aDistance), NULL, aOrigin);
}

__int64 TextFile::_Tell() const


{
LARGE_INTEGER in = {0}, out;
return SetFilePointerEx(mFile, in, &out, FILE_CURRENT) ? out.QuadPart : -1;
}

__int64 TextFile::_Length() const


{
LARGE_INTEGER size;
GetFileSizeEx(mFile, &size);
return size.QuadPart;
}
 

18
FileAppend

Writes text to the end of a file (first creating the file, if necessary).
 
FileAppend [, Text, Filename, Encoding]
 
Parameters

• Text The text to append to the file. This text may include linefeed characters (‘n) to start new lines. In addition, a single long line can be broken
up into several shorter ones by means of a continuation section.

If Text is blank, Filename will be created as an empty file (but if the file already exists, its modification time will be updated).

If Text is %ClipboardAll% or a variable that was previously assigned the value of ClipboardAll, Filename will be unconditionally overwritten with
the entire contents of the clipboard (i.e. FileDelete is not necessary).

• Filename The name of the file to be appended, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

End of line (EOL) translation: To disable EOL translation, prepend an asterisk to the filename. This causes each linefeed character (‘n) to be
written as a single linefeed (LF) rather than the Windows standard of CR+LF. For example: *C:\My Unix File.txt.

If the file is not already open (due to being inside a file-reading loop), EOL translation is automatically disabled if Text contains any carriage
return and linefeed pairs (‘r‘n). In other words, the asterisk option described in the previous paragraph is put into effect automatically. However,
specifying the asterisk when Text contains ‘r‘n improves performance because the program does not need to scan Text for ‘r‘n.

Standard Output (stdout): Specifying an asterisk (*) for Filename causes Text to be sent to standard output (stdout). Such text can be redirected
to a file, piped to another EXE, or captured by fancy text editors. For example, the following would be valid if typed at a command prompt:

”%ProgramFiles%\AutoHotkey\AutoHotkey.exe” ”My Script.ahk” >”Error Log.txt”

However, text sent to stdout will not appear at the command prompt it was launched from. This can be worked around by piping a script’s output
to another command or program. For example:

”%ProgramFiles%\AutoHotkey\AutoHotkey.exe” ”My Script.ahk” |more For /F ”tokens=*” %L in ('””%ProgramFiles


,→ %\AutoHotkey\AutoHotkey.exe” ”My Script .ahk””')do @Echo %L
Specifying two asterisks (**) for Filename causes Text to be sent to the stderr stream.

• Encoding Overrides the default encoding set by FileEncoding, where Encoding follows the same format.

Remarks

To overwrite an existing file, delete it with FileDelete prior to using FileAppend.

The target file is automatically closed after the text is appended (except when FileAppend is used in its single-parameter mode inside a file-reading/writing
loop).

FileOpen() in append mode provides more control than FileAppend and allows the file to be kept open rather than opening and closing it each time.
Once a file is opened in append mode, use file.Write(string) to append the string. File objects also support binary I/O via RawWrite/RawRead or
WriteNum/ReadNum, whereas FileAppend supports only text.
 
ResultType Line::FileAppend(LPTSTR aFilespec, LPTSTR aBuf, LoopReadFileStruct *aCurrentReadFile)
{
// The below is avoided because want to allow ”nothing” to be written to a file in case the
// user is doing this to reset it ' s timestamp (or create an empty file).
//if (!aBuf || !*aBuf)
// return g_ErrorLevel->Assign(ERRORLEVEL_NONE);

if (aCurrentReadFile) // It always takes precedence over aFilespec.


aFilespec = aCurrentReadFile->mWriteFileName;
if (!*aFilespec) // Nothing to write to (caller relies on this check).
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

TextStream *ts = aCurrentReadFile ? aCurrentReadFile->mWriteFile : NULL;


bool file_was_already_open = ts;
BOOL result;

bool open_as_binary = (*aFilespec == ' * ' );

19
if (open_as_binary)
{
if (aFilespec[1] && (aFilespec[1] != ' * ' || !aFilespec[2])) // i.e. it ' s not just * (stdout) or ** (
,→ stderr).
{
// Do not do this because it ' s possible for filenames to start with a space
// (even though Explorer itself won ' t let you create them that way):
//write_filespec = omit_leading_whitespace(write_filespec + 1);
// Instead just do this:
++aFilespec;
}
#ifdef CONFIG_DEBUGGER
else if (!aFilespec[1] && g_Debugger.FileAppendStdOut(aBuf))
{
// StdOut has been redirected to the debugger, so return.
return SetErrorsOrThrow(false, 0);
}
#endif
}
else if (!file_was_already_open) // As of 1.0.25, auto-detect binary if that mode wasn ' t explicitly
,→ specified.
{
// sArgVar is used for two reasons:
// 1) It properly resolves dynamic variables, such as ”FileAppend, % %
,→ VarContainingTheStringClipboardAll%, File”.
// 2) It resolves them only once at a prior stage, rather than having to do them again here
// (which helps performance).
if (ARGVAR1)
{
if (ARGVAR1->Type() == VAR_CLIPBOARDALL)
return WriteClipboardToFile(aFilespec);
else if (ARGVAR1->IsBinaryClip())
{
// Since there is at least one deref in Arg #1 and the first deref is binary clipboard,
// assume this operation ' s only purpose is to write binary data from that deref to a file.
// This is because that ' s the only purpose that seems useful and that ' s currently supported.
// In addition, the file is always overwritten in this mode, since appending clipboard data
// to an existing clipboard file would not work due to:
// 1) Duplicate clipboard formats not making sense (i.e. two CF_TEXT formats would cause the
// first to be overwritten by the second when restoring to clipboard).
// 2) There is a 4-byte zero terminator at the end of the file.
return WriteClipboardToFile(aFilespec, ARGVAR1);
}
}
// Auto-detection avoids the need to have to translate \r\n to \n when reading
// a file via the FileRead command. This seems extremely unlikely to break any
// existing scripts because the intentional use of \r\r\n in a text file (two
// consecutive carriage returns) -- which would happen if \r\n were written in
// text mode -- is so rare as to be close to non-existent. If this behavior
// is ever specifically needed, the script can explicitly places some \r\r\n ' s
// in the file and then write it as binary mode.
open_as_binary = _tcsstr(aBuf, _T(”\r\n”)); // Performance: The following could be done instead, but
,→ seems likely to cause some scripts to write \r\r\n and even \r\r\r\n due to the text having
,→ both \n and \r\n in it: char *first_newline = _tcschr(aBuf, ' \n ' )... open_as_binary =
,→ first_newline > aBuf && aBuf[-1] == ' \r '
// Due to ”else if”, the above will not turn off binary mode if binary was explicitly specified.
// That is useful to write Unix style text files whose lines end in solitary linefeeds.
}

// Check if the file needs to be opened. As of 1.0.25, this is done here rather than

20
// at the time the loop first begins so that:
// 1) Binary mode can be auto-detected if the first block of text appended to the file
// contains any \r\n ' s.
// 2) To avoid opening the file if the file-reading loop has zero iterations (i.e. it ' s
// opened only upon first actual use to help performance and avoid changing the
// file-modification time when no actual text will be appended).
if (!file_was_already_open)
{
DWORD flags = TextStream::APPEND | (open_as_binary ? 0 : TextStream::EOL_CRLF);

UINT codepage = mArgc > 2 ? ConvertFileEncoding(ARG3) : g->Encoding;


if (codepage == -1) // ARG3 was invalid.
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

ASSERT( (˜CP_AHKNOBOM) == CP_AHKCP );


// codepage may include CP_AHKNOBOM, in which case below will not add BOM_UTFxx flag.
if (codepage == CP_UTF8)
flags |= TextStream::BOM_UTF8;
else if (codepage == CP_UTF16)
flags |= TextStream::BOM_UTF16;

// Open the output file (if one was specified). Unlike the input file, this is not
// a critical error if it fails. We want it to be non-critical so that FileAppend
// commands in the body of the loop will set ErrorLevel to indicate the problem:
if ( !(ts = new TextFile) ) // ts was alredy verified NULL via !file_was_already_open.
return LineError(ERR_OUTOFMEM);
if ( !ts->Open(aFilespec, flags, codepage & CP_AHKCP) )
{
delete ts; // Must be deleted explicitly!
return SetErrorsOrThrow(true);
}
if (aCurrentReadFile)
aCurrentReadFile->mWriteFile = ts;
}

// Write to the file:


DWORD length = (DWORD)_tcslen(aBuf);
result = length && ts->Write(aBuf, length) == 0; // Relies on short-circuit boolean evaluation. If buf is
,→ empty, we ' ve already succeeded in creating the file and have nothing further to do.

if (!aCurrentReadFile)
delete ts;
// else it ' s the caller ' s responsibility, or it ' s caller ' s, to close it.

return SetErrorsOrThrow(result);
}
 

21
FileOpen

Opens a file.
 
file := FileOpen(Filename, Flags [, Encoding])
 
Parameters

• Filename The path of the file to open, which is assumed to be in A_WorkingDir if an absolute path isn’t specified.

Specify an asterisk (or two) as shown below to open the standard input/output/error stream:
 
FileOpen(”*”, ”r”) ; for stdin
FileOpen(”*”, ”w”) ; for stdout
FileOpen(”**”, ”w”) ; for stderr
 
• Flags Either a string of characters indicating the desired access mode followed by other options (with optional spaces or tabs in between); or a
combination (sum) of numeric flags. Supported values are described in the table below.

• Encoding The code page to use for text I/O if the file does not contain a UTF-8 or UTF-16 byte order mark, or if the h (handle) flag is used. If
omitted, the current value of A_FileEncoding is used.

Return Value

If the file is opened successfully, the return value is a File object.

If the function fails, the return value is 0 and A_LastError contains an error code.

Use if file or IsObject(file) to check if the function succeeded.

Remarks

When a UTF-8 or UTF-16 file is created, a byte order mark is written to the file unless Encoding (or A_FileEncoding if Encoding is omitted) contains
“UTF-8-RAW” or “UTF-16-RAW”.

When a file containing a UTF-8 or UTF-16 byte order mark (BOM) is opened with read access, the BOM is excluded from the output by positioning the
file pointer after it. Therefore, File.Position may report 3 or 2 immediately after opening the file.
 
BIF_DECL(BIF_FileOpen)
{
DWORD aFlags;
UINT aEncoding;

if (TokenIsPureNumeric(*aParam[1]))
{
aFlags = (DWORD) TokenToInt64(*aParam[1]);
}
else
{
LPCTSTR sflag = TokenToString(*aParam[1], aResultToken.buf);

sflag = omit_leading_whitespace(sflag); // For consistency with the loop below.

// Access mode must come first:


switch (tolower(*sflag))
{
case ' r ' :
if (tolower(sflag[1]) == ' w ' )
{
aFlags = TextStream::UPDATE;
++sflag;
}
else
aFlags = TextStream::READ;
break;
case ' w ' : aFlags = TextStream::WRITE; break;

22
case ' a ' : aFlags = TextStream::APPEND; break;
case ' h ' : aFlags = TextStream::USEHANDLE; break;
default:
// Invalid flag.
goto invalid_param;
}

// Default to not locking file, for consistency with fopen/standard AutoHotkey and because it seems
,→ best for flexibility.
aFlags |= TextStream::SHARE_ALL;

for (++sflag; *sflag; ++sflag)


{
switch (ctolower(*sflag))
{
case ' \n ' : aFlags |= TextStream::EOL_CRLF; break;
case ' \r ' : aFlags |= TextStream::EOL_ORPHAN_CR; break;
case ' ' :
case ' \t ' :
// Allow spaces and tabs for readability.
break;
case ' - ' :
for (++sflag; ; ++sflag)
{
switch (ctolower(*sflag))
{
case ' r ' : aFlags &= ˜TextStream::SHARE_READ; continue;
case ' w ' : aFlags &= ˜TextStream::SHARE_WRITE; continue;
case ' d ' : aFlags &= ˜TextStream::SHARE_DELETE; continue;
// Whitespace not allowed here. Outer loop allows ”-r -w” but not ”-r w”.
}
if (sflag[-1] == ' - ' )
// Let ”-” on its own be equivalent to ”-rwd”.
aFlags &= ˜TextStream::SHARE_ALL;
break;
}
--sflag; // Point sflag at the last char of this option. Outer loop will do ++sflag.
break;
default:
// Invalid flag.
goto invalid_param;
}
}
}

if (aParamCount > 2)
{
if (!TokenIsPureNumeric(*aParam[2]))
{
aEncoding = Line::ConvertFileEncoding(TokenToString(*aParam[2]));
if (aEncoding == -1)
{ // Invalid param.
goto invalid_param;
}
}
else aEncoding = (UINT) TokenToInt64(*aParam[2]);
}
else aEncoding = g->Encoding;

ASSERT( (˜CP_AHKNOBOM) == CP_AHKCP );

23
// aEncoding may include CP_AHKNOBOM, in which case below will not add BOM_UTFxx flag.
if (aEncoding == CP_UTF8)
aFlags |= TextStream::BOM_UTF8;
else if (aEncoding == CP_UTF16)
aFlags |= TextStream::BOM_UTF16;

LPTSTR aFileName;
if ((aFlags & TextStream::ACCESS_MODE_MASK) == TextStream::USEHANDLE)
aFileName = (LPTSTR)(HANDLE)TokenToInt64(*aParam[0]);
else
aFileName = TokenToString(*aParam[0], aResultToken.buf);

if (aResultToken.object = FileObject::Open(aFileName, aFlags, aEncoding & CP_AHKCP))


aResultToken.symbol = SYM_OBJECT;

g->LastError = GetLastError(); // Even on success, since it might provide something useful.

if (!aResultToken.object)
{
aResultToken.value_int64 = 0; // and symbol is already SYM_INTEGER.
if (g->InTryBlock)
Script::ThrowRuntimeException(_T(”Failed to open file.”), _T(”FileOpen”));
}

return;

invalid_param:
aResultToken.value_int64 = 0;
g->LastError = ERROR_INVALID_PARAMETER; // For consistency.
if (g->InTryBlock)
Script::ThrowRuntimeException(ERR_PARAM2_INVALID, _T(”FileOpen”));
}
 

24
FileReadLine

Reads the specified line from a file and stores the text in a variable.
 
FileReadLine, OutputVar, Filename, LineNum
 
Parameters

• OutputVar The name of the variable in which to store the retrieved text.

• Filename The name of the file to access, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. Windows and Unix
formats are supported; that is, the file’s lines may end in either carriage return and linefeed (‘r‘n) or just linefeed (‘n).

• LineNum Which line to read (1 is the first, 2 the second, and so on). This can be an expression.

If the specified line number is greater than the number of lines in the file, ErrorLevel is set to 1 and OutputVar is not changed. This also happens
when the specified line number is the last line in the file but that line is blank and does not end in a newline/CRLF.

Remarks

It is strongly recommended to use this command only for small files, or in cases where only a single line of text is needed. To scan and process a large
number of lines (one by one), use a file-reading loop for best performance. To read an entire file into a variable, use FileRead.

Although any leading and trailing tabs and spaces present in the line will be written to OutputVar, the linefeed character (‘n) at the end of the line
will not. Tabs and spaces can be trimmed from both ends of any variable by assigning it to itself while AutoTrim is on (the default). For example:
MyLine = %MyLine%.

Lines up to 65,534 characters long can be read. If the length of a line exceeds this, the remaining characters cannot be retrieved by this command (use
FileRead or a file-reading loop instead).
 
ResultType Line::FileReadLine(LPTSTR aFilespec, LPTSTR aLineNumber)
// Returns OK or FAIL. Will almost always return OK because if an error occurs,
// the script ' s ErrorLevel variable will be set accordingly. However, if some
// kind of unexpected and more serious error occurs, such as variable-out-of-memory,
// that will cause FAIL to be returned.
{
Var &output_var = *OUTPUT_VAR; // Fix for v1.0.45.01: Must be resolved and saved before MsgSleep() (
,→ LONG_OPERATION) because that allows some other thread to interrupt and overwrite sArgVar[].

__int64 line_number = ATOI64(aLineNumber);


if (line_number < 1)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

TextFile tfile;
if (!tfile.Open(aFilespec, DEFAULT_READ_FLAGS, g->Encoding & CP_AHKCP))
return SetErrorsOrThrow(true);

// Remember that once the first call to MsgSleep() is done, a new hotkey subroutine
// may fire and suspend what we ' re doing here. Such a subroutine might also overwrite
// the values our params, some of which may be in the deref buffer. So be sure not
// to refer to those strings once MsgSleep() has been done, below. Alternatively,
// a copy of such params can be made using our own stack space.

LONG_OPERATION_INIT

DWORD buf_length;
TCHAR buf[READ_FILE_LINE_SIZE];
for (__int64 i = 0; i < line_number; ++i)
{
if ( !(buf_length = tfile.ReadLine(buf, _countof(buf) - 1)) ) // end-of-file or error
{
g->LastError = GetLastError();
tfile.Close();
return SetErrorLevelOrThrow();

25
}
LONG_OPERATION_UPDATE
}
tfile.Close();

if (buf_length && buf[buf_length - 1] == ' \n ' ) // Remove any trailing newline for the user.
--buf_length;

if (!buf_length)
{
if (!output_var.Assign()) // Explicitly call it this way so that it won ' t free the memory.
return FAIL;
}
else
if (!output_var.Assign(buf, (VarSizeType)buf_length))
return FAIL;

return SetErrorsOrThrow(false, 0); // Indicate success.


}
 

26
FileRead

Reads a file’s contents into a variable.


 
FileRead, OutputVar, Filename
 
Parameters

• OutputVar The name of the variable in which to store the retrieved data. OutputVar will be made blank if a problem occurs such as the file being
“in use” or not existing (in which case ErrorLevel is set to 1). It will also be made blank if Filename is an empty file (in which case ErrorLevel is
set to 0).

• Filename The name of the file to read, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

Options: Zero or more of the following strings may be also be present immediately before the name of the file. Separate each option from the
next with a single space or tab. For example: *t *m5000 C:\Log Files\200601.txt.

*c: Load a ClipboardAll file or other binary data. All other options are ignored when *c is present.

*m1024: If this option is omitted, the entire file is loaded unless there is insufficient memory, in which case an error message is shown and the
thread exits (but Try can be used to avoid this). Otherwise, replace 1024 with a decimal or hexadecimal number of bytes. If the file is larger than
this, only its leading part is loaded. Note: This might result in the last line ending in a naked carriage return (‘r) rather than ‘r‘n.

*t: Replaces any/all occurrences of carriage return & linefeed (‘r‘n) with linefeed (‘n). However, this translation reduces performance and is usually
not necessary. For example, text containing ‘r‘n is already in the right format to be added to a Gui Edit control. Similarly, FileAppend detects the
presence of ‘r‘n when it opens a new file; it knows to write each ‘r‘n as-is rather than translating it to ‘r‘r‘n. Finally, the following parsing loop will
work correctly regardless of whether each line ends in ‘r‘n or just ‘n: Loop, parse, MyFileContents, ‘n, ‘r.

*Pnnn: Overrides the default encoding set by FileEncoding, where nnn must be a numeric code page identifier.

Reading Binary Data

Depending on the file, parameters and default settings, FileRead may interpret the file data as text and convert it to the native encoding used by the
script. This is likely to cause problems if the file contains binary data, except in the following cases:

• If the *C option is present, all code page and end-of-line translations are unconditionally bypassed.
• If the *Pnnn option is present and nnn corresponds to the native string encoding, no code page translation occurrs.
• If the current file encoding setting corresponds to the native string encoding, no code page translation occurrs.

Note that once the data has been read into OutputVar, only the text before the first binary zero (if any are present) will be “seen” by most AutoHotkey
commands and functions. However, the entire contents are still present and can be accessed by advanced methods such as NumGet().

Finally, FileOpen() and File.RawRead() or File.ReadNum() may be used to read binary data without first reading the entire file into memory.

Remarks

When the goal is to load all or a large part of a file into memory, FileRead performs much better than using a file-reading loop.

A file greater than 1 GB in size will cause ErrorLevel to be set to 1 and OutputVar to be made blank unless the *m option is present, in which case the
leading part of the file is loaded.

FileRead does not obey #MaxMem. If there is concern about using too much memory, check the file size beforehand with FileGetSize.

FileOpen() provides more advanced functionality than FileRead, such as reading or writing data at a specific location in the file without reading the entire
file into memory. See File Object for a list of functions.
 
ResultType Line::FileRead(LPTSTR aFilespec)
// Returns OK or FAIL. Will almost always return OK because if an error occurs,
// the script ' s ErrorLevel variable will be set accordingly. However, if some
// kind of unexpected and more serious error occurs, such as variable-out-of-memory,
// that will cause FAIL to be returned.
{
Var &output_var = *OUTPUT_VAR;
// Init output var to be blank as an additional indicator of failure (or empty file).
// Caller must check ErrorLevel to distinguish between an empty file and an error.
output_var.Assign();

const DWORD DWORD_MAX = ˜0;

27
// Set default options:
bool translate_crlf_to_lf = false;
bool is_binary_clipboard = false;
unsigned __int64 max_bytes_to_load = ULLONG_MAX; // By default, fail if the file is too large. See
,→ comments near bytes_to_read below.
UINT codepage = g->Encoding & CP_AHKCP;

// It ' s done as asterisk+option letter to permit future expansion. A plain asterisk such as used
// by the FileAppend command would create ambiguity if there was ever an effort to add other asterisk-
// prefixed options later.
LPTSTR cp;
for (;;)
{
cp = omit_leading_whitespace(aFilespec); // omit leading whitespace only temporarily in case aFilespec
,→ contains literal whitespace we want to retain.
if (*cp != ' * ' ) // No more options.
break; // Make no further changes to aFilespec.
switch (ctoupper(*++cp)) // This could move cp to the terminator if string ends in an asterisk.
{
case ' C ' : // Clipboard (binary).
is_binary_clipboard = true; // When this option is present, any others are parsed (to skip over
,→ them) but ignored as documented.
break;
case ' M ' : // Maximum number of bytes to load.
max_bytes_to_load = ATOU64(cp + 1); // Relies upon the fact that it ceases conversion upon
,→ reaching a space or tab.
// Skip over the digits of this option in case it ' s the last option.
if ( !(cp = StrChrAny(cp, _T(” \t”))) ) // Find next space or tab (there should be one if
,→ options are properly formatted).
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);
--cp; // Standardize it to make it conform to the other options, for use below.
break;
case ' P ' :
codepage = _ttoi(cp + 1);
// Skip over the digits of this option in case it ' s the last option.
if ( !(cp = StrChrAny(cp, _T(” \t”))) ) // Find next space or tab (there should be one if
,→ options are properly formatted).
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);
--cp; // Standardize it to make it conform to the other options, for use below.
break;
case ' T ' : // Text mode.
translate_crlf_to_lf = true;
break;
}
// Note: because it ' s possible for filenames to start with a space (even though Explorer itself
// won ' t let you create them that way), allow exactly one space between end of option and the
// filename itself:
aFilespec = cp; // aFilespec is now the option letter after the asterisk *or* empty string if there
,→ was none.
if (*aFilespec)
{
++aFilespec;
// Now it ' s the space or tab (if there is one) after the option letter. It seems best for
// future expansion to assume that this is a space or tab even if it ' s really the start of
// the filename. For example, in the future, multiletter options might be wanted, in which
// case allowing the omission of the space or tab between *t and the start of the filename
// would cause the following to be ambiguous:
// FileRead, OutputVar, *delimC:\File.txt
// (assuming *delim would accept an optional arg immediately following it).
// Enforcing this format also simplifies parsing in the future, if there are ever multiple options

28
,→ .
// It also conforms to the precedent/behavior of GuiControl when it accepts picture sizing options
// such as *w/h and *x/y
if (*aFilespec)
++aFilespec; // And now it ' s the start of the filename or the asterisk of the next option.
// This behavior is as documented in the help file.
}
} // for()

// It seems more flexible to allow other processes to read and write the file while we ' re reading it.
// For example, this allows the file to be appended to during the read operation, which could be
// desirable, especially it ' s a very large log file that would take a long time to read.
// MSDN: ”To enable other processes to share the object while your process has it open, use a combination
// of one or more of [FILE_SHARE_READ, FILE_SHARE_WRITE].”
HANDLE hfile = CreateFile(aFilespec, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING
, FILE_FLAG_SEQUENTIAL_SCAN, NULL); // MSDN says that FILE_FLAG_SEQUENTIAL_SCAN will often improve
,→ performance
if (hfile == INVALID_HANDLE_VALUE) // in cases like these (and it seems best even if
,→ max_bytes_to_load was specified).
return SetErrorsOrThrow(true);

unsigned __int64 bytes_to_read = GetFileSize64(hfile);


if (bytes_to_read == ULLONG_MAX) // GetFileSize64() failed.
{
g->LastError = GetLastError();
CloseHandle(hfile);
return SetErrorLevelOrThrow();
}
// In addition to imposing the limit set by the *M option, the following check prevents an error
// caused by 64 to 32-bit truncation -- that is, a file size of 0x100000001 would be truncated to
// 0x1, allowing the command to complete even though it should fail. UPDATE: This check was never
// sufficient since max_bytes_to_load could exceed DWORD_MAX on x64 (prior to v1.1.16). It ' s now
// checked separately below to try to match the documented behaviour (truncating the data only to
// the caller-specified limit).
if (bytes_to_read > max_bytes_to_load) // This is the limit set by the caller.
bytes_to_read = max_bytes_to_load;
// Fixed for v1.1.16: Show an error message if the file is larger than DWORD_MAX, otherwise the
// truncation issue described above could occur. Reading more than DWORD_MAX could be supported
// by calling ReadFile() in a loop, but it seems unlikely that a script will genuinely want to
// do this AND actually be able to allocate a 4GB+ memory block (having 4GB of total free memory
// is usually not sufficient, perhaps due to memory fragmentation).
#ifdef _WIN64
if (bytes_to_read > DWORD_MAX)
#else
// Reserve 2 bytes to avoid integer overflow below. Although any amount larger than 2GB is almost
// guaranteed to fail at the malloc stage, that might change if we ever become large address aware.
if (bytes_to_read > DWORD_MAX - sizeof(wchar_t))
#endif
return LineError(ERR_OUTOFMEM); // Using this instead of ”File too large.” to reduce code size, since
,→ this condition is very rare (and malloc succeeding would be even rarer).

if (!bytes_to_read)
{
CloseHandle(hfile);
return SetErrorsOrThrow(false, 0); // Indicate success (a zero-length file results in empty output_var
,→ ).
}

LPBYTE output_buf;
bool output_buf_is_var = is_binary_clipboard && output_var.Type() != VAR_CLIPBOARD;

29
if (output_buf_is_var)
{
// Set up the var, enlarging it if necessary. If the output_var is of type VAR_CLIPBOARD,
// this call will set up the clipboard for writing:
if (output_var.SetCapacity(VarSizeType(bytes_to_read) + (sizeof(wchar_t) - sizeof(TCHAR)), true, false
,→ ) == OK) // SetCapacity() reserves 1 TCHAR for null-terminator. Allow an extra byte on ANSI
,→ builds for wchar_t.
output_buf = (LPBYTE) output_var.Contents();
else
output_buf = NULL; // Above already displayed the error message.
}
else
{
// Either we ' re reading text and need an intermediate buffer to allow text conversion,
// or we ' re reading binary clipboard data into the Clipboard and need a temporary buffer
// to read into before calling SetClipboardData().
output_buf = (LPBYTE) malloc(size_t(bytes_to_read + sizeof(wchar_t)));
if (!output_buf)
LineError(ERR_OUTOFMEM);
}
if (!output_buf)
{
CloseHandle(hfile);
// ErrorLevel doesn ' t matter now because the current quasi-thread will be aborted.
return FAIL;
}

DWORD bytes_actually_read;
BOOL result = ReadFile(hfile, output_buf, (DWORD)bytes_to_read, &bytes_actually_read, NULL);
g->LastError = GetLastError();
CloseHandle(hfile);

// Upon result==success, bytes_actually_read is not checked against bytes_to_read because it


// shouldn ' t be different (result should have set to failure if there was a read error).
// If it ever is different, a partial read is considered a success since ReadFile() told us
// that nothing bad happened.

if (result)
{
if (!is_binary_clipboard) // text mode, do UTF-8 and UTF-16LE BOM checking
{
bool has_bom;
if (bytes_actually_read >= 3 && output_buf[0] == 0xEF && output_buf[1] == 0xBB && output_buf[2] ==
,→ 0xBF) // UTF-8 BOM
{
if (!output_var.AssignStringFromUTF8((LPCSTR)output_buf + 3, bytes_actually_read - 3))
result = FALSE;
}
else if ( (has_bom = (bytes_actually_read >= 2 && output_buf[0] == 0xFF && output_buf[1] == 0xFE))
,→ // UTF-16LE BOM
|| codepage == CP_UTF16 ) // Covers FileEncoding UTF-16 and FileEncoding UTF-16-RAW.
{
LPCWSTR text_start = (LPCWSTR)output_buf;
DWORD text_size = bytes_actually_read;
if (has_bom) {
text_start ++; // Skip BOM.
text_size -= 2; // Exclude BOM from calculations below for consistency; include only the
,→ actual data.
}
if (!output_var.AssignStringW(text_start, text_size / sizeof(wchar_t)))

30
result = FALSE;
}
else
{
#ifndef UNICODE
if (codepage == CP_ACP || codepage == GetACP())
{
// Avoid any unnecessary conversion or copying by using our malloc ' d buffer directly.
// This should be worth doing since the string must otherwise be converted to UTF-16 and
,→ back.
output_buf[bytes_actually_read] = 0; // Ensure text is terminated where indicated.
output_var.AcceptNewMem((LPTSTR)output_buf, bytes_actually_read);
output_buf = NULL; // AcceptNewMem took charge of it.
}
else
#endif
if (!output_var.AssignStringFromCodePage((LPCSTR)output_buf, bytes_actually_read, codepage))
result = FALSE;
}
if (output_buf) // i.e. it wasn ' t ”claimed” above.
free(output_buf);
output_buf = (LPBYTE) output_var.Contents();
if (translate_crlf_to_lf)
{
// Since a larger string is being replaced with a smaller, there ' s a good chance the 2 GB
// address limit will not be exceeded by StrReplace even if the file is close to the
// 1 GB limit as described above:
VarSizeType var_length = output_var.CharLength();
StrReplace((LPTSTR)output_buf, _T(”\r\n”), _T(”\n”), SCS_SENSITIVE, UINT_MAX, -1, NULL, &
,→ var_length);
output_var.SetCharLength(var_length);
}
}
else // is_binary_clipboard == true
{
if (output_var.Type() == VAR_CLIPBOARD) // Reading binary clipboard data directly back onto the
,→ clipboard.
{
result = Var::SetClipboardAll(output_buf, bytes_actually_read) == OK;
free(output_buf);
if (!result)
return FAIL;
return SetErrorLevelOrThrowBool(false);
}
// Although binary clipboard data is always null-terminated, this might be some other kind
// of binary data or actually text (but the caller passed *c to skip codepage conversion),
// so might not be terminated. Ensure the data is null-terminated:
DWORD terminate_at = bytes_actually_read;
#ifdef UNICODE
// Since the data might be interpreted as UTF-16, we need to ensure the null-terminator is
// aligned correctly, like ”xxxx 0000” and not ”xx00 00??” (where xx is valid data and ??
// is an uninitialized byte).
if (terminate_at & 1) // Odd number of bytes.
output_buf[terminate_at++] = 0; // Put an extra zero byte in and move the actual terminator
,→ right one byte.
#endif
*LPTSTR(output_buf + terminate_at) = 0;
// Update the output var ' s length. In this case the script wants the actual data size rather
// than the ”usable” length. v1.1.16: Although it might change the behaviour of some scripts,
// it seems safer to use the ”rounded up” size than an odd byte count, which would cause the

31
// last byte to be truncated due to integer division in Var::CharLength().
output_var.ByteLength() = terminate_at;
}
}
else
{
// ReadFile() failed. Since MSDN does not document what is in the buffer at this stage,
// or whether what ' s in it is even null-terminated, or even whether bytes_to_read contains
// a valid value, it seems best to abort the entire operation rather than try to put partial
// file contents into output_var. ErrorLevel will indicate the failure.
// Since ReadFile() failed, to avoid complications or side-effects in functions such as Var::Close(),
// avoid storing a potentially non-terminated string in the variable.
*((LPTSTR)output_buf) = ' \0 ' ; // Assign() at this point would fail for the clipboard since it ' s
,→ already open for writing.
output_var.ByteLength() = 0;
if (!output_buf_is_var)
free(output_buf);
}

if (!output_var.Close(is_binary_clipboard)) // Must be called after Assign(NULL, ...) or when Contents()


,→ has been altered because it updates the variable ' s attributes and properly handles VAR_CLIPBOARD.
return FAIL;

return SetErrorLevelOrThrowBool(!result);
}
 

32
Loop (read file contents)

Retrieves the lines in a text file, one at a time (performs better than FileReadLine).
 
Loop, Read, InputFile [, OutputFile]
 
Parameters

• Read This parameter must be the word READ.

• InputFile The name of the text file whose contents will be read by the loop, which is assumed to be in %A_WorkingDir% if an absolute path isn’t
specified. Windows and Unix formats are supported; that is, the file’s lines may end in either carriage return and linefeed (‘r‘n) or just linefeed (‘n).

• OutputFile (Optional) The name of the file to be kept open for the duration of the loop, which is assumed to be in %A_WorkingDir% if an absolute
path isn’t specified.

Within the loop’s body, use the FileAppend command with only one parameter (the text to be written) to append to this special file. Appending to
a file in this manner performs better than using FileAppend in its 2-parameter mode because the file does not need to be closed and re-opened
for each operation. Remember to include a linefeed (‘n) after the text, if desired.

The file is not opened if nothing is ever written to it. This happens if the Loop performs zero iterations or if it never uses the FileAppend command.

End of line (EOL) translation: To disable EOL translation, prepend an asterisk to the filename. This causes each linefeed character (‘n) to be
written as a single linefeed (LF) rather than the Windows standard of CR+LF. For example: *C:\My Unix File.txt. Even without the asterisk, EOL
translation is disabled automatically if the Loop’s first use of FileAppend writes any carriage return and linefeed pairs (‘r‘n).

Standard Output (stdout): Specifying an asterisk (*) for OutputFile sends any text written by FileAppend to standard output (stdout). Although
such output can be redirected to a file, piped to another EXE, or captured by fancy text editors, it will not appear at the command prompt it was
launched from. See FileAppend for more details.

Escaped Commas: Unlike the last parameter of most other commands, commas in OutputFile must be escaped (‘,).

Remarks

A file-reading loop is useful when you want to operate on each line contained in a text file, one at a time. It performs better than using FileReadLine
because: 1) the file can be kept open for the entire operation; and 2) the file does not have to be re-scanned each time to find the requested line number.

The built-in variable A_LoopReadLine exists within any file-reading loop. It contains the contents of the current line excluding the carriage return and
linefeed (‘r‘n) that marks the end of the line. If an inner file-reading loop is enclosed by an outer file-reading loop, the innermost loop’s file-line will take
precedence.

Lines up to 65,534 characters long can be read. If the length of a line exceeds this, its remaining characters will be read during the next loop iteration.

StringSplit or a parsing loop is often used inside a file-reading loop to parse the contents of each line retrieved from InputFile. For example, if InputFile’s
lines are each a series of tab-delimited fields, those fields can individually retrieved as in this example:
 
Loop, read, C:\Database Export.txt
{
Loop, parse, A_LoopReadLine, %A_Tab%
{
MsgBox, Field number %A_Index% is %A_LoopField%.
}
}
 
To load an entire file into variable, use FileRead because it performs much better than a loop (especially for large files).
 
// ..........
case (size_t)ATTR_LOOP_READ_FILE:
{
TextFile tfile;
if (*ARG2 && tfile.Open(ARG2, DEFAULT_READ_FLAGS, g.Encoding & CP_AHKCP)) // v1.0.47: Added check for ””
,→ to avoid debug-assertion failure while in debug mode (maybe it ' s bad to to open file ”” in release
,→ mode too).
{
result = line->PerformLoopReadFile(aResultToken, continue_main_loop, jump_to_line, until
, &tfile, ARG3);
tfile.Close();

33
}
else
// The open of a the input file failed. So just set result to OK since setting the
// ErrorLevel isn ' t supported with loops (since that seems like it would be an overuse
// of ErrorLevel, perhaps changing its value too often when the user would want
// it saved -- in any case, changing that now might break existing scripts).
result = OK;
}
break;
// ..........

ResultType Line::PerformLoopReadFile(ExprTokenType *aResultToken, bool &aContinueMainLoop, Line *&aJumpToLine,


,→ Line *aUntil
, TextStream *aReadFile, LPTSTR aWriteFileName)
{
LoopReadFileStruct loop_info(aReadFile, aWriteFileName);
size_t line_length;
ResultType result;
Line *jump_to_line;
global_struct &g = *::g; // Primarily for performance in this case.

for (;; ++g.mLoopIteration)


{
if ( !(line_length = loop_info.mReadFile->ReadLine(loop_info.mCurrentLine, _countof(loop_info.
,→ mCurrentLine) - 1)) ) // -1 to ensure there ' s room for a null-terminator.
{
// We want to return OK except in some specific cases handled below (see ”break”).
result = OK;
break;
}
if (loop_info.mCurrentLine[line_length - 1] == ' \n ' ) // Remove newlines like FileReadLine does.
--line_length;
loop_info.mCurrentLine[line_length] = ' \0 ' ;
g.mLoopReadFile = &loop_info;
if (mNextLine->mActionType == ACT_BLOCK_BEGIN) // See PerformLoop() for comments about this section.
do
result = mNextLine->mNextLine->ExecUntil(UNTIL_BLOCK_END, aResultToken, &jump_to_line);
while (jump_to_line == mNextLine);
else
result = mNextLine->ExecUntil(ONLY_ONE_LINE, aResultToken, &jump_to_line);
if (jump_to_line && !(result == LOOP_CONTINUE && jump_to_line == this)) // See comments in PerformLoop
,→ () about this section.
{
if (jump_to_line == this)
aContinueMainLoop = true;
else
aJumpToLine = jump_to_line; // Signal our caller to handle this jump.
break;
}
if (result != OK && result != LOOP_CONTINUE) // i.e. result == LOOP_BREAK || result == EARLY_RETURN ||
,→ result == EARLY_EXIT || result == FAIL)
break;
if (aUntil && aUntil->EvaluateLoopUntil(result))
break;
}

if (loop_info.mWriteFile)
{
loop_info.mWriteFile->Close();
delete loop_info.mWriteFile;

34
}

return result;
}
 

35
Path Utility Functions
 
void Line::Util_GetFullPathName(LPCTSTR szIn, LPTSTR szOut)
// Returns the full pathname and strips any trailing \s. Assumes output is _MAX_PATH in size.
{
LPTSTR szFilePart;
GetFullPathName(szIn, _MAX_PATH, szOut, &szFilePart);
strip_trailing_backslash(szOut);
}

void Line::Util_ExpandFilenameWildcardPart(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)


{
LPTSTR lpTemp;
int i, j, k;

// Replace first * in the dest with the src, remove any other *
i = 0; j = 0; k = 0;
lpTemp = (LPTSTR)_tcschr(szDest, ' * ' );
if (lpTemp != NULL)
{
// Contains at least one *, copy up to this point
while(szDest[i] != ' * ' )
szExpandedDest[j++] = szDest[i++];
// Skip the * and replace in the dest with the srcext
while(szSource[k] != ' \0 ' )
szExpandedDest[j++] = szSource[k++];
// Skip any other *
i++;
while(szDest[i] != ' \0 ' )
{
if (szDest[i] == ' * ' )
i++;
else
szExpandedDest[j++] = szDest[i++];
}
szExpandedDest[j] = ' \0 ' ;
}
else
{
// No wildcard, straight copy of destext
_tcscpy(szExpandedDest, szDest);
}
}
 

36
Drive

Ejects/retracts the tray in a CD or DVD drive, or sets a drive’s volume label.


 
Drive, Sub-command [, Drive , Value]
 
The sub-command, drive, and value parameters are dependent upon each other and their usage is described below.

Label, Drive [, NewLabel]: Changes Drive’s volume label to be NewLabel (if NewLabel is omitted, the drive will have no label). Drive is the drive letter
followed by a colon and an optional backslash (might also work on UNCs and mapped drives). For example: Drive, Label, C:, Seagate200.

To retrieve the current label, follow this example: DriveGet, OutputVar, Label, C:.

Lock, Drive: Prevents a drive’s eject feature from working. For example: Drive, Lock, D:. Most drives cannot be “locked open”. However, locking
the drive while it is open will probably result in it becoming locked the moment it is closed. This command has no effect on drives that do not support
locking (such as most read-only drives). If a drive is locked by a script and that script exits, the drive will stay locked until another script or program
unlocks it, or the system is restarted. If the specified drive does not exist or does not support the locking feature, ErrorLevel is set to 1. Otherwise, it is
set to 0.

Unlock, Drive: Reverses the above. Unlock needs to be executed multiple times if the drive was locked multiple times (at least for some drives). For
example, if Drive, Lock, D: was executed three times, three executions of Drive, Unlock, D: might be needed to unlock it. Because of this and the fact
that there is no way to determine whether a drive is currently locked, it is often useful to keep track of its lock-state in a variable.

Eject [, Drive, 1]: Ejects or retracts the tray of a CD or DVD drive (to eject other types of media or devices, see the DllCall example at the bottom of this
page).

If Drive is omitted, the default CD/DVD drive will be used. To eject the tray, omit the last parameter. To retract/close the tray, specify 1 for the last
parameter; for example: Drive, Eject, D:, 1.

Drive Eject waits for the ejection or retraction to complete before allowing the script to continue. If the tray is already in the correct state (open or closed),
ErrorLevel is set to 0 (i.e. “no error”).

Drive Eject will probably not work on a network drive or non-CD/DVD drive. If it fails in such cases or for any other reason, ErrorLevel is set to 1.

It may be possible to detect the previous tray state by measuring the time the command takes to complete. For example, the following hotkey toggles
the tray to the opposite state (open or closed):
 
#c::
Drive, Eject
; If the command completed quickly, the tray was probably already ejected.
; In that case, retract it:
if A_TimeSinceThisHotkey < 1000 ; Adjust this time if needed.
Drive, Eject,, 1
return
 
To determine the media status of a CD or DVD drive (playing, stopped, open, etc.), see DriveGet.
 
ResultType Line::Drive(LPTSTR aCmd, LPTSTR aValue, LPTSTR aValue2) // aValue not aValue1, for use with a
,→ shared macro.
{
DriveCmds drive_cmd = ConvertDriveCmd(aCmd);

TCHAR path[MAX_PATH + 1]; // +1 to allow room for trailing backslash in case it needs to be added.
size_t path_length;

// Notes about the below macro:


// - It adds a backslash to the contents of the path variable because certain API calls or OS versions
// might require it.
// - It is used by both Drive() and DriveGet().
// - Leave space for the backslash in case its needed.
#define DRIVE_SET_PATH \
tcslcpy(path, aValue, _countof(path) - 1);\
path_length = _tcslen(path);\
if (path_length && path[path_length - 1] != ' \\ ' )\
path[path_length++] = ' \\ ' ;

37
switch(drive_cmd)
{
case DRIVE_CMD_INVALID:
// Since command names are validated at load-time, this only happens if the command name
// was contained in a variable reference. Since that is very rare, just set ErrorLevel
// and return:
return SetErrorLevelOrThrow();

case DRIVE_CMD_LOCK:
case DRIVE_CMD_UNLOCK:
return SetErrorLevelOrThrowBool(!DriveLock(*aValue, drive_cmd == DRIVE_CMD_LOCK));

case DRIVE_CMD_EJECT:
// Don ' t do DRIVE_SET_PATH in this case since trailing backslash might prevent it from
// working on some OSes.
// It seems best not to do the below check since:
// 1) aValue usually lacks a trailing backslash so that it will work correctly with ”open c: type
,→ cdaudio”.
// That lack might prevent DriveGetType() from working on some OSes.
// 2) It ' s conceivable that tray eject/retract might work on certain types of drives even though
// they aren ' t of type DRIVE_CDROM.
// 3) One or both of the calls to mciSendString() will simply fail if the drive isn ' t of the right
,→ type.
//if (GetDriveType(aValue) != DRIVE_CDROM) // Testing reveals that the below method does not work on
,→ Network CD/DVD drives.
// return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
TCHAR mci_string[256];
MCIERROR error;
// Note: The following comment is obsolete because research of MSDN indicates that there is no way
// not to wait when the tray must be physically opened or closed, at least on Windows XP. Omitting
// the word ”wait” from both ”close cd wait” and ”set cd door open/closed wait” does not help, nor
// does replacing wait with the word notify in ”set cdaudio door open/closed wait”.
// The word ”wait” is always specified with these operations to ensure consistent behavior across
// all OSes (on the off-chance that the absence of ”wait” really avoids waiting on Win9x or future
// OSes, or perhaps under certain conditions or for certain types of drives). See above comment
// for details.
if (!*aValue) // When drive is omitted, operate upon default CD/DVD drive.
{
sntprintf(mci_string, _countof(mci_string), _T(”set cdaudio door %s wait”), ATOI(aValue2) == 1 ?
,→ _T(”closed”) : _T(”open”));
error = mciSendString(mci_string, NULL, 0, NULL); // Open or close the tray.
return SetErrorLevelOrThrowBool(error);
}
sntprintf(mci_string, _countof(mci_string), _T(”open %s type cdaudio alias cd wait shareable”), aValue
,→ );
if (mciSendString(mci_string, NULL, 0, NULL)) // Error.
return SetErrorLevelOrThrow();
sntprintf(mci_string, _countof(mci_string), _T(”set cd door %s wait”), ATOI(aValue2) == 1 ? _T(”closed
,→ ”) : _T(”open”));
error = mciSendString(mci_string, NULL, 0, NULL); // Open or close the tray.
mciSendString(_T(”close cd wait”), NULL, 0, NULL);
return SetErrorLevelOrThrowBool(error);

case DRIVE_CMD_LABEL: // Note that it is possible and allowed for the new label to be blank.
DRIVE_SET_PATH
return SetErrorLevelOrThrowBool(!SetVolumeLabel(path, aValue2));

} // switch()

38
return FAIL; // Should never be executed. Helps catch bugs.
}
 

39
DriveGet

Retrieves various types of information about the computer’s drive(s).


 
DriveGet, OutputVar, Cmd [, Value]
 
Parameters

• OutputVar The name of the variable in which to store the result of Cmd.
• Cmd, Value See list below.

Cmd, Value

The Cmd and Value parameters are dependent upon each other and their usage is described below. If a problem is encountered OutputVar is made
blank and ErrorLevel is set to 1.

List [, Type]: Sets OutputVar to be a string of letters, one character for each drive letter in the system. For example: ACDEZ. If Type is omitted, all
drive types are retrieved. Otherwise, Type should be one of the following words to retrieve only a specific type of drive: CDROM, REMOVABLE, FIXED,
NETWORK, RAMDISK, UNKNOWN.

Capacity (or Cap), Path: Retrieves the total capacity of Path (e.g. C:\) in megabytes. Use DriveSpaceFree to determine the free space.

Filesystem (or FS), Drive: Retrieves the type of Drive’s file system, where Drive is the drive letter followed by a colon and an optional backslash, or
a UNC name such \\server1\share1. OutputVar will be set to one of the following words: FAT, FAT32, NTFS, CDFS (typically indicates a CD), UDF
(typically indicates a DVD). OutputVar will be made blank and ErrorLevel set to 1 if the drive does not contain formatted media.

Label, Drive: Retrieves Drive’s volume label, where Drive is the drive letter followed by a colon and an optional backslash, or a UNC name such
\\server1\share1. To change the label, follow this example: Drive, Label, C:, MyLabel.

Serial, Drive: Retrieves Drive’s volume serial number expressed as decimal integer, where Drive is the drive letter followed by a colon and an optional
backslash, or a UNC name such \\server1\share1. See SetFormat for how to convert it to hexadecimal.

Type, Path: Retrieves Path’s drive type, which is one of the following words: Unknown, Removable, Fixed, Network, CDROM, RAMDisk.

Status, Path: Retrieves Path’s status, which is one of the following words: Unknown (might indicate unformatted/RAW), Ready, NotReady (typical for
removable drives that don’t contain media), Invalid (Path does not exist or is a network drive that is presently inaccessible, etc.)

StatusCD [, Drive]: Retrieves the media status of a CD or DVD drive, where Drive is the drive letter followed by a colon (if Drive is omitted, the default
CD/DVD drive will be used). OutputVar is made blank if the status cannot be determined. Otherwise, it is set to one of the following strings:

• not ready: The drive is not ready to be accessed, perhaps due to being engaged in a write operation. Known limitation: “not ready” also occurs
when the drive contains a DVD rather than a CD.
• open: The drive contains no disc, or the tray is ejected.
• playing: The drive is playing a disc.
• paused: The previously playing audio or video is now paused.
• seeking: The drive is seeking.
• stopped: The drive contains a CD but is not currently accessing it.

This command will probably not work on a network drive or non-CD/DVD drive; if it fails in such cases or for any other reason, OutputVar is made blank
and ErrorLevel is set to 1.

If the tray was recently closed, there may be a delay before the command completes.

To eject or retract the tray, see the Drive command.


 
ResultType Line::DriveGet(LPTSTR aCmd, LPTSTR aValue)
{
DriveGetCmds drive_get_cmd = ConvertDriveGetCmd(aCmd);
if (drive_get_cmd == DRIVEGET_CMD_CAPACITY)
return DriveSpace(aValue, false);

TCHAR path[MAX_PATH + 1]; // +1 to allow room for trailing backslash in case it needs to be added.
size_t path_length;

if (drive_get_cmd == DRIVEGET_CMD_SETLABEL) // The is retained for backward compatibility even though the
,→ Drive cmd is normally used.
{
DRIVE_SET_PATH

40
LPTSTR new_label = omit_leading_whitespace(aCmd + 9); // Example: SetLabel:MyLabel
return g_ErrorLevel->Assign(SetVolumeLabel(path, new_label) ? ERRORLEVEL_NONE : ERRORLEVEL_ERROR);
}

Var &output_var = *OUTPUT_VAR;

output_var.Assign(); // Init to empty string.

switch(drive_get_cmd)
{

case DRIVEGET_CMD_INVALID:
// Since command names are validated at load-time, this only happens if the command name
// was contained in a variable reference. Since that is very rare, just set ErrorLevel
// and return:
goto error;

case DRIVEGET_CMD_LIST:
{
UINT drive_type;
#define ALL_DRIVE_TYPES 256
if (!*aValue) drive_type = ALL_DRIVE_TYPES;
else if (!_tcsicmp(aValue, _T(”CDRom”))) drive_type = DRIVE_CDROM;
else if (!_tcsicmp(aValue, _T(”Removable”))) drive_type = DRIVE_REMOVABLE;
else if (!_tcsicmp(aValue, _T(”Fixed”))) drive_type = DRIVE_FIXED;
else if (!_tcsicmp(aValue, _T(”Network”))) drive_type = DRIVE_REMOTE;
else if (!_tcsicmp(aValue, _T(”Ramdisk”))) drive_type = DRIVE_RAMDISK;
else if (!_tcsicmp(aValue, _T(”Unknown”))) drive_type = DRIVE_UNKNOWN;
else
goto error;

TCHAR found_drives[32]; // Need room for all 26 possible drive letters.


int found_drives_count;
UCHAR letter;
TCHAR buf[128], *buf_ptr;

for (found_drives_count = 0, letter = ' A ' ; letter <= ' Z ' ; ++letter)
{
buf_ptr = buf;
*buf_ptr++ = letter;
*buf_ptr++ = ' : ' ;
*buf_ptr++ = ' \\ ' ;
*buf_ptr = ' \0 ' ;
UINT this_type = GetDriveType(buf);
if (this_type == drive_type || (drive_type == ALL_DRIVE_TYPES && this_type != DRIVE_NO_ROOT_DIR))
found_drives[found_drives_count++] = letter; // Store just the drive letters.
}
found_drives[found_drives_count] = ' \0 ' ; // Terminate the string of found drive letters.
output_var.Assign(found_drives);
if (!*found_drives)
goto error; // Seems best to flag zero drives in the system as ErrorLevel of ”1”.
break;
}

case DRIVEGET_CMD_FILESYSTEM:
case DRIVEGET_CMD_LABEL:
case DRIVEGET_CMD_SERIAL:
{
TCHAR volume_name[256];
TCHAR file_system[256];

41
DRIVE_SET_PATH
DWORD serial_number, max_component_length, file_system_flags;
if (!GetVolumeInformation(path, volume_name, _countof(volume_name) - 1, &serial_number, &
,→ max_component_length
, &file_system_flags, file_system, _countof(file_system) - 1))
goto error;
switch(drive_get_cmd)
{
case DRIVEGET_CMD_FILESYSTEM: output_var.Assign(file_system); break;
case DRIVEGET_CMD_LABEL: output_var.Assign(volume_name); break;
case DRIVEGET_CMD_SERIAL: output_var.Assign(serial_number); break;
}
break;
}

case DRIVEGET_CMD_TYPE:
{
DRIVE_SET_PATH
switch (GetDriveType(path))
{
case DRIVE_UNKNOWN: output_var.Assign(_T(”Unknown”)); break;
case DRIVE_REMOVABLE: output_var.Assign(_T(”Removable”)); break;
case DRIVE_FIXED: output_var.Assign(_T(”Fixed”)); break;
case DRIVE_REMOTE: output_var.Assign(_T(”Network”)); break;
case DRIVE_CDROM: output_var.Assign(_T(”CDROM”)); break;
case DRIVE_RAMDISK: output_var.Assign(_T(”RAMDisk”)); break;
default: // DRIVE_NO_ROOT_DIR
goto error;
}
break;
}

case DRIVEGET_CMD_STATUS:
{
DRIVE_SET_PATH
DWORD sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters;
switch (GetDiskFreeSpace(path, &sectors_per_cluster, &bytes_per_sector, &free_clusters, &
,→ total_clusters)
? ERROR_SUCCESS : GetLastError())
{
case ERROR_SUCCESS: output_var.Assign(_T(”Ready”)); break;
case ERROR_PATH_NOT_FOUND: output_var.Assign(_T(”Invalid”)); break;
case ERROR_NOT_READY: output_var.Assign(_T(”NotReady”)); break;
case ERROR_WRITE_PROTECT: output_var.Assign(_T(”ReadOnly”)); break;
default: output_var.Assign(_T(”Unknown”));
}
break;
}

case DRIVEGET_CMD_STATUSCD:
// Don ' t do DRIVE_SET_PATH in this case since trailing backslash might prevent it from
// working on some OSes.
// It seems best not to do the below check since:
// 1) aValue usually lacks a trailing backslash so that it will work correctly with ”open c: type
,→ cdaudio”.
// That lack might prevent DriveGetType() from working on some OSes.
// 2) It ' s conceivable that tray eject/retract might work on certain types of drives even though
// they aren ' t of type DRIVE_CDROM.
// 3) One or both of the calls to mciSendString() will simply fail if the drive isn ' t of the right
,→ type.

42
//if (GetDriveType(aValue) != DRIVE_CDROM) // Testing reveals that the below method does not work on
,→ Network CD/DVD drives.
// return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
TCHAR mci_string[256], status[128];
// Note that there is apparently no way to determine via mciSendString() whether the tray is ejected
// or not, since ”open” is returned even when the tray is closed but there is no media.
if (!*aValue) // When drive is omitted, operate upon default CD/DVD drive.
{
if (mciSendString(_T(”status cdaudio mode”), status, _countof(status), NULL)) // Error.
goto error;
}
else // Operate upon a specific drive letter.
{
sntprintf(mci_string, _countof(mci_string), _T(”open %s type cdaudio alias cd wait shareable”),
,→ aValue);
if (mciSendString(mci_string, NULL, 0, NULL)) // Error.
goto error;
MCIERROR error = mciSendString(_T(”status cd mode”), status, _countof(status), NULL);
mciSendString(_T(”close cd wait”), NULL, 0, NULL);
if (error)
goto error;
}
// Otherwise, success:
output_var.Assign(status);
break;

} // switch()

// Note that ControlDelay is not done for the Get type commands, because it seems unnecessary.
return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.

error:
return SetErrorLevelOrThrow();
}
 

43
DriveSpaceFree

Retrieves the free disk space of a drive, in Megabytes.


 
DriveSpaceFree, OutputVar, Path
 
Parameters

• OutputVar The variable in which to store the result, which is rounded down to the nearest whole number.
• path Path of drive to receive information from. Since NTFS supports mounted volumes and directory junctions, different amounts of free space
might be available in different folders of the same “drive” in some cases.

Remarks

OutputVar is set to the amount of free disk space in Megabytes (rounded down to the nearest Megabyte).
 
ResultType Line::DriveSpace(LPTSTR aPath, bool aGetFreeSpace)
// Because of NTFS ' s ability to mount volumes into a directory, a path might not necessarily
// have the same amount of free space as its root drive. However, I ' m not sure if this
// method here actually takes that into account.
{
OUTPUT_VAR->Assign(); // Init to empty string regardless of whether we succeed here.

if (!aPath || !*aPath) goto error; // Below relies on this check.

TCHAR buf[MAX_PATH + 1]; // +1 to allow appending of backslash.


tcslcpy(buf, aPath, _countof(buf));
size_t length = _tcslen(buf);
if (buf[length - 1] != ' \\ ' ) // Trailing backslash is present, which some of the API calls below don ' t
,→ like.
{
if (length + 1 >= _countof(buf)) // No room to fix it.
goto error;
buf[length++] = ' \\ ' ;
buf[length] = ' \0 ' ;
}

// The program won ' t launch at all on Win95a (original Win95) unless the function address is resolved
// at runtime:
typedef BOOL (WINAPI *GetDiskFreeSpaceExType)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
static GetDiskFreeSpaceExType MyGetDiskFreeSpaceEx =
(GetDiskFreeSpaceExType)GetProcAddress(GetModuleHandle(_T(”kernel32”)), ”GetDiskFreeSpaceEx”
,→ WINAPI_SUFFIX);

// MSDN: ”The GetDiskFreeSpaceEx function returns correct values for all volumes, including those
// that are greater than 2 gigabytes.”
__int64 free_space;
if (MyGetDiskFreeSpaceEx) // Function is available (unpatched Win95 and WinNT might not have it).
{
ULARGE_INTEGER total, free, used;
if (!MyGetDiskFreeSpaceEx(buf, &free, &total, &used))
goto error;
// Casting this way allows sizes of up to 2,097,152 gigabytes:
free_space = (__int64)((unsigned __int64)(aGetFreeSpace ? free.QuadPart : total.QuadPart)
/ (1024*1024));
}
else // For unpatched versions of Win95/NT4, fall back to compatibility mode.
{
DWORD sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters;
if (!GetDiskFreeSpace(buf, &sectors_per_cluster, &bytes_per_sector, &free_clusters, &total_clusters))
goto error;
free_space = (__int64)((unsigned __int64)((aGetFreeSpace ? free_clusters : total_clusters)

44
* sectors_per_cluster * bytes_per_sector) / (1024*1024));
}

g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.


return OUTPUT_VAR->Assign(free_space);

error:
return SetErrorLevelOrThrow();
}
 

45
FileCopy

Copies one or more files.


 
FileCopy, SourcePattern, DestPattern [, Flag]
 
Parameters

• SourcePattern The name of a single file or folder, or a wildcard pattern such as C:\Temp\*.tmp. SourcePattern is assumed to be in
%A_WorkingDir% if an absolute path isn’t specified.

• DestPattern The name or pattern of the destination, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. To perform a
simple copy – retaining the existing file name(s) – specify only the folder name as shown in these functionally identical examples:

FileCopy, C:\*.txt, C:\My Folder

FileCopy, C:\*.txt, C:\My Folder\*.*

• Flag (optional) this flag determines whether to overwrite files if they already exist:

– 0 = (default) do not overwrite existing files


– 1 = overwrite existing files

This parameter can be an expression, even one that evalutes to true or false (since true and false are stored internally as 1 and 0).

Remarks

FileCopy copies files only. To copy a single folder (including its subfolders), use FileCopyDir.

The operation will continue even if error(s) are encountered.


 
///////////////////////////////////////////////////////////////////////////////
// Util_CopyFile()
// (moves files too)
// Returns the number of files that could not be copied or moved due to error.
///////////////////////////////////////////////////////////////////////////////
int Line::Util_CopyFile(LPCTSTR szInputSource, LPCTSTR szInputDest, bool bOverwrite, bool bMove, DWORD &
,→ aLastError)
{
TCHAR szSource[_MAX_PATH+1];
TCHAR szDest[_MAX_PATH+1];
TCHAR szExpandedDest[MAX_PATH+1];
TCHAR szTempPath[_MAX_PATH+1];
TCHAR szDrive[_MAX_PATH+1];
TCHAR szDir[_MAX_PATH+1];
TCHAR szFile[_MAX_PATH+1];
TCHAR szExt[_MAX_PATH+1];

// Get local version of our source/dest with full path names, strip trailing \s
Util_GetFullPathName(szInputSource, szSource);
Util_GetFullPathName(szInputDest, szDest);

// If the source or dest is a directory then add *.* to the end


if (Util_IsDir(szSource))
_tcscat(szSource, _T(”\\*.*”));
if (Util_IsDir(szDest))
_tcscat(szDest, _T(”\\*.*”));

WIN32_FIND_DATA findData;
HANDLE hSearch = FindFirstFile(szSource, &findData);
if (hSearch == INVALID_HANDLE_VALUE)
{
aLastError = GetLastError(); // Set even in this case since FindFirstFile can fail due to actual
,→ errors, such as an invalid path.
return 0; // Indicate no failures.

46
}
aLastError = 0; // Set default. Overridden only when a failure occurs.

// Otherwise, loop through all the matching files.


// Split source into file and extension (we need this info in the loop below to reconstruct the path)
_tsplitpath(szSource, szDrive, szDir, szFile, szExt);
// Note we now rely on the SOURCE being the contents of szDrive, szDir, szFile, etc.
size_t szTempPath_length = sntprintf(szTempPath, _countof(szTempPath), _T(”%s%s”), szDrive, szDir);
LPTSTR append_pos = szTempPath + szTempPath_length;
size_t space_remaining = _countof(szTempPath) - szTempPath_length - 1;

int failure_count = 0;
LONG_OPERATION_INIT

do
{
// Since other script threads can interrupt during LONG_OPERATION_UPDATE, it ' s important that
// this function and those that call it not refer to sArgDeref[] and sArgVar[] anytime after an
// interruption becomes possible. This is because an interrupting thread usually changes the
// values to something inappropriate for this thread.
LONG_OPERATION_UPDATE

// Make sure the returned handle is a file and not a directory before we
// try and do copy type things on it!
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // dwFileAttributes should never be invalid
,→ (0xFFFFFFFF) in this case.
continue;

if (_tcslen(findData.cFileName) > space_remaining) // v1.0.45.03: Basic check in case of files whose


,→ full spec is over 260 characters long.
{
aLastError = ERROR_BUFFER_OVERFLOW; // MSDN: ”The file name is too long.”
++failure_count;
continue;
}
_tcscpy(append_pos, findData.cFileName); // Indirectly populate szTempPath. Above has ensured this won
,→ ' t overflow.

// Expand the destination based on this found file


Util_ExpandFilenameWildcard(findData.cFileName, szDest, szExpandedDest);

// Fixed for v1.0.36.01: This section has been revised to avoid unnecessary calls; but more
// importantly, it now avoids the deletion and complete loss of a file when it is copied or
// moved onto itself. That used to happen because any existing destination file used to be
// deleted prior to attempting the move/copy.
if (bMove) // Move vs. copy mode.
{
// Note that MoveFile() is capable of moving a file to a different volume, regardless of
// operating system version. That ' s enough for what we need because this function never
// moves directories, only files.

// The following call will report success if source and dest are the same file, even if
// source is something like ”..\Folder\Filename.txt” and dest is something like
// ”C:\Folder\Filename.txt” (or if source is an 8.3 filename and dest is the long name
// of the same file). This is good because it avoids the need to devise code
// to determine whether two different path names refer to the same physical file
// (note that GetFullPathName() has shown itself to be inadequate for this purpose due
// to problems with short vs. long names, UNC vs. mapped drive, and possibly NTFS hard
// links (aliases) that might all cause two different filenames to point to the same
// physical file on disk (hopefully MoveFile handles all of these correctly by indicating

47
// success [below] when a file is moved onto itself, though it has only been tested for
// basic cases of relative vs. absolute path).
if (!MoveFile(szTempPath, szExpandedDest))
{
// If overwrite mode was not specified by the caller, or it was but the existing
// destination file cannot be deleted (perhaps because it is a folder rather than
// a file), or it can be deleted but the source cannot be moved, indicate a failure.
// But by design, continue the operation. The following relies heavily on
// short-circuit boolean evaluation order:
if ( !(bOverwrite && DeleteFile(szExpandedDest) && MoveFile(szTempPath, szExpandedDest)) )
{
aLastError = GetLastError();
++failure_count; // At this stage, any of the above 3 being false is cause for failure.
}
//else everything succeeded, so nothing extra needs to be done. In either case,
// continue on to the next file.
}
}
else // The mode is ”Copy” vs. ”Move”
if (!CopyFile(szTempPath, szExpandedDest, !bOverwrite)) // Force it to fail if bOverwrite==false.
{
aLastError = GetLastError();
++failure_count;
}
} while (FindNextFile(hSearch, &findData));

FindClose(hSearch);
return failure_count;
}
 

48
FileCopyDir

Copies a folder along with all its sub-folders and files (similar to xcopy).
 
FileCopyDir, Source, Dest [, Flag]
 
Parameters

• Source Name of the source directory (with no trailing backslash), which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.
For example: C:\My Folder

• Dest Name of the destination directory (with no trailing baskslash), which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.
For example: C:\Copy of My Folder

• Flag (optional) this flag determines whether to overwrite files if they already exist:

– 0 (default): Do not overwrite existing files. The operation will fail and have no effect if Dest already exists as a file or directory.
– 1: Overwrite existing files. However, any files or subfolders inside Dest that do not have a counterpart in Source will not be deleted.

This parameter can be an expression, even one that evalutes to true or false (since true and false are stored internally as 1 and 0).

Remarks

If the destination directory structure doesn’t exist it will be created if possible.

Since the operation will recursively copy a folder along with all its subfolders and files, the result of copying a folder to a destination somewhere inside
itself is undefined. To work around this, first copy it to a destination outside itself, then use FileMoveDir to move that copy to the desired location.

FileCopyDir copies a single folder.


 
bool Line::Util_CopyDir(LPCTSTR szInputSource, LPCTSTR szInputDest, bool bOverwrite)
{
// Get the fullpathnames and strip trailing \s
TCHAR szSource[_MAX_PATH+2];
TCHAR szDest[_MAX_PATH+2];
Util_GetFullPathName(szInputSource, szSource);
Util_GetFullPathName(szInputDest, szDest);

// Ensure source is a directory


if (Util_IsDir(szSource) == false)
return false; // Nope

// Does the destination dir exist?


if (Util_IsDir(szDest))
{
if (bOverwrite == false)
return false;
}
else // Although dest doesn ' t exist as a dir, it might be a file, which is covered below too.
{
// We must create the top level directory
if (!Util_CreateDir(szDest)) // Failure is expected to happen if szDest is an existing *file*, since a
,→ dir should never be allowed to overwrite a file (to avoid accidental loss of data).
return false;
}

// To work under old versions AND new version of shell32.dll the source must be specified
// as ”dir\*.*” and the destination directory must already exist... Goddamn Microsoft and their APIs...
_tcscat(szSource, _T(”\\*.*”));

// We must also make source\dest double nulled strings for the SHFileOp API
szSource[_tcslen(szSource)+1] = ' \0 ' ;
szDest[_tcslen(szDest)+1] = ' \0 ' ;

// Setup the struct

49
SHFILEOPSTRUCT FileOp = {0};
FileOp.pFrom = szSource;
FileOp.pTo = szDest;
FileOp.wFunc = FO_COPY;
FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI; // FOF_NO_UI (”
,→ perform the operation with no user input”) is not present for in case it would break compatibility
,→ somehow, and because the other flags already present seem to make its behavior implicit. Also,
,→ unlike FileMoveDir, FOF_MULTIDESTFILES never seems to be needed.
// All of the below left set to NULL/FALSE by the struct initializer higher above:
//FileOp.hNameMappings = NULL;
//FileOp.lpszProgressTitle = NULL;
//FileOp.fAnyOperationsAborted = FALSE;
//FileOp.hwnd = NULL;

// If the source directory contains any saved webpages consisting of a SiteName.htm file and a
// corresponding directory named SiteName_files, the following may indicate an error even when the
// copy is successful. Under Windows XP at least, the return value is 7 under these conditions,
// which according to WinError.h is ”ERROR_ARENA_TRASHED: The storage control blocks were destroyed.”
// However, since this error might occur under a variety of circumstances, it probably wouldn ' t be
// proper to consider it a non-error.
// I also checked GetLastError() after calling SHFileOperation(), but it does not appear to be
// valid/useful in this case (MSDN mentions this fact but isn ' t clear about it).
// The issue appears to affect only FileCopyDir, not FileMoveDir or FileRemoveDir. It also seems
// unlikely to affect FileCopy/FileMove because they never copy directories.
return !SHFileOperation(&FileOp);
}
 

50
FileCreateDir

Creates a directory/folder.
 
FileCreateDir, DirName
 
Parameters

• DirName Name of the directory to create, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

Remarks

This command will also create all parent directories given in DirName if they do not already exist.
 
ResultType Line::FileCreateDir(LPTSTR aDirSpec)
{
if (!aDirSpec || !*aDirSpec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

DWORD attr = GetFileAttributes(aDirSpec);


if (attr != 0xFFFFFFFF) // aDirSpec already exists.
return SetErrorsOrThrow(!(attr & FILE_ATTRIBUTE_DIRECTORY), ERROR_ALREADY_EXISTS); // Indicate success
,→ if it already exists as a dir.

// If it has a backslash, make sure all its parent directories exist before we attempt
// to create this directory:
LPTSTR last_backslash = _tcsrchr(aDirSpec, ' \\ ' );
if (last_backslash > aDirSpec) // v1.0.48.04: Changed ”last_backslash” to ”last_backslash > aDirSpec” so
,→ that an aDirSpec with a leading \ (but no other backslashes), such as \dir, is supported.
{
TCHAR parent_dir[MAX_PATH];
if (_tcslen(aDirSpec) >= _countof(parent_dir)) // avoid overflow
return SetErrorsOrThrow(true, ERROR_BUFFER_OVERFLOW);
tcslcpy(parent_dir, aDirSpec, last_backslash - aDirSpec + 1); // Omits the last backslash.
FileCreateDir(parent_dir); // Recursively create all needed ancestor directories.

// v1.0.44: Fixed ErrorLevel being set to 1 when the specified directory ends in a backslash. In such
,→ cases,
// two calls were made to CreateDirectory for the same folder: the first without the backslash and
,→ then with
// it. Since the directory already existed on the second call, ErrorLevel was wrongly set to 1 even
,→ though
// everything succeeded. So now, when recursion finishes creating all the ancestors of this directory
// our own layer here does not call CreateDirectory() when there ' s a trailing backslash because a
,→ previous
// layer already did:
if (!last_backslash[1] || *g_ErrorLevel->Contents() == *ERRORLEVEL_ERROR) // Compare first char of
,→ each string, which is valid because ErrorLevel is stored as a quoted/literal string rather than
,→ an integer.
return OK; // Let the previously set ErrorLevel (whatever it is) tell the story.
}

// The above has recursively created all parent directories of aDirSpec if needed.
// Now we can create aDirSpec. Be sure to explicitly set g_ErrorLevel since it ' s value
// is now indeterminate due to action above:
return SetErrorsOrThrow(!CreateDirectory(aDirSpec, NULL));
}
 

51
FileCreateShortcut

Creates a shortcut (.lnk) file.


 
FileCreateShortcut, Target, LinkFile [, WorkingDir, Args, Description, IconFile, ShortcutKey, IconNumber,
,→ RunState]
 
Parameters

• Target Name of the file that the shortcut refers to, which should include an absolute path unless the file is integrated with the system
(e.g. Notepad.exe). The file does not have to exist at the time the shortcut is created; in other words, shortcuts to invalid targets can be created.

• LinkFile Name of the shortcut file to be created, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. Be sure to include
the .lnk extension. If the file already exists, it will be overwritten.

• WorkingDir Directory that will become Target’s current working directory when the shortcut is launched. If blank or omitted, the shortcut will have
a blank “Start in” field and the system will provide a default working directory when the shortcut is launched.

• Args Parameters that will be passed to Target when it is launched. Separate parameters with spaces. If a parameter contains spaces, enclose it
in double quotes.

• Description Comments that describe the shortcut (used by the OS to display a tooltip, etc.)

• IconFile The full path and name of the icon to be displayed for LinkFile. It must either be an ico file or the very first icon of an EXE or DLL.

• ShortcutKey A single letter, number, or the name of a single key from the key list (mouse buttons and other non-standard keys might not be
supported). Do not include modifier symbols. Currently, all shortcut keys are created as CTRL+ALT shortcuts. For example, if the letter B is
specified for this parameter, the shortcut key will be CTRL-ALT-B.

• IconNumber To use an icon in IconFile other than the first, specify that number here (can be an expression). For example, 2 is the second icon.

• RunState To have Target launched minimized or maximized, specify one of the following digits:

– 1 - Normal (this is the default)


– 3 - Maximized
– 7 - Minimized

Remarks

Target might not need to include a path if the target file resides in one of the folders listed in the system’s PATH environment variable.

The ShortcutKey of a newly created shortcut will have no effect unless the shortcut file resides on the desktop or somewhere in the Start Menu. If the
ShortcutKey you choose is already in use, your new shortcut takes precedence.

An alternative way to create a shortcut to a URL is the following example, which creates a special URL shortcut. Change the first two parameters to suit
your preferences:
 
IniWrite, http://www.google.com, C:\My Shortcut.url, InternetShortcut, URL.
 
The following may be optionally added to assign an icon to the above:
 
IniWrite, <IconFile>, C:\My Shortcut.url, InternetShortcut, IconFile
IniWrite, 0, C:\My Shortcut.url, InternetShortcut, IconIndex
 
In the above, replace 0 with the index of the icon (0 is used for the first icon) and replace with a URL, EXE, DLL, or ICO file. Examples: C:\Icons.dll,
C:\App.exe, http://www.somedomain.com/ShortcutIcon.ico

The operating system will treat a .URL file created by the above as a real shortcut even though it is a plain text file rather than a .LNK file.
 
ResultType Line::FileCreateShortcut(LPTSTR aTargetFile, LPTSTR aShortcutFile, LPTSTR aWorkingDir, LPTSTR aArgs
, LPTSTR aDescription, LPTSTR aIconFile, LPTSTR aHotkey, LPTSTR aIconNumber, LPTSTR aRunState)
{
bool bSucceeded = false;
CoInitialize(NULL);
IShellLink *psl;

if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl


,→ )))

52
{
psl->SetPath(aTargetFile);
if (*aWorkingDir)
psl->SetWorkingDirectory(aWorkingDir);
if (*aArgs)
psl->SetArguments(aArgs);
if (*aDescription)
psl->SetDescription(aDescription);
if (*aIconFile)
psl->SetIconLocation(aIconFile, *aIconNumber ? ATOI(aIconNumber) - 1 : 0); // Doesn ' t seem
,→ necessary to validate aIconNumber as not being negative, etc.
if (*aHotkey)
{
// If badly formatted, it ' s not a critical error, just continue.
// Currently, only shortcuts with a CTRL+ALT are supported.
// AutoIt3 note: Make sure that CTRL+ALT is selected (otherwise invalid)
vk_type vk = TextToVK(aHotkey);
if (vk)
// Vk in low 8 bits, mods in high 8:
psl->SetHotkey( (WORD)vk | ((WORD)(HOTKEYF_CONTROL | HOTKEYF_ALT) << 8) );
}
if (*aRunState)
psl->SetShowCmd(ATOI(aRunState)); // No validation is done since there ' s a chance other numbers
,→ might be valid now or in the future.

IPersistFile *ppf;
if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile,(LPVOID *)&ppf)))
{
#ifndef UNICODE
WCHAR wsz[MAX_PATH];
ToWideChar(aShortcutFile, wsz, MAX_PATH); // Dest. size is in wchars, not bytes.
#else
LPCWSTR wsz = aShortcutFile;
#endif
// MSDN says to pass ”The absolute path of the file”. Windows 10 requires it.
WCHAR full_path[MAX_PATH];
GetFullPathNameW(wsz, _countof(full_path), full_path, NULL);
if (SUCCEEDED(ppf->Save(full_path, TRUE)))
{
g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
bSucceeded = true;
}
ppf->Release();
}
psl->Release();
}

CoUninitialize();
if (bSucceeded)
return OK;
else
return SetErrorLevelOrThrow();
}
 

53
FileDelete

Deletes one or more files.


 
FileDelete, FilePattern
 
Parameters

• FilePattern The name of a single file or a wildcard pattern such as C:\Temp\*.tmp. FilePattern is assumed to be in %A_WorkingDir% if an absolute
path isn’t specified.

To remove an entire folder, along with all its sub-folders and files, use FileRemoveDir.

Remarks

To delete a read-only file, first remove the read-only attribute. For example: FileSetAttrib, -R, C:\My File.txt.
 
ResultType Line::FileDelete()
{
// Below is done directly this way rather than passed in as args mainly to emphasize that
// ArgLength() can safely be called in Line methods like this one (which is done further below).
// It also may also slightly improve performance and reduce code size.
LPTSTR aFilePattern = ARG1;

if (!*aFilePattern)
{
// Let ErrorLevel indicate an error, since this is probably not what the user intended.
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);
}

if (!StrChrAny(aFilePattern, _T(”?*”)))
{
SetLastError(0); // For sanity: DeleteFile appears to set it only on failure.
// ErrorLevel will indicate failure if DeleteFile fails.
return SetErrorsOrThrow(!DeleteFile(aFilePattern));
}

// Otherwise aFilePattern contains wildcards, so we ' ll search for all matches and delete them.
// Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
// than 256 or so, even if the pattern would match files whose names are short enough to be legal.
// Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
// MSDN confirms this in a vague way: ”In the ANSI version of FindFirstFile(), [plpFileName] is
// limited to MAX_PATH characters.”
if (ArgLength(1) >= MAX_PATH) // Checked early to simplify things later below.
{
// Let ErrorLevel indicate the error.
return SetErrorsOrThrow(true, ERROR_BUFFER_OVERFLOW);
}

LONG_OPERATION_INIT
int failure_count = 0; // Set default.

WIN32_FIND_DATA current_file;
HANDLE file_search = FindFirstFile(aFilePattern, &current_file);
if (file_search == INVALID_HANDLE_VALUE) // No matching files found.
{
g->LastError = GetLastError(); // Probably (but not necessarily) ERROR_FILE_NOT_FOUND.
return g_ErrorLevel->Assign(0); // Deleting a wildcard pattern that matches zero files is a success.
}

// Otherwise:
TCHAR file_path[MAX_PATH];
_tcscpy(file_path, aFilePattern); // Above has already confirmed this won ' t overflow.

54
// Remove the filename and/or wildcard part. But leave the trailing backslash on it for
// consistency with below:
size_t file_path_length;
LPTSTR last_backslash = _tcsrchr(file_path, ' \\ ' );
if (last_backslash)
{
*(last_backslash + 1) = ' \0 ' ; // i.e. retain the trailing backslash.
file_path_length = _tcslen(file_path);
}
else // Use current working directory, e.g. if user specified only *.*
{
*file_path = ' \0 ' ;
file_path_length = 0;
}

LPTSTR append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only
,→ once. This is where the changing part gets appended.
size_t space_remaining = _countof(file_path) - file_path_length - 1; // Space left in file_path for the
,→ changing part.

g->LastError = 0; // Set default. Overridden only when a failure occurs.

do
{
// Since other script threads can interrupt during LONG_OPERATION_UPDATE, it ' s important that
// this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
// possible. This is because an interrupting thread usually changes the values to something
// inappropriate for this thread.
LONG_OPERATION_UPDATE
if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // skip any matching directories.
continue;
if (_tcslen(current_file.cFileName) > space_remaining)
{
// v1.0.45.03: Don ' t even try to operate upon truncated filenames in case they accidentally
// match the name of a real/existing file.
g->LastError = ERROR_BUFFER_OVERFLOW;
++failure_count;
}
else
{
_tcscpy(append_pos, current_file.cFileName); // Above has ensured this won ' t overflow.
if (!DeleteFile(file_path))
{
g->LastError = GetLastError();
++failure_count;
}
}
} while (FindNextFile(file_search, &current_file));
FindClose(file_search);

return SetErrorLevelOrThrowInt(failure_count); // i.e. indicate success if there were no failures.


}
 

55
FileExist()

Checks for the existence of a file or folder and returns its attributes.
 
AttributeString := FileExist(FilePattern)
 
Parameters

• FilePattern The path, filename, or file pattern to check. FilePattern is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

Return Value

This function returns the attribute string (a subset of “RASHNDOCT”) of the first matching file or folder:

• R = READONLY
• A = ARCHIVE
• S = SYSTEM
• H = HIDDEN
• N = NORMAL
• D = DIRECTORY
• O = OFFLINE
• C = COMPRESSED
• T = TEMPORARY

If the file has no attributes (rare), “X” is returned. If no file is found, an empty string is returned.

Remarks

This function is a combination of IfExist and FileGetAttrib.

Since an empty string is seen as “false”, the function’s return value can always be used as a quasi-boolean value. For example, the statement if
FileExist(“C:\My File.txt”) would be true if the file exists and false otherwise. Similarly, the statement if InStr(FileExist(“C:\My Folder”), “D”) would be true
only if the file exists and is a directory.
 
bool DoesFilePatternExist(LPTSTR aFilePattern, DWORD *aFileAttr)
// Returns true if the file/folder exists or false otherwise.
// If non-NULL, aFileAttr ' s DWORD is set to the attributes of the file/folder if a match is found.
// If there is no match, its contents are undefined.
{
if (!aFilePattern || !*aFilePattern) return false;
// Fix for v1.0.35.12: Don ' t consider the question mark in ”\\?\Volume{GUID}\” to be a wildcard.
// Such volume names are obtained from GetVolumeNameForVolumeMountPoint() and perhaps other functions.
// However, testing shows that wildcards beyond that first one should be seen as real wildcards
// because the wildcard match-method below does work for such volume names.
LPTSTR cp = _tcsncmp(aFilePattern, _T(”\\\\?\\”), 4) ? aFilePattern : aFilePattern + 4;
if (StrChrAny(cp, _T(”?*”)))
{
WIN32_FIND_DATA wfd;
HANDLE hFile = FindFirstFile(aFilePattern, &wfd);
if (hFile == INVALID_HANDLE_VALUE)
return false;
FindClose(hFile);
if (aFileAttr)
*aFileAttr = wfd.dwFileAttributes;
return true;
}
else
{
DWORD attr = GetFileAttributes(aFilePattern);
if (aFileAttr)
*aFileAttr = attr;
return attr != 0xFFFFFFFF;
}
}

56
LPTSTR FileAttribToStr(LPTSTR aBuf, DWORD aAttr)
// Caller must ensure that aAttr is valid (i.e. that it ' s not 0xFFFFFFFF).
{
if (!aBuf) return aBuf;
int length = 0;
if (aAttr & FILE_ATTRIBUTE_READONLY)
aBuf[length++] = ' R ' ;
if (aAttr & FILE_ATTRIBUTE_ARCHIVE)
aBuf[length++] = ' A ' ;
if (aAttr & FILE_ATTRIBUTE_SYSTEM)
aBuf[length++] = ' S ' ;
if (aAttr & FILE_ATTRIBUTE_HIDDEN)
aBuf[length++] = ' H ' ;
if (aAttr & FILE_ATTRIBUTE_NORMAL)
aBuf[length++] = ' N ' ;
if (aAttr & FILE_ATTRIBUTE_DIRECTORY)
aBuf[length++] = ' D ' ;
if (aAttr & FILE_ATTRIBUTE_OFFLINE)
aBuf[length++] = ' O ' ;
if (aAttr & FILE_ATTRIBUTE_COMPRESSED)
aBuf[length++] = ' C ' ;
if (aAttr & FILE_ATTRIBUTE_TEMPORARY)
aBuf[length++] = ' T ' ;
aBuf[length] = ' \0 ' ; // Perform the final termination.
return aBuf;
}

BIF_DECL(BIF_FileExist)
{
TCHAR filename_buf[MAX_NUMBER_SIZE]; // Because aResultToken.buf is used for something else below.
LPTSTR filename = ParamIndexToString(0, filename_buf);
aResultToken.marker = aResultToken.buf; // If necessary, it will be moved to a persistent memory location
,→ by our caller.
aResultToken.symbol = SYM_STRING;
DWORD attr;
if (DoesFilePatternExist(filename, &attr))
{
// Yield the attributes of the first matching file. If not match, yield an empty string.
// This relies upon the fact that a file ' s attributes are never legitimately zero, which
// seems true but in case it ever isn ' t, this forces a non-empty string be used.
// UPDATE for v1.0.44.03: Someone reported that an existing file (created by NTbackup.exe) can
// apparently have undefined/invalid attributes (i.e. attributes that have no matching letter in
// ”RASHNDOCT”). Although this is unconfirmed, it ' s easy to handle that possibility here by
// checking for a blank string. This allows FileExist() to report boolean TRUE rather than FALSE
// for such ”mystery files”:
FileAttribToStr(aResultToken.marker, attr);
if (!*aResultToken.marker) // See above.
{
// The attributes might be all 0, but more likely the file has some of the newer attributes
// such as FILE_ATTRIBUTE_ENCRYPTED (or has undefined attributes). So rather than storing attr as
// a hex number (which could be zero and thus defeat FileExist ' s ability to detect the file), it
// seems better to store some arbitrary letter (other than those in ”RASHNDOCT”) so that FileExist
,→ ' s
// return value is seen as boolean ”true”.
aResultToken.marker[0] = ' X ' ;
aResultToken.marker[1] = ' \0 ' ;
}
}
else // Empty string is the indicator of ”not found” (seems more consistent than using an integer 0, since

57
,→ caller might rely on it being SYM_STRING).
*aResultToken.marker = ' \0 ' ;
}
 

58
FileGetAttrib

Reports whether a file or folder is read-only, hidden, etc.


 
FileGetAttrib, OutputVar [, Filename]
AttributeString := FileExist(FilePattern)
 
Parameters

• OutputVar The name of the variable in which to store the retrieved text.

• Filename The name of the target file, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. If omitted, the current file of
the innermost enclosing File-Loop will be used instead.

Remarks

The string returned will contain a subset of the letters in the string “RASHNDOCT”:

• R = READONLY
• A = ARCHIVE
• S = SYSTEM
• H = HIDDEN
• N = NORMAL
• D = DIRECTORY
• O = OFFLINE
• C = COMPRESSED
• T = TEMPORARY

To check if a particular attribute is present in the retrieved string, following this example:
 
FileGetAttrib, Attributes, C:\My File.txt
IfInString, Attributes, H
MsgBox The file is hidden.
 
On a related note, to retrieve a file’s 8.3 short name, follow this example:
 
Loop, C:\My Documents\Address List.txt
ShortPathName = %A_LoopFileShortPath% ; Will yield something similar to C:\MYDOCU˜1\ADDRES˜1.txt
 
A similar method can be used to get the long name of an 8.3 short name.
 
LPTSTR FileAttribToStr(LPTSTR aBuf, DWORD aAttr)
// Caller must ensure that aAttr is valid (i.e. that it ' s not 0xFFFFFFFF).
{
if (!aBuf) return aBuf;
int length = 0;
if (aAttr & FILE_ATTRIBUTE_READONLY)
aBuf[length++] = ' R ' ;
if (aAttr & FILE_ATTRIBUTE_ARCHIVE)
aBuf[length++] = ' A ' ;
if (aAttr & FILE_ATTRIBUTE_SYSTEM)
aBuf[length++] = ' S ' ;
if (aAttr & FILE_ATTRIBUTE_HIDDEN)
aBuf[length++] = ' H ' ;
if (aAttr & FILE_ATTRIBUTE_NORMAL)
aBuf[length++] = ' N ' ;
if (aAttr & FILE_ATTRIBUTE_DIRECTORY)
aBuf[length++] = ' D ' ;
if (aAttr & FILE_ATTRIBUTE_OFFLINE)
aBuf[length++] = ' O ' ;
if (aAttr & FILE_ATTRIBUTE_COMPRESSED)
aBuf[length++] = ' C ' ;
if (aAttr & FILE_ATTRIBUTE_TEMPORARY)
aBuf[length++] = ' T ' ;

59
aBuf[length] = ' \0 ' ; // Perform the final termination.
return aBuf;
}

ResultType Line::FileGetAttrib(LPTSTR aFilespec)


{
OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.

if (!aFilespec || !*aFilespec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

DWORD attr = GetFileAttributes(aFilespec);


if (attr == 0xFFFFFFFF) // Failure, probably because file doesn ' t exist.
return SetErrorsOrThrow(true);

SetErrorsOrThrow(false, 0);
TCHAR attr_string[128];
return OUTPUT_VAR->Assign(FileAttribToStr(attr_string, attr));
}
 

60
FileGetShortcut

Retrieves information about a shortcut (.lnk) file, such as its target file.
 
FileGetShortcut, LinkFile [, OutTarget, OutDir, OutArgs, OutDescription, OutIcon, OutIconNum, OutRunState]
 
Parameters

• LinkFile Name of the shortcut file to be analyzed, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. Be sure to
include the .lnk extension.

• OutTarget Name of the variable in which to store the shortcut’s target (not including any arguments it might have). For example:
C:\WINDOWS\system32\notepad.exe

• OutDir Name of the variable in which to store the shortcut’s working directory. For example: C:\My Documents. If environment variables such
as %WinDir% are present in the string, one way to resolve them is via StringReplace. For example: StringReplace, OutDir, OutDir, %WinDir%,
%A_WinDir%.

• OutArgs Name of the variable in which to store the shortcut’s parameters (blank if none).

• OutDescription Name of the variable in which to store the shortcut’s comments (blank if none).

• OutIcon Name of the variable in which to store the filename of the shortcut’s icon (blank if none).

• OutIconNum Name of the variable in which to store the shortcut’s icon number within the icon file (blank if none). This value is most often 1, which
means the first icon.

• OutRunState Name of the variable in which to store the shortcut’s initial launch state, which is one of the following digits:

– 1: Normal
– 3: Maximized
– 7: Minimized

Remarks

Any of the output variables may be omitted if the corresponding information is not needed.
 
ResultType Line::FileGetShortcut(LPTSTR aShortcutFile) // Credited to Holger <Holger.Kotsch at GMX de>.
{
Var *output_var_target = ARGVAR2; // These might be omitted in the parameter list, so it ' s okay if
Var *output_var_dir = ARGVAR3; // they resolve to NULL. Also, load-time validation has ensured
Var *output_var_arg = ARGVAR4; // that these are valid output variables (e.g. not built-in vars).
Var *output_var_desc = ARGVAR5; // Load-time validation has ensured that these are valid output
,→ variables (e.g. not built-in vars).
Var *output_var_icon = ARGVAR6;
Var *output_var_icon_idx = ARGVAR7;
Var *output_var_show_state = ARGVAR8;

// For consistency with the behavior of other commands, the output variables are initialized to blank
// so that there is another way to detect failure:
if (output_var_target) output_var_target->Assign();
if (output_var_dir) output_var_dir->Assign();
if (output_var_arg) output_var_arg->Assign();
if (output_var_desc) output_var_desc->Assign();
if (output_var_icon) output_var_icon->Assign();
if (output_var_icon_idx) output_var_icon_idx->Assign();
if (output_var_show_state) output_var_show_state->Assign();

bool bSucceeded = false;

if (!Util_DoesFileExist(aShortcutFile))
goto error;

CoInitialize(NULL);
IShellLink *psl;

61
if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl
,→ )))
{
IPersistFile *ppf;
if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf)))
{
#ifdef UNICODE
if (SUCCEEDED(ppf->Load(aShortcutFile, 0)))
#else
WCHAR wsz[MAX_PATH+1]; // +1 hasn ' t been explained, but is retained in case it ' s needed.
ToWideChar(aShortcutFile, wsz, MAX_PATH+1); // Dest. size is in wchars, not bytes.
if (SUCCEEDED(ppf->Load((const WCHAR*)wsz, 0)))
#endif
{
TCHAR buf[MAX_PATH+1];
int icon_index, show_cmd;

if (output_var_target)
{
psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);
output_var_target->Assign(buf);
}
if (output_var_dir)
{
psl->GetWorkingDirectory(buf, MAX_PATH);
output_var_dir->Assign(buf);
}
if (output_var_arg)
{
psl->GetArguments(buf, MAX_PATH);
output_var_arg->Assign(buf);
}
if (output_var_desc)
{
psl->GetDescription(buf, MAX_PATH); // Testing shows that the OS limits it to 260
,→ characters.
output_var_desc->Assign(buf);
}
if (output_var_icon || output_var_icon_idx)
{
psl->GetIconLocation(buf, MAX_PATH, &icon_index);
if (output_var_icon)
output_var_icon->Assign(buf);
if (output_var_icon_idx)
if (*buf)
output_var_icon_idx->Assign(icon_index + 1); // Convert from 0-based to 1-based
,→ for consistency with the Menu command, etc.
else
output_var_icon_idx->Assign(); // Make it blank to indicate that there is none.
}
if (output_var_show_state)
{
psl->GetShowCmd(&show_cmd);
output_var_show_state->Assign(show_cmd);
// For the above, decided not to translate them to Max/Min/Normal since other
// show-state numbers might be supported in the future (or are already). In other
// words, this allows the flexibility to specify some number other than 1/3/7 when
// creating the shortcut in case it happens to work. Of course, that applies only
// to FileCreateShortcut, not here. But it ' s done here so that this command is
// compatible with that one.

62
}
g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
bSucceeded = true;
}
ppf->Release();
}
psl->Release();
}
CoUninitialize();

if (!bSucceeded)
goto error;

return OK; // ErrorLevel might still indicate failure if one of the above calls failed.

error:
return SetErrorLevelOrThrow();
}
 

63
FileGetSize

Retrieves the size of a file.


 
FileGetSize, OutputVar [, Filename, Units]
 
Parameters

• OutputVar The name of the variable in which to store the retrieved size (rounded down to the nearest whole number).

• Filename The name of the target file, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. If omitted, the current file of
the innermost enclosing File-Loop will be used instead.

• Units If present, this parameter causes the result to be returned in units other than bytes:

– K = kilobytes
– M = megabytes

Remarks

Files of any size are supported, even those over 4 gigabytes, and even if Units is bytes.

If the target file is a directory, the size will be reported as whatever the OS believes it to be (probably zero in all cases).

To calculate the size of folder, including all its files, follow this example:
 
SetBatchLines, -1 ; Make the operation run at maximum speed.
FolderSize = 0
FileSelectFolder, WhichFolder ; Ask the user to pick a folder.
Loop, %WhichFolder%\*.*, , 1
FolderSize += %A_LoopFileSize%
MsgBox Size of %WhichFolder% is %FolderSize% bytes.
 
 
ResultType Line::FileGetSize(LPTSTR aFilespec, LPTSTR aGranularity)
{
OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.

if (!aFilespec || !*aFilespec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER); // Let ErrorLevel indicate an error, since
,→ this is probably not what the user intended.

BOOL got_file_size = false;


__int64 size;

// Try CreateFile() and GetFileSizeEx() first, since they can be more accurate.
// See ”Why is the file size reported incorrectly for files that are still being written to?”
// http://blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
HANDLE hfile = CreateFile(aFilespec, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE |
,→ FILE_SHARE_DELETE
, NULL, OPEN_EXISTING, 0, NULL);
if (hfile != INVALID_HANDLE_VALUE)
{
got_file_size = GetFileSizeEx(hfile, (PLARGE_INTEGER)&size);
CloseHandle(hfile);
}

if (!got_file_size)
{
WIN32_FIND_DATA found_file;
HANDLE file_search = FindFirstFile(aFilespec, &found_file);
if (file_search == INVALID_HANDLE_VALUE)
return SetErrorsOrThrow(true); // Let ErrorLevel tell the story.
FindClose(file_search);
size = ((__int64)found_file.nFileSizeHigh << 32) | found_file.nFileSizeLow;

64
}

switch(ctoupper(*aGranularity))
{
case ' K ' : // KB
size /= 1024;
break;
case ' M ' : // MB
size /= (1024 * 1024);
break;
// default: // i.e. either ' B ' for bytes, or blank, or some other unknown value, so default to bytes.
// do nothing
}

SetErrorsOrThrow(false, 0); // Indicate success.


return OUTPUT_VAR->Assign(size);
// The below comment is obsolete in light of the switch to 64-bit integers. But it might
// be good to keep for background:
// Currently, the above is basically subject to a 2 gig limit, I believe, after which the
// size will appear to be negative. Beyond a 4 gig limit, the value will probably wrap around
// to zero and start counting from there as file sizes grow beyond 4 gig (UPDATE: The size
// is now set to -1 [the maximum DWORD when expressed as a signed int] whenever >4 gig).
// There ' s not much sense in putting values larger than 2 gig into the var as a text string
// containing a positive number because such a var could never be properly handled by anything
// that compares to it (e.g. IfGreater) or does math on it (e.g. EnvAdd), since those operations
// use ATOI() to convert the string. So as a future enhancement (unless the whole program is
// revamped to use 64bit ints or something) might add an optional param to the end to indicate
// size should be returned in K(ilobyte) or M(egabyte). However, this is sorta bad too since
// adding a param can break existing scripts which use filenames containing commas (delimiters)
// with this command. Therefore, I think I ' ll just add the K/M param now.
// Also, the above assigns an int because unsigned ints should never be stored in script
// variables. This is because an unsigned variable larger than INT_MAX would not be properly
// converted by ATOI(), which is current standard method for variables to be auto-converted
// from text back to a number whenever that is needed.
}
 

65
FileGetTime

Retrieves the datetime stamp of a file or folder.


 
FileGetTime, OutputVar [, Filename, WhichTime]
 
Parameters

• OutputVar The name of the variable in which to store the retrieved date-time in format YYYYMMDDHH24MISS. The time is your own local time,
not UTC/GMT.

• Filename The name of the target file or folder, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. If omitted, the
current file of the innermost enclosing File-Loop will be used instead.

• WhichTime Which timestamp to retrieve:

– M = Modification time (this is the default if the parameter is omitted)


– C = Creation time
– A = Last access time
 
LPTSTR SystemTimeToYYYYMMDD(LPTSTR aBuf, SYSTEMTIME &aTime)
// Returns aBuf.
// Remember not to offer a ”aConvertToLocalTime” option, because calling SystemTimeToTzSpecificLocalTime()
// on Win9x apparently results in an invalid time because the function is implemented only as a stub on
// those OSes.
{
_stprintf(aBuf, _T(”%04d%02d%02d”) _T(”%02d%02d%02d”)
, aTime.wYear, aTime.wMonth, aTime.wDay
, aTime.wHour, aTime.wMinute, aTime.wSecond);
return aBuf;
}

LPTSTR FileTimeToYYYYMMDD(LPTSTR aBuf, FILETIME &aTime, bool aConvertToLocalTime)


// Returns aBuf.
{
FILETIME ft;
if (aConvertToLocalTime)
FileTimeToLocalFileTime(&aTime, &ft); // MSDN says that target cannot be the same var as source.
else
memcpy(&ft, &aTime, sizeof(FILETIME)); // memcpy() might be less code size that a struct assignment,
,→ ft = aTime.
SYSTEMTIME st;
if (FileTimeToSystemTime(&ft, &st))
return SystemTimeToYYYYMMDD(aBuf, st);
*aBuf = ' \0 ' ;
return aBuf;
}

ResultType Line::FileGetTime(LPTSTR aFilespec, TCHAR aWhichTime)


{
OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.

if (!aFilespec || !*aFilespec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);

// Don ' t use CreateFile() & FileGetSize() size they will fail to work on a file that ' s in use.
// Research indicates that this method has no disadvantages compared to the other method.
WIN32_FIND_DATA found_file;
HANDLE file_search = FindFirstFile(aFilespec, &found_file);
if (file_search == INVALID_HANDLE_VALUE)
return SetErrorsOrThrow(true);
FindClose(file_search);

66
FILETIME local_file_time;
switch (ctoupper(aWhichTime))
{
case ' C ' : // File ' s creation time.
FileTimeToLocalFileTime(&found_file.ftCreationTime, &local_file_time);
break;
case ' A ' : // File ' s last access time.
FileTimeToLocalFileTime(&found_file.ftLastAccessTime, &local_file_time);
break;
default: // ' M ' , unspecified, or some other value. Use the file ' s modification time.
FileTimeToLocalFileTime(&found_file.ftLastWriteTime, &local_file_time);
}

SetErrorsOrThrow(false, 0); // Indicate success.


TCHAR local_file_time_string[128];
return OUTPUT_VAR->Assign(FileTimeToYYYYMMDD(local_file_time_string, local_file_time));
}
 

67
FileGetVersion

Retrieves the version of a file.


 
FileGetVersion, OutputVar [, Filename]
 
Parameters

• OutputVar The name of the variable in which to store the version number/string.

• Filename The name of the target file. If a full path is not specified, this function uses the search sequence specified by the system LoadLibrary
function. If omitted, the current file of the innermost enclosing File-Loop will be used instead.

Remarks

Most non-executable files (and even some EXEs) won’t have a version, and thus the OutputVar will be blank in these cases.
 
ResultType Line::FileGetVersion(LPTSTR aFilespec)
{
OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.

if (!aFilespec || !*aFilespec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER); // Error out, since this is probably not what
,→ the user intended.

DWORD dwUnused, dwSize;


if ( !(dwSize = GetFileVersionInfoSize(aFilespec, &dwUnused)) ) // No documented limit on how large
,→ it can be, so don ' t use _alloca().
return SetErrorsOrThrow(true);

BYTE *pInfo = (BYTE*)malloc(dwSize); // Allocate the size retrieved by the above.


VS_FIXEDFILEINFO *pFFI;
UINT uSize;

// Read the version resource


if (!GetFileVersionInfo(aFilespec, 0, dwSize, (LPVOID)pInfo)
// Locate the fixed information
|| !VerQueryValue(pInfo, _T(”\\”), (LPVOID *)&pFFI, &uSize))
{
g->LastError = GetLastError();
free(pInfo);
return SetErrorLevelOrThrow();
}

// extract the fields you want from pFFI


UINT iFileMS = (UINT)pFFI->dwFileVersionMS;
UINT iFileLS = (UINT)pFFI->dwFileVersionLS;
TCHAR version_string[128]; // AutoIt3: 43+1 is the maximum size, but leave a little room to increase
,→ confidence.
sntprintf(version_string, _countof(version_string), _T(”%u.%u.%u.%u”)
, (iFileMS >> 16), (iFileMS & 0xFFFF), (iFileLS >> 16), (iFileLS & 0xFFFF));

free(pInfo);

SetErrorsOrThrow(false, 0); // Indicate success.


return OUTPUT_VAR->Assign(version_string);
}
 

68
FileMove

Moves or renames one or more files.


 
FileMove, SourcePattern, DestPattern [, Flag]
 
Parameters

• SourcePattern The name of a single file or a wildcard pattern such as C:\Temp\*.tmp. SourcePattern is assumed to be in %A_WorkingDir% if an
absolute path isn’t specified.

• DestPattern The name or pattern of the destination, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified. To perform a
simple move – retaining the existing file name(s) – specify only the folder name as shown in these functionally identical examples:

FileMove, C:\*.txt, C:\My Folder

FileMove, C:\*.txt, C:\My Folder\*.*

• Flag (optional) this flag determines whether to overwrite files if they already exist:

– 0 = (default) do not overwrite existing files


– 1 = overwrite existing files

This parameter can be an expression, even one that evalutes to true or false (since true and false are stored internally as 1 and 0).

Remarks

FileMove moves files only. To move or rename a single folder, use FileMoveDir.

The operation will continue even if error(s) are encountered.

Although this command is capable of moving files to a different volume, the operation will take longer than a same-volume move. This is because a
same-volume move is similar to a rename, and therefore much faster.
 
void Line::Util_ExpandFilenameWildcard(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)
{
// copy one.two.three *.txt = one.two .txt
// copy one.two.three *.*.txt = one.two .three .txt
// copy one.two.three *.*.*.txt = one.two .three ..txt
// copy one.two test = test

TCHAR szFileTemp[_MAX_PATH+1];
TCHAR szExtTemp[_MAX_PATH+1];

TCHAR szSrcFile[_MAX_PATH+1];
TCHAR szSrcExt[_MAX_PATH+1];

TCHAR szDestDrive[_MAX_PATH+1];
TCHAR szDestDir[_MAX_PATH+1];
TCHAR szDestFile[_MAX_PATH+1];
TCHAR szDestExt[_MAX_PATH+1];

// If the destination doesn ' t include a wildcard, send it back verbatim


if (_tcschr(szDest, ' * ' ) == NULL)
{
_tcscpy(szExpandedDest, szDest);
return;
}

// Split source and dest into file and extension


_tsplitpath( szSource, szDestDrive, szDestDir, szSrcFile, szSrcExt );
_tsplitpath( szDest, szDestDrive, szDestDir, szDestFile, szDestExt );

// Source and Dest ext will either be ”.nnnn” or ”” or ”.*”, remove the period
if (szSrcExt[0] == ' . ' )
_tcscpy(szSrcExt, &szSrcExt[1]);

69
if (szDestExt[0] == ' . ' )
_tcscpy(szDestExt, &szDestExt[1]);

// Start of the destination with the drive and dir


_tcscpy(szExpandedDest, szDestDrive);
_tcscat(szExpandedDest, szDestDir);

// Replace first * in the destext with the srcext, remove any other *
Util_ExpandFilenameWildcardPart(szSrcExt, szDestExt, szExtTemp);

// Replace first * in the destfile with the srcfile, remove any other *
Util_ExpandFilenameWildcardPart(szSrcFile, szDestFile, szFileTemp);

// Concat the filename and extension if req


if (szExtTemp[0] != ' \0 ' )
{
_tcscat(szFileTemp, _T(”.”));
_tcscat(szFileTemp, szExtTemp);
}
else
{
// Dest extension was blank SOURCE MIGHT NOT HAVE BEEN!
if (szSrcExt[0] != ' \0 ' )
{
_tcscat(szFileTemp, _T(”.”));
_tcscat(szFileTemp, szSrcExt);
}
}

// Now add the drive and directory bit back onto the dest
_tcscat(szExpandedDest, szFileTemp);

void Line::Util_ExpandFilenameWildcardPart(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)


{
LPTSTR lpTemp;
int i, j, k;

// Replace first * in the dest with the src, remove any other *
i = 0; j = 0; k = 0;
lpTemp = (LPTSTR)_tcschr(szDest, ' * ' );
if (lpTemp != NULL)
{
// Contains at least one *, copy up to this point
while(szDest[i] != ' * ' )
szExpandedDest[j++] = szDest[i++];
// Skip the * and replace in the dest with the srcext
while(szSource[k] != ' \0 ' )
szExpandedDest[j++] = szSource[k++];
// Skip any other *
i++;
while(szDest[i] != ' \0 ' )
{
if (szDest[i] == ' * ' )
i++;
else
szExpandedDest[j++] = szDest[i++];

70
}
szExpandedDest[j] = ' \0 ' ;

}
else
{
// No wildcard, straight copy of destext
_tcscpy(szExpandedDest, szDest);
}
}

///////////////////////////////////////////////////////////////////////////////
// Util_CopyFile()
// (moves files too)
// Returns the number of files that could not be copied or moved due to error.
///////////////////////////////////////////////////////////////////////////////
int Line::Util_CopyFile(LPCTSTR szInputSource, LPCTSTR szInputDest, bool bOverwrite, bool bMove, DWORD &
,→ aLastError)
{
TCHAR szSource[_MAX_PATH+1];
TCHAR szDest[_MAX_PATH+1];
TCHAR szExpandedDest[MAX_PATH+1];
TCHAR szTempPath[_MAX_PATH+1];
TCHAR szDrive[_MAX_PATH+1];
TCHAR szDir[_MAX_PATH+1];
TCHAR szFile[_MAX_PATH+1];
TCHAR szExt[_MAX_PATH+1];

// Get local version of our source/dest with full path names, strip trailing \s
Util_GetFullPathName(szInputSource, szSource);
Util_GetFullPathName(szInputDest, szDest);

// If the source or dest is a directory then add *.* to the end


if (Util_IsDir(szSource))
_tcscat(szSource, _T(”\\*.*”));
if (Util_IsDir(szDest))
_tcscat(szDest, _T(”\\*.*”));

WIN32_FIND_DATA findData;
HANDLE hSearch = FindFirstFile(szSource, &findData);
if (hSearch == INVALID_HANDLE_VALUE)
{
aLastError = GetLastError(); // Set even in this case since FindFirstFile can fail due to actual
,→ errors, such as an invalid path.
return 0; // Indicate no failures.
}
aLastError = 0; // Set default. Overridden only when a failure occurs.

// Otherwise, loop through all the matching files.


// Split source into file and extension (we need this info in the loop below to reconstruct the path)
_tsplitpath(szSource, szDrive, szDir, szFile, szExt);
// Note we now rely on the SOURCE being the contents of szDrive, szDir, szFile, etc.
size_t szTempPath_length = sntprintf(szTempPath, _countof(szTempPath), _T(”%s%s”), szDrive, szDir);
LPTSTR append_pos = szTempPath + szTempPath_length;
size_t space_remaining = _countof(szTempPath) - szTempPath_length - 1;

int failure_count = 0;
LONG_OPERATION_INIT

do
{

71
// Since other script threads can interrupt during LONG_OPERATION_UPDATE, it ' s important that
// this function and those that call it not refer to sArgDeref[] and sArgVar[] anytime after an
// interruption becomes possible. This is because an interrupting thread usually changes the
// values to something inappropriate for this thread.
LONG_OPERATION_UPDATE

// Make sure the returned handle is a file and not a directory before we
// try and do copy type things on it!
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // dwFileAttributes should never be invalid
,→ (0xFFFFFFFF) in this case.
continue;

if (_tcslen(findData.cFileName) > space_remaining) // v1.0.45.03: Basic check in case of files whose


,→ full spec is over 260 characters long.
{
aLastError = ERROR_BUFFER_OVERFLOW; // MSDN: ”The file name is too long.”
++failure_count;
continue;
}
_tcscpy(append_pos, findData.cFileName); // Indirectly populate szTempPath. Above has ensured this won
,→ ' t overflow.

// Expand the destination based on this found file


Util_ExpandFilenameWildcard(findData.cFileName, szDest, szExpandedDest);

// Fixed for v1.0.36.01: This section has been revised to avoid unnecessary calls; but more
// importantly, it now avoids the deletion and complete loss of a file when it is copied or
// moved onto itself. That used to happen because any existing destination file used to be
// deleted prior to attempting the move/copy.
if (bMove) // Move vs. copy mode.
{
// Note that MoveFile() is capable of moving a file to a different volume, regardless of
// operating system version. That ' s enough for what we need because this function never
// moves directories, only files.

// The following call will report success if source and dest are the same file, even if
// source is something like ”..\Folder\Filename.txt” and dest is something like
// ”C:\Folder\Filename.txt” (or if source is an 8.3 filename and dest is the long name
// of the same file). This is good because it avoids the need to devise code
// to determine whether two different path names refer to the same physical file
// (note that GetFullPathName() has shown itself to be inadequate for this purpose due
// to problems with short vs. long names, UNC vs. mapped drive, and possibly NTFS hard
// links (aliases) that might all cause two different filenames to point to the same
// physical file on disk (hopefully MoveFile handles all of these correctly by indicating
// success [below] when a file is moved onto itself, though it has only been tested for
// basic cases of relative vs. absolute path).
if (!MoveFile(szTempPath, szExpandedDest))
{
// If overwrite mode was not specified by the caller, or it was but the existing
// destination file cannot be deleted (perhaps because it is a folder rather than
// a file), or it can be deleted but the source cannot be moved, indicate a failure.
// But by design, continue the operation. The following relies heavily on
// short-circuit boolean evaluation order:
if ( !(bOverwrite && DeleteFile(szExpandedDest) && MoveFile(szTempPath, szExpandedDest)) )
{
aLastError = GetLastError();
++failure_count; // At this stage, any of the above 3 being false is cause for failure.
}
//else everything succeeded, so nothing extra needs to be done. In either case,
// continue on to the next file.

72
}
}
else // The mode is ”Copy” vs. ”Move”
if (!CopyFile(szTempPath, szExpandedDest, !bOverwrite)) // Force it to fail if bOverwrite==false.
{
aLastError = GetLastError();
++failure_count;
}
} while (FindNextFile(hSearch, &findData));

FindClose(hSearch);
return failure_count;
}
 

73
FileMoveDir

Moves a folder along with all its sub-folders and files. It can also rename a folder.
 
FileMoveDir, Source, Dest [, Flag]
 
Parameters

• Source Name of the source directory (with no trailing backslash), which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.
For example: C:\My Folder

• Dest The new path and name of the directory (with no trailing baskslash), which is assumed to be in %A_WorkingDir% if an absolute path isn’t
specified. For example: D:\My Folder. Note: Dest is the actual path and name that the directory will have after it is moved; it is not the directory
into which Source is moved (except for the known limitation mentioned below).

• Flag (options) Specify one of the following single characters:

– 0 (default): Do not overwrite existing files. The operation will fail if Dest already exists as a file or directory.
– 1: Overwrite existing files. However, any files or subfolders inside Dest that do not have a counterpart in Source will not be deleted. Known
limitation: If Dest already exists as a folder and it is on the same volume as Source, Source will be moved into it rather than overwriting it.
To avoid this, see the next option.
– 2: The same as mode 1 above except that the limitation is absent.
– R: Rename the directory rather than moving it. Although renaming normally has the same effect as moving, it is helpful in cases where
you want “all or none” behavior; that is, when you don’t want the operation to be only partially successful when Source or one of its files is
locked (in use). Although this method cannot move Source onto a different volume, it can move it to any other directory on its own volume.
The operation will fail if Dest already exists as a file or directory.

Remarks

FileMoveDir moves a single folder to a new location. To instead move the contents of a folder (all its files and subfolders), see the examples section of
FileMove.

If the source and destination are on different volumes or UNC paths, a copy/delete operation will be performed rather than a move.
 
bool Line::Util_IsDifferentVolumes(LPCTSTR szPath1, LPCTSTR szPath2)
// Checks two paths to see if they are on the same volume.
{
TCHAR szP1Drive[_MAX_DRIVE+1];
TCHAR szP2Drive[_MAX_DRIVE+1];

TCHAR szDir[_MAX_DIR+1];
TCHAR szFile[_MAX_FNAME+1];
TCHAR szExt[_MAX_EXT+1];

TCHAR szP1[_MAX_PATH+1];
TCHAR szP2[_MAX_PATH+1];

// Get full pathnames


Util_GetFullPathName(szPath1, szP1);
Util_GetFullPathName(szPath2, szP2);

// Split the target into bits


_tsplitpath( szP1, szP1Drive, szDir, szFile, szExt );
_tsplitpath( szP2, szP2Drive, szDir, szFile, szExt );

if (szP1Drive[0] == ' \0 ' || szP2Drive[0] == ' \0 ' )


// One or both paths is a UNC - assume different volumes
return true;
else
return _tcsicmp(szP1Drive, szP2Drive);
}

bool Line::Util_MoveDir(LPCTSTR szInputSource, LPCTSTR szInputDest, int OverwriteMode)


{

74
// Get the fullpathnames and strip trailing \s
TCHAR szSource[_MAX_PATH+2];
TCHAR szDest[_MAX_PATH+2];
Util_GetFullPathName(szInputSource, szSource);
Util_GetFullPathName(szInputDest, szDest);

// Ensure source is a directory


if (Util_IsDir(szSource) == false)
return false; // Nope

// Does the destination dir exist?


DWORD attr = GetFileAttributes(szDest);
if (attr != 0xFFFFFFFF) // Destination already exists as a file or directory.
{
if (attr & FILE_ATTRIBUTE_DIRECTORY) // Dest already exists as a directory.
{
if (OverwriteMode != 1 && OverwriteMode != 2) // Overwrite Mode is ”Never”. Strict validation for
,→ safety.
return false; // For consistency, mode1 actually should move the source-dir *into* the
,→ identically name dest dir. But for backward compatibility, this change hasn ' t been
,→ made.
}
else // Dest already exists as a file.
return false; // Don ' t even attempt to overwrite a file with a dir, regardless of mode (I think
,→ SHFileOperation refuses to do it anyway).
}

if (Util_IsDifferentVolumes(szSource, szDest))
{
// If the source and dest are on different volumes then we must copy rather than move
// as move in this case only works on some OSes. Copy and delete (poor man ' s move).
if (!Util_CopyDir(szSource, szDest, true))
return false;
return Util_RemoveDir(szSource, true);
}

// Since above didn ' t return, source and dest are on same volume.
// We must also make source\dest double nulled strings for the SHFileOp API
szSource[_tcslen(szSource)+1] = ' \0 ' ;
szDest[_tcslen(szDest)+1] = ' \0 ' ;

// Setup the struct


SHFILEOPSTRUCT FileOp = {0};
FileOp.pFrom = szSource;
FileOp.pTo = szDest;
FileOp.wFunc = FO_MOVE;
FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI; // Set default.
,→ FOF_NO_UI (”perform the operation with no user input”) is not present for in case it would break
,→ compatibility somehow, and because the other flags already present seem to make its behavior
,→ implicit.
if (OverwriteMode == 2) // v1.0.46.07: Using the FOF_MULTIDESTFILES flag (as hinted by MSDN) overwrites/
,→ merges any existing target directory. This logic supersedes and fixes old logic that didn ' t work
,→ properly when the source dir was being both renamed and moved to overwrite an existing directory.
FileOp.fFlags |= FOF_MULTIDESTFILES;
// All of the below left set to NULL/FALSE by the struct initializer higher above:
//FileOp.hNameMappings = NULL;
//FileOp.lpszProgressTitle = NULL;
//FileOp.fAnyOperationsAborted = FALSE;
//FileOp.hwnd = NULL;

75
return !SHFileOperation(&FileOp);
}
 

76
FileRecycle

Sends a file or directory to the recycle bin, if possible.


 
FileRecycle, FilePattern
 
Parameters

• FilePattern The name of a single file or a wildcard pattern such as C:\Temp\*.tmp. FilePattern is assumed to be in %A_WorkingDir% if an absolute
path isn’t specified.

To recycle an entire directory, provide its name without a trailing backslash.

Remarks

SHFileOperation is used to do the actual work. This function may permanently delete the file if it is too large to be recycled; as of v1.0.96, a warning
should be shown before this occurs.
 
ResultType Line::FileRecycle(LPTSTR aFilePattern)
{
if (!aFilePattern || !*aFilePattern)
return SetErrorLevelOrThrow(); // Since this is probably not what the user intended.

SHFILEOPSTRUCT FileOp;
TCHAR szFileTemp[_MAX_PATH+2];

// au3: Get the fullpathname - required for UNDO to work


Util_GetFullPathName(aFilePattern, szFileTemp);

// au3: We must also make it a double nulled string *sigh*


szFileTemp[_tcslen(szFileTemp)+1] = ' \0 ' ;

// au3: set to known values - Corrects crash


FileOp.hNameMappings = NULL;
FileOp.lpszProgressTitle = NULL;
FileOp.fAnyOperationsAborted = FALSE;
FileOp.hwnd = NULL;
FileOp.pTo = NULL;

FileOp.pFrom = szFileTemp;
FileOp.wFunc = FO_DELETE;
FileOp.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_WANTNUKEWARNING;

// SHFileOperation() returns 0 on success:


return SetErrorLevelOrThrowBool(SHFileOperation(&FileOp));
}
 

77
FileRecycleEmpty

Empties the recycle bin.


 
FileRecycleEmpty [, DriveLetter]
 
Parameters

• DriveLetter If omitted, the recycle bin for all drives is emptied. Otherwise, specify a drive letter such as C:\

Remarks

This commands requires that MS Internet Explorer 4 or later be installed.


 
ResultType Line::FileRecycleEmpty(LPTSTR aDriveLetter)
{
LPCTSTR szPath = *aDriveLetter ? aDriveLetter : NULL;
HRESULT hr = SHEmptyRecycleBin(NULL, szPath, SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);
return SetErrorLevelOrThrowBool(hr != S_OK);
}
 

78
FileRemoveDir

Deletes a folder.
 
FileRemoveDir, DirName [, Recurse?]
 
Parameters

• DirName Name of the directory to delete, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

• Recurse?

– 0 (default): Do not remove files and sub-directories contained in DirName. In this case, if DirName is not empty, no action will be taken and
ErrorLevel will be set to 1.
– 1: Remove all files and subdirectories (like the Windows command “rmdir /S”).

This parameter can be an expression, even one that evalutes to true or false (since true and false are stored internally as 1 and 0).
 
bool Line::Util_RemoveDir(LPCTSTR szInputSource, bool bRecurse)
{
SHFILEOPSTRUCT FileOp;
TCHAR szSource[_MAX_PATH+2];

// Get the fullpathnames and strip trailing \s


Util_GetFullPathName(szInputSource, szSource);

// Ensure source is a directory


if (Util_IsDir(szSource) == false)
return false; // Nope

// If recursion not on just try a standard delete on the directory (the SHFile function WILL
// delete a directory even if not empty no matter what flags you give it...)
if (bRecurse == false)
{
if (!RemoveDirectory(szSource))
return false;
else
return true;
}

// We must also make double nulled strings for the SHFileOp API
szSource[_tcslen(szSource)+1] = ' \0 ' ;

// Setup the struct


FileOp.pFrom = szSource;
FileOp.pTo = NULL;
FileOp.hNameMappings = NULL;
FileOp.lpszProgressTitle = NULL;
FileOp.fAnyOperationsAborted = FALSE;
FileOp.hwnd = NULL;

FileOp.wFunc = FO_DELETE;
FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI;

return !SHFileOperation(&FileOp);
}
 

79
FileSelectFile

Displays a standard dialog that allows the user to open or save file(s).
 
FileSelectFile, OutputVar [, Options, RootDir\Filename, Prompt, Filter]
 
Parameters

• OutputVar The name of the variable in which to store the filename(s) selected by the user. This will be made blank if the user cancels the dialog
(i.e. does not wish to select a file).

• Options If omitted, it will default to zero, which is the same as having none of the options below.

M: Multi-select. Specify the letter M to allow the user to select more than one file via shift-click, control-click, or other means. M may optionally be
followed by a number as described below (for example, both M and M1 are valid). To extract the individual files, see the example at the bottom
of this page.

S: Save button. Specify the letter S to cause the dialog to always contain a Save button instead of an Open button. S may optionally be followed
by a number (or sum of numbers) as described below (for example, both S and S24 are valid).

Even if M and S are absent, the following numbers can be used. To put more than one of them into effect, add them up. For example, to use 8
and 16, specify the number 24.

– 1: File Must Exist


– 2: Path Must Exist
– 8: Prompt to Create New File
– 16: Prompt to OverWrite File
– 32 [v1.0.43.09+]: Shortcuts (.lnk files) are selected as-is rather than being resolved to their targets. This option also prevents navigation
into a folder via a folder shortcut.

If the “Prompt to Overwrite” option is present without the “Prompt to Create” option, the dialog will contain a Save button rather than an Open
button. This behavior is due to a quirk in Windows.

• RootDir\Filename If present, this parameter contains one or both of the following:

RootDir: The root (starting) directory, which is assumed to be a subfolder in %A_WorkingDir% if an absolute path is not specified. If omitted
or blank, the starting directory will be a default that might depend on the OS version (it will likely be the directory most recently selected by
the user during a prior use of FileSelectFile). In v1.0.43.10+ on Windows XP/2003 and earlier, a CLSID such as ::{20d04fe0-3aea-1069-a2d8-
08002b30309d} (i.e. My Computer) may also be specified, in which case any subdirectory present after the CLSID should end in a backslash
(otherwise, the string after the last backslash will be interpreted as the default filename, below).

Filename: The default filename to initially show in the dialog’s edit field. Only the naked filename (with no path) will be shown. To ensure that
the dialog is properly shown, ensure that no illegal characters are present (such as /<|:“).

Examples:
 
C:\My Pictures\Default Image Name.gif ; Both RootDir and Filename are present.
C:\My Pictures ; Only RootDir is present.
My Pictures ; Only RootDir is present, and it ' s relative to the current working directory.
My File ; Only Filename is present (but if ”My File” exists as a folder, it is assumed to be RootDir).
 
• Prompt Text displayed in the window to instruct the user what to do. If omitted or blank, it will default to “Select File - %A_SCRIPTNAME%”
(i.e. the name of the current script).

• Filter Indicates which types of files are shown by the dialog.

– Example: Documents (*.txt)


– Example: Audio (*.wav; *.mp2; *.mp3)

If omitted, the filter defaults to All Files (*.*). An option for Text Documents (*.txt) will also be available in the dialog’s “files of type” menu.

Otherwise, the filter uses the indicated string but also provides an option for All Files (*.*) in the dialog’s “files of type” drop-down list. To include
more than one file extension in the filter, separate them with semicolons as illustrated in the example above.

Remarks

If the user didn’t select anything (e.g. pressed CANCEL), OutputVar is made blank.

If multi-select is not in effect, OutputVar is set to the full path and name of the single file chosen by the user.

80
If the M option (multi-select) is in effect, OutputVar is set to a list of items, each of which except the last is followed by a linefeed (‘n) character. The first
item in the list is the path that contains all the selected files (this path will end in a backslash only if it is a root folder such as C:\). The other items are
the selected filenames (without path). For example:
 
C:\My Documents\New Folder [this is the path in which all the files below reside]
test1.txt [these are the naked filenames: no path info]
test2.txt
... etc.
 
When multi-select is in effect, the sum of the lengths of the selected filenames is limited to 64 KB. Although this is typically enough to hold several
thousand files, OutputVar will be made blank if the limit is exceeded.

A GUI window may display a modal file-selection dialog by means of Gui +OwnDialogs. A modal dialog prevents the user from interacting with the GUI
window until the dialog is dismissed.

Known limitation: A timer that launches during the display of a FileSelectFile dialog will postpone the effect of the user’s clicks inside the dialog until
after the timer finishes. To work around this, avoid using timers whose subroutines take a long time to finish, or disable all timers during the dialog:
 
Thread, NoTimers
FileSelectFile, OutputVar
Thread, NoTimers, false
 
 
ResultType Line::FileSelectFile(LPTSTR aOptions, LPTSTR aWorkingDir, LPTSTR aGreeting, LPTSTR aFilter)
// Since other script threads can interrupt this command while it ' s running, it ' s important that
// this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
// This is because an interrupting thread usually changes the values to something inappropriate for this
,→ thread.
{
Var &output_var = *OUTPUT_VAR; // Fix for v1.0.45.01: Must be resolved and saved early. See comment above
,→ .
if (g_nFileDialogs >= MAX_FILEDIALOGS)
{
// Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
return LineError(_T(”The maximum number of File Dialogs has been reached.”));
}

// Large in case more than one file is allowed to be selected.


// The call to GetOpenFileName() may fail if the first character of the buffer isn ' t NULL
// because it then thinks the buffer contains the default filename, which if it ' s uninitialized
// may be a string that ' s too long.
TCHAR file_buf[65535] = _T(””); // Set default.

TCHAR working_dir[MAX_PATH];
if (!aWorkingDir || !*aWorkingDir)
*working_dir = ' \0 ' ;
else
{
tcslcpy(working_dir, aWorkingDir, _countof(working_dir));
// v1.0.43.10: Support CLSIDs such as:
// My Computer ::{20d04fe0-3aea-1069-a2d8-08002b30309d}
// My Documents ::{450d8fba-ad25-11d0-98a8-0800361b1103}
// Also support optional subdirectory appended to the CLSID.
// Neither SetCurrentDirectory() nor GetFileAttributes() directly supports CLSIDs, so rely on other
,→ means
// to detect whether a CLSID ends in a directory vs. filename.
bool is_directory, is_clsid;
if (is_clsid = !_tcsncmp(working_dir, _T(”::{”), 3))
{
LPTSTR end_brace;
if (end_brace = _tcschr(working_dir, ' } ' ))
is_directory = !end_brace[1] // First ' } ' is also the last char in string, so it ' s naked CLSID
,→ (so assume directory).

81
|| working_dir[_tcslen(working_dir) - 1] == ' \\ ' ; // Or path ends in backslash.
else // Badly formatted clsid.
is_directory = true; // Arbitrary default due to rarity.
}
else // Not a CLSID.
{
DWORD attr = GetFileAttributes(working_dir);
is_directory = (attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY);
}
if (!is_directory)
{
// Above condition indicates it ' s either an existing file that ' s not a folder, or a nonexistent
// folder/filename. In either case, it seems best to assume it ' s a file because the user may want
// to provide a default SAVE filename, and it would be normal for such a file not to already exist
,→ .
LPTSTR last_backslash;
if (last_backslash = _tcsrchr(working_dir, ' \\ ' ))
{
tcslcpy(file_buf, last_backslash + 1, _countof(file_buf)); // Set the default filename.
*last_backslash = ' \0 ' ; // Make the working directory just the file ' s path.
}
else // The entire working_dir string is the default file (unless this is a clsid).
if (!is_clsid)
{
tcslcpy(file_buf, working_dir, _countof(file_buf));
*working_dir = ' \0 ' ; // This signals it to use the default directory.
}
//else leave working_dir set to the entire clsid string in case it ' s somehow valid.
}
// else it is a directory, so just leave working_dir set as it was initially.
}

TCHAR greeting[1024];
if (aGreeting && *aGreeting)
tcslcpy(greeting, aGreeting, _countof(greeting));
else
// Use a more specific title so that the dialogs of different scripts can be distinguished
// from one another, which may help script automation in rare cases:
sntprintf(greeting, _countof(greeting), _T(”Select File - %s”), g_script.mFileName);

// The filter must be terminated by two NULL characters. One is explicit, the other automatic:
TCHAR filter[1024] = _T(””), pattern[1024] = _T(””); // Set default.
if (*aFilter)
{
LPTSTR pattern_start = _tcschr(aFilter, ' ( ' );
if (pattern_start)
{
// Make pattern a separate string because we want to remove any spaces from it.
// For example, if the user specified Documents (*.txt; *.doc), the space after
// the semicolon should be removed for the pattern string itself but not from
// the displayed version of the pattern:
tcslcpy(pattern, ++pattern_start, _countof(pattern));
LPTSTR pattern_end = _tcsrchr(pattern, ' ) ' ); // strrchr() in case there are other literal
,→ parentheses.
if (pattern_end)
*pattern_end = ' \0 ' ; // If parentheses are empty, this will set pattern to be the empty
,→ string.
else // no closing paren, so set to empty string as an indicator:
*pattern = ' \0 ' ;

82
}
else // No open-paren, so assume the entire string is the filter.
tcslcpy(pattern, aFilter, _countof(pattern));
if (*pattern)
{
// Remove any spaces present in the pattern, such as a space after every semicolon
// that separates the allowed file extensions. The API docs specify that there
// should be no spaces in the pattern itself, even though it ' s okay if they exist
// in the displayed name of the file-type:
StrReplace(pattern, _T(” ”), _T(””), SCS_SENSITIVE);
// Also include the All Files (*.*) filter, since there doesn ' t seem to be much
// point to making this an option. This is because the user could always type
// *.* and press ENTER in the filename field and achieve the same result:
sntprintf(filter, _countof(filter), _T(”%s%c%s%cAll Files (*.*)%c*.*%c”)
, aFilter, ' \0 ' , pattern, ' \0 ' , ' \0 ' , ' \0 ' ); // The final ' \0 ' double-terminates by virtue of
,→ the fact that sntprintf() itself provides a final terminator.
}
else
*filter = ' \0 ' ; // It will use a standard default below.
}

OPENFILENAME ofn = {0};


// OPENFILENAME_SIZE_VERSION_400 must be used for 9x/NT otherwise the dialog will not appear!
// MSDN: ”In an application that is compiled with WINVER and _WIN32_WINNT >= 0x0500, use
// OPENFILENAME_SIZE_VERSION_400 for this member. Windows 2000/XP: Use sizeof(OPENFILENAME)
// for this parameter.”
ofn.lStructSize = g_os.IsWin2000orLater() ? sizeof(OPENFILENAME) : OPENFILENAME_SIZE_VERSION_400;
ofn.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used instead of main window since no need to
,→ have main window forced into the background for this.
ofn.lpstrTitle = greeting;
ofn.lpstrFilter = *filter ? filter : _T(”All Files (*.*)\0*.*\0Text Documents (*.txt)\0*.txt\0”);
ofn.lpstrFile = file_buf;
ofn.nMaxFile = _countof(file_buf) - 1; // -1 to be extra safe.
// Specifying NULL will make it default to the last used directory (at least in Win2k):
ofn.lpstrInitialDir = *working_dir ? working_dir : NULL;

// Note that the OFN_NOCHANGEDIR flag is ineffective in some cases, so we ' ll use a custom
// workaround instead. MSDN: ”Windows NT 4.0/2000/XP: This flag is ineffective for GetOpenFileName.”
// In addition, it does not prevent the CWD from changing while the user navigates from folder to
// folder in the dialog, except perhaps on Win9x.

// For v1.0.25.05, the new ”M” letter is used for a new multi-select method since the old multi-select
// is faulty in the following ways:
// 1) If the user selects a single file in a multi-select dialog, the result is inconsistent: it
// contains the full path and name of that single file rather than the folder followed by the
// single file name as most users would expect. To make matters worse, it includes a linefeed
// after that full path in name, which makes it difficult for a script to determine whether
// only a single file was selected.
// 2) The last item in the list is terminated by a linefeed, which is not as easily used with a
// parsing loop as shown in example in the help file.
bool always_use_save_dialog = false; // Set default.
bool new_multi_select_method = false; // Set default.
switch (ctoupper(*aOptions))
{
case ' M ' : // Multi-select.
++aOptions;
new_multi_select_method = true;
break;
case ' S ' : // Have a ”Save” button rather than an ”Open” button.
++aOptions;

83
always_use_save_dialog = true;
break;
}

int options = ATOI(aOptions);


// v1.0.43.09: OFN_NODEREFERENCELINKS is now omitted by default because most people probably want a click
// on a shortcut to navigate to the shortcut ' s target rather than select the shortcut and end the dialog.
ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER; // OFN_HIDEREADONLY: Hides the Read Only check box.
if (options & 0x20) // v1.0.43.09.
ofn.Flags |= OFN_NODEREFERENCELINKS;
if (options & 0x10)
ofn.Flags |= OFN_OVERWRITEPROMPT;
if (options & 0x08)
ofn.Flags |= OFN_CREATEPROMPT;
if (new_multi_select_method || (options & 0x04))
ofn.Flags |= OFN_ALLOWMULTISELECT;
if (options & 0x02)
ofn.Flags |= OFN_PATHMUSTEXIST;
if (options & 0x01)
ofn.Flags |= OFN_FILEMUSTEXIST;

// At this point, we know a dialog will be displayed. See macro ' s comments for details:
DIALOG_PREP
POST_AHK_DIALOG(0) // Do this only after the above. Must pass 0 for timeout in this case.

++g_nFileDialogs;
// Below: OFN_CREATEPROMPT doesn ' t seem to work with GetSaveFileName(), so always
// use GetOpenFileName() in that case:
BOOL result = (always_use_save_dialog || ((ofn.Flags & OFN_OVERWRITEPROMPT) && !(ofn.Flags &
,→ OFN_CREATEPROMPT)))
? GetSaveFileName(&ofn) : GetOpenFileName(&ofn);
--g_nFileDialogs;

DIALOG_END

// Both GetOpenFileName() and GetSaveFileName() change the working directory as a side-effect


// of their operation. The below is not a 100% workaround for the problem because even while
// a new quasi-thread is running (having interrupted this one while the dialog is still
// displayed), the dialog is still functional, and as a result, the dialog changes the
// working directory every time the user navigates to a new folder.
// This is only needed when the user pressed OK, since the dialog auto-restores the
// working directory if CANCEL is pressed or the window was closed by means other than OK.
// UPDATE: No, it ' s needed for CANCEL too because GetSaveFileName/GetOpenFileName will restore
// the working dir to the wrong dir if the user changed it (via SetWorkingDir) while the
// dialog was displayed.
// Restore the original working directory so that any threads suspended beneath this one,
// and any newly launched ones if there aren ' t any suspended threads, will have the directory
// that the user expects. NOTE: It ' s possible for g_WorkingDir to have changed via the
// SetWorkingDir command while the dialog was displayed (e.g. a newly launched quasi-thread):
if (*g_WorkingDir)
SetCurrentDirectory(g_WorkingDir);

if (!result) // User pressed CANCEL vs. OK to dismiss the dialog or there was a problem displaying it.
{
// It seems best to clear the variable in these cases, since this is a scripting
// language where performance is not the primary goal. So do that and return OK,
// but leave ErrorLevel set to ERRORLEVEL_ERROR.
if (output_var.Assign() != OK)
return FAIL;
return CommDlgExtendedError() ? SetErrorLevelOrThrow() // An error occurred.

84
: g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // User pressed CANCEL, so never throw an exception.
}
else
g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate that the user pressed OK vs. CANCEL.

if (ofn.Flags & OFN_ALLOWMULTISELECT)


{
LPTSTR cp;
if (new_multi_select_method) // v1.0.25.05+ method.
{
// If the first terminator in file_buf is also the last, the user selected only
// a single file:
size_t length = _tcslen(file_buf);
if (!file_buf[length + 1]) // The list contains only a single file (full path and name).
{
// v1.0.25.05: To make the result of selecting one file the same as selecting multiple files
// -- and thus easier to work with in a script -- convert the result into the multi-file
// format (folder as first item and naked filename as second):
if (cp = _tcsrchr(file_buf, ' \\ ' ))
{
*cp = ' \n ' ;
// If the folder is the root folder, add a backslash so that selecting a single
// file yields the same reported folder as selecting multiple files. One reason
// for doing it this way is that SetCurrentDirectory() requires a backslash after
// a root folder to succeed. This allows a script to use SetWorkingDir to change
// to the selected folder before operating on each of the selected/naked filenames.
if (cp - file_buf == 2 && cp[-1] == ' : ' ) // e.g. ”C:”
{
tmemmove(cp + 1, cp, _tcslen(cp) + 1); // Make room to insert backslash (since only
,→ one file was selected, the buf is large enough).
*cp = ' \\ ' ;
}
}
}
else // More than one file was selected.
{
// Use the same method as the old multi-select format except don ' t provide a
// linefeed after the final item. That final linefeed would make parsing via
// a parsing loop more complex because a parsing loop would see a blank item
// at the end of the list:
for (cp = file_buf;;)
{
for (; *cp; ++cp); // Find the next terminator.
if (!cp[1]) // This is the last file because it ' s double-terminated, so we ' re done.
break;
*cp = ' \n ' ; // Replace zero-delimiter with a visible/printable delimiter, for the user.
}
}
}
else // Old multi-select method is in effect (kept for backward compatibility).
{
// Replace all the zero terminators with a delimiter, except the one for the last file
// (the last file should be followed by two sequential zero terminators).
// Use a delimiter that can ' t be confused with a real character inside a filename, i.e.
// not a comma. We only have room for one without getting into the complexity of having
// to expand the string, so \r\n is disqualified for now.
for (cp = file_buf;;)
{
for (; *cp; ++cp); // Find the next terminator.
*cp = ' \n ' ; // Replace zero-delimiter with a visible/printable delimiter, for the user.

85
if (!cp[1]) // This is the last file because it ' s double-terminated, so we ' re done.
break;
}
}
}
return output_var.Assign(file_buf);
}
 

86
FileSelectFolder

Displays a standard dialog that allows the user to select a folder.


 
FileSelectFolder, OutputVar [, StartingFolder, Options, Prompt]
 
Parameters

• OutputVar The name of the variable in which to store the user’s selected folder. This will be made blank if the user cancels the dialog (i.e. does
not wish to select a folder). If the user selects a root directory (such as C:\), OutputVar will contain a trailing backslash.

• StartingFolder If blank or omitted, the dialog’s initial selection will be the user’s My Documents folder (or possibly My Computer). A CLSID folder
such as ::{20d04fe0-3aea-1069-a2d8-08002b30309d} (i.e. My Computer) may be specified start navigation at a specific special folder.

Otherwise, the most common usage of this parameter is an asterisk followed immediately by the absolute path of the drive or folder to be initially
selected. For example, *C:\ would initially select the C drive. Similarly, *C:\My Folder would initially select that particular folder.

The asterisk indicates that the user is permitted to navigate upward (closer to the root) from the starting folder. Without the asterisk, the user
would be forced to select a folder inside StartingFolder (or StartingFolder itself). One benefit of omitting the asterisk is that StartingFolder is
initially shown in a tree-expanded state, which may save the user from having to click the first plus sign.

If the asterisk is present, upward navigation may optionally be restricted to a folder other than Desktop. This is done by preceding the asterisk
with the absolute path of the uppermost folder followed by exactly one space or tab. In the following example, the user would not be allowed to
navigate any higher than C:\My Folder (but the initial selection would be C:\My Folder\Projects):

C:\My Folder *C:\My Folder\Projects

• Options One of the following numbers:

0: The options below are all disabled (except on Windows 2000, where the “make new folder” button might appear anyway).

1 (default): A button is provided that allows the user to create new folders.

Add 2 to the above number to provide an edit field that allows the user to type the name of a folder. For example, a value of 3 for this parameter
provides both an edit field and a “make new folder” button.

Add 4 to the above number to omit the BIF_NEWDIALOGSTYLE property. Adding 4 ensures that FileSelectFolder will work properly even in a
Preinstallation Environment like WinPE or BartPE. However, this prevents the appearance of a “make new folder” button, at least on Windows
XP. [“4” requires v1.0.48+]

If the user types an invalid folder name in the edit field, OutputVar will be set to the folder selected in the navigation tree rather than what the user
entered, at least on Windows XP.

This parameter can be an expression.

• Prompt Text displayed in the window to instruct the user what to do. If omitted or blank, it will default to “Select Folder - %A_SCRIPTNAME%”
(i.e. the name of the current script).

Remarks

A GUI window may display a modal folder-selection dialog by means of Gui +OwnDialogs. A modal dialog prevents the user from interacting with the
GUI window until the dialog is dismissed.

Known limitation: A timer that launches during the display of a FileSelectFolder dialog will postpone the effect of the user’s clicks inside the dialog until
after the timer finishes. To work around this, avoid using timers whose subroutines take a long time to finish, or disable all timers during the dialog:
 
Thread, NoTimers
FileSelectFolder, OutputVar,, 3
Thread, NoTimers, false
 
 
int CALLBACK FileSelectFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if (uMsg == BFFM_INITIALIZED) // Caller has ensured that lpData isn ' t NULL by having set a valid lParam
,→ value.
SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
// In spite of the quote below, the behavior does not seem to vary regardless of what value is returned
// upon receipt of BFFM_VALIDATEFAILED, at least on XP. But in case it matters on other OSes, preserve
// compatibility with versions older than 1.0.36.03 by keeping the dialog displayed even if the user
,→ enters

87
// an invalid folder:
// MSDN: ”Returns zero except in the case of BFFM_VALIDATEFAILED. For that flag, returns zero to dismiss
// the dialog or nonzero to keep the dialog displayed.”
return uMsg == BFFM_VALIDATEFAILED; // i.e. zero should be returned in almost every case.
}

ResultType Line::FileSelectFolder(LPTSTR aRootDir, LPTSTR aOptions, LPTSTR aGreeting)


// Since other script threads can interrupt this command while it ' s running, it ' s important that
// the command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
// This is because an interrupting thread usually changes the values to something inappropriate for this
,→ thread.
{
Var &output_var = *OUTPUT_VAR; // Must be resolved early. See comment above.
if (!output_var.Assign()) // Initialize the output variable.
return FAIL;

if (g_nFolderDialogs >= MAX_FOLDERDIALOGS)


{
// Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
return LineError(_T(”The maximum number of Folder Dialogs has been reached.”));
}

LPMALLOC pMalloc;
if (SHGetMalloc(&pMalloc) != NOERROR) // Initialize
return SetErrorLevelOrThrow();

// v1.0.36.03: Support initial folder, which is different than the root folder because the root only
// controls the origin point (above which the control cannot navigate).
LPTSTR initial_folder;
TCHAR root_dir[MAX_PATH*2 + 5]; // Up to two paths might be present inside, including an asterisk and
,→ spaces between them.
tcslcpy(root_dir, aRootDir, _countof(root_dir)); // Make a modifiable copy.
if (initial_folder = _tcschr(root_dir, ' * ' ))
{
*initial_folder = ' \0 ' ; // Terminate so that root_dir becomes an isolated string.
// Must eliminate the trailing whitespace or it won ' t work. However, only up to one space or tab
// so that path names that really do end in literal spaces can be used:
if (initial_folder > root_dir && IS_SPACE_OR_TAB(initial_folder[-1]))
initial_folder[-1] = ' \0 ' ;
// In case absolute paths can ever have literal leading whitespace, preserve that whitespace
// by incrementing by only one and not calling omit_leading_whitespace(). This has been documented.
++initial_folder;
}
else
initial_folder = NULL;
if (!*(omit_leading_whitespace(root_dir))) // Count all-whitespace as a blank string, but retain leading
,→ whitespace if there is also non-whitespace inside.
*root_dir = ' \0 ' ;

BROWSEINFO bi;
if (initial_folder)
{
bi.lpfn = FileSelectFolderCallback;
bi.lParam = (LPARAM)initial_folder; // Used by the callback above.
}
else
bi.lpfn = NULL; // It will ignore the value of bi.lParam when lpfn is NULL.

if (*root_dir)
{

88
IShellFolder *pDF;
if (SHGetDesktopFolder(&pDF) == NOERROR)
{
LPITEMIDLIST pIdl = NULL;
ULONG chEaten;
ULONG dwAttributes;
#ifdef UNICODE
pDF->ParseDisplayName(NULL, NULL, root_dir, &chEaten, &pIdl, &dwAttributes);
#else
OLECHAR olePath[MAX_PATH]; // wide-char version of path name
ToWideChar(root_dir, olePath, MAX_PATH); // Dest. size is in wchars, not bytes.
pDF->ParseDisplayName(NULL, NULL, olePath, &chEaten, &pIdl, &dwAttributes);
#endif
pDF->Release();
bi.pidlRoot = pIdl;
}
}
else // No root directory.
bi.pidlRoot = NULL; // Make it use ”My Computer” as the root dir.

int iImage = 0;
bi.iImage = iImage;
bi.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used rather than main window since no need to
,→ have main window forced into the background by this.
TCHAR greeting[1024];
if (aGreeting && *aGreeting)
tcslcpy(greeting, aGreeting, _countof(greeting));
else
sntprintf(greeting, _countof(greeting), _T(”Select Folder - %s”), g_script.mFileName);
bi.lpszTitle = greeting;

DWORD options = *aOptions ? ATOI(aOptions) : FSF_ALLOW_CREATE;


bi.ulFlags =
((options & FSF_NONEWDIALOG) ? 0 : BIF_NEWDIALOGSTYLE) // v1.0.48: Added to support
,→ BartPE/WinPE.
| ((options & FSF_ALLOW_CREATE) ? 0 : BIF_NONEWFOLDERBUTTON)
| ((options & FSF_EDITBOX) ? BIF_EDITBOX : 0);

TCHAR Result[2048];
bi.pszDisplayName = Result; // This will hold the user ' s choice.

// At this point, we know a dialog will be displayed. See macro ' s comments for details:
DIALOG_PREP
POST_AHK_DIALOG(0) // Do this only after the above. Must pass 0 for timeout in this case.

++g_nFolderDialogs;
LPITEMIDLIST lpItemIDList = SHBrowseForFolder(&bi); // Spawn Dialog
--g_nFolderDialogs;

DIALOG_END
if (!lpItemIDList)
// Due to rarity and because there doesn ' t seem to be any way to detect it,
// no exception is thrown when the function fails. Instead, we just assume
// that the user pressed CANCEL (which should not be treated as an error):
return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);

*Result = ' \0 ' ; // Reuse this var, this time to old the result of the below:
SHGetPathFromIDList(lpItemIDList, Result);
pMalloc->Free(lpItemIDList);
pMalloc->Release();

89
g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
return output_var.Assign(Result);
}
 

90
FileSetAttrib

Changes the attributes of one or more files or folders. Wildcards are supported.
 
FileSetAttrib, Attributes [, FilePattern, OperateOnFolders?, Recurse?]
 
Parameters

• Attributes The attributes to change (see Remarks).

• FilePattern The name of a single file or folder, or a wildcard pattern such as C:\Temp\*.tmp. FilePattern is assumed to be in %A_WorkingDir% if
an absolute path isn’t specified.

If omitted, the current file of the innermost enclosing File-Loop will be used instead.

• OperateOnFolders?

– 0 (default) Folders are not operated upon (only files).


– 1 All files and folders that match the wildcard pattern are operated upon.
– 2 Only folders are operated upon (no files).

Note: If FilePattern is a single folder rather than a wildcard pattern, it will always be operated upon regardless of this setting.

This parameter can be an expression.

• Recurse? 0 (default) Subfolders are not recursed into.

1 Subfolders are recursed into so that files and folders contained therein are operated upon if they match FilePattern. All subfolders will be
recursed into, not just those whose names match FilePattern. However, files and folders with a complete path name longer than 259 characters
are skipped over as though they do not exist. Such files are rare because normally, the operating system does not allow their creation.

This parameter can be an expression.

Remarks

The Attributes parameter consists of a collection of operators and attribute letters.

Operators:

• +: Turn on the attribute


• -: Turn off the attribute
• ˆ: Toggle the attribute (set it to the opposite value of what it is now)

Attribute letters:

• R = READONLY
• A = ARCHIVE
• S = SYSTEM
• H = HIDDEN
• N = NORMAL (this is valid only when used without any other attributes)
• O = OFFLINE
• T = TEMPORARY

Note: Currently, the compression state of files cannot be changed with this command.
 
int Line::FileSetAttrib(LPTSTR aAttributes, LPTSTR aFilePattern, FileLoopModeType aOperateOnFolders
, bool aDoRecurse, bool aCalledRecursively)
// Returns the number of files and folders that could not be changed due to an error.
{
if (!aCalledRecursively) // i.e. Only need to do this if we ' re not called by ourself:
{
if (!*aFilePattern)
{
g->LastError = ERROR_INVALID_PARAMETER;
SetErrorLevelOrThrow();
return 0;
}
if (aOperateOnFolders == FILE_LOOP_INVALID) // In case runtime dereference of a var was an invalid
,→ value.

91
aOperateOnFolders = FILE_LOOP_FILES_ONLY; // Set default.
g->LastError = 0; // Set default. Overridden only when a failure occurs.
}

if (_tcslen(aFilePattern) >= MAX_PATH) // Checked early to simplify other things below.


{
g->LastError = ERROR_BUFFER_OVERFLOW;
SetErrorLevelOrThrow();
return 0;
}

// Related to the comment at the top: Since the script subroutine that resulted in the call to
// this function can be interrupted during our MsgSleep(), make a copy of any params that might
// currently point directly to the deref buffer. This is done because their contents might
// be overwritten by the interrupting subroutine:
TCHAR attributes[64];
tcslcpy(attributes, aAttributes, _countof(attributes));

// Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
// than 256 or so, even if the pattern would match files whose names are short enough to be legal.
// Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
// MSDN confirms this in a vague way: ”In the ANSI version of FindFirstFile(), [plpFileName] is
// limited to MAX_PATH characters.”
TCHAR file_pattern[MAX_PATH], file_path[MAX_PATH]; // Giving +3 extra for ”*.*” seems fairly pointless
,→ because any files that actually need that extra room would fail to be retrieved by FindFirst/Next
,→ due to their inability to support paths much over 256.
_tcscpy(file_pattern, aFilePattern); // Make a copy in case of overwrite of deref buf during
,→ LONG_OPERATION/MsgSleep.
_tcscpy(file_path, aFilePattern); // An earlier check has ensured these won ' t overflow.

size_t file_path_length; // The length of just the path portion of the filespec.
LPTSTR last_backslash = _tcsrchr(file_path, ' \\ ' );
if (last_backslash)
{
// Remove the filename and/or wildcard part. But leave the trailing backslash on it for
// consistency with below:
*(last_backslash + 1) = ' \0 ' ;
file_path_length = _tcslen(file_path);
}
else // Use current working directory, e.g. if user specified only *.*
{
*file_path = ' \0 ' ;
file_path_length = 0;
}
LPTSTR append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only
,→ once. This is where the changing part gets appended.
size_t space_remaining = _countof(file_path) - file_path_length - 1; // Space left in file_path for the
,→ changing part.

// For use with aDoRecurse, get just the naked file name/pattern:
LPTSTR naked_filename_or_pattern = _tcsrchr(file_pattern, ' \\ ' );
if (naked_filename_or_pattern)
++naked_filename_or_pattern;
else
naked_filename_or_pattern = file_pattern;

if (!StrChrAny(naked_filename_or_pattern, _T(”?*”)))
// Since no wildcards, always operate on this single item even if it ' s a folder.
aOperateOnFolders = FILE_LOOP_FILES_AND_FOLDERS;

92
LPTSTR cp;
enum attrib_modes {ATTRIB_MODE_NONE, ATTRIB_MODE_ADD, ATTRIB_MODE_REMOVE, ATTRIB_MODE_TOGGLE};
attrib_modes mode = ATTRIB_MODE_NONE;

LONG_OPERATION_INIT
int failure_count = 0;
WIN32_FIND_DATA current_file;
HANDLE file_search = FindFirstFile(file_pattern, &current_file);

if (file_search != INVALID_HANDLE_VALUE)
{
do
{
// Since other script threads can interrupt during LONG_OPERATION_UPDATE, it ' s important that
// this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
// possible. This is because an interrupting thread usually changes the values to something
// inappropriate for this thread.
LONG_OPERATION_UPDATE

if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)


{
if (current_file.cFileName[0] == ' . ' && (!current_file.cFileName[1] // Relies on short-
,→ circuit boolean order.
|| current_file.cFileName[1] == ' . ' && !current_file.cFileName[2]) //
// Regardless of whether this folder will be recursed into, this folder ' s own attributes
// will not be affected when the mode is files-only:
|| aOperateOnFolders == FILE_LOOP_FILES_ONLY)
continue; // Never operate upon or recurse into these.
}
else // It ' s a file, not a folder.
if (aOperateOnFolders == FILE_LOOP_FOLDERS_ONLY)
continue;

if (_tcslen(current_file.cFileName) > space_remaining)


{
// v1.0.45.03: Don ' t even try to operate upon truncated filenames in case they accidentally
// match the name of a real/existing file.
g->LastError = ERROR_BUFFER_OVERFLOW;
++failure_count;
continue;
}
// Otherwise, make file_path be the filespec of the file to operate upon:
_tcscpy(append_pos, current_file.cFileName); // Above has ensured this won ' t overflow.

for (cp = attributes; *cp; ++cp)


{
switch (ctoupper(*cp))
{
case ' + ' : mode = ATTRIB_MODE_ADD; break;
case ' - ' : mode = ATTRIB_MODE_REMOVE; break;
case ' ˆ ' : mode = ATTRIB_MODE_TOGGLE; break;
// Note that D (directory) and C (compressed) are currently not supported:
case ' R ' :
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_READONLY;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_READONLY;
break;

93
case 'A ':

if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_ARCHIVE;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_ARCHIVE;
break;
case ' S ' :
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_SYSTEM;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_SYSTEM;
break;
case ' H ' :
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_HIDDEN;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_HIDDEN;
break;
case ' N ' : // Docs say it ' s valid only when used alone. But let the API handle it if this is
,→ not so.
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_NORMAL;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_NORMAL;
break;
case ' O ' :
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_OFFLINE;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_OFFLINE;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_OFFLINE;
break;
case ' T ' :
if (mode == ATTRIB_MODE_ADD)
current_file.dwFileAttributes |= FILE_ATTRIBUTE_TEMPORARY;
else if (mode == ATTRIB_MODE_REMOVE)
current_file.dwFileAttributes &= ˜FILE_ATTRIBUTE_TEMPORARY;
else if (mode == ATTRIB_MODE_TOGGLE)
current_file.dwFileAttributes ˆ= FILE_ATTRIBUTE_TEMPORARY;
break;
}
}

if (!SetFileAttributes(file_path, current_file.dwFileAttributes))
{
g->LastError = GetLastError();
++failure_count;
}
} while (FindNextFile(file_search, &current_file));

FindClose(file_search);

94
} // if (file_search != INVALID_HANDLE_VALUE)

if (aDoRecurse && space_remaining > 2) // The space_remaining check ensures there ' s enough room to append
,→ ”*.*” (if not, just avoid recursing into it due to rarity).
{
// Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
// than 256 or so, even if the pattern would match files whose names are short enough to be legal.
// Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
// MSDN confirms this in a vague way: ”In the ANSI version of FindFirstFile(), [plpFileName] is
// limited to MAX_PATH characters.”
_tcscpy(append_pos, _T(”*.*”)); // Above has ensured this won ' t overflow.
file_search = FindFirstFile(file_path, &current_file);

if (file_search != INVALID_HANDLE_VALUE)
{
size_t pattern_length = _tcslen(naked_filename_or_pattern);
do
{
LONG_OPERATION_UPDATE
if (!(current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|| current_file.cFileName[0] == ' . ' && (!current_file.cFileName[1] // Relies on short-
,→ circuit boolean order.
|| current_file.cFileName[1] == ' . ' && !current_file.cFileName[2]) //
// v1.0.45.03: Skip over folders whose full-path-names are too long to be supported by the
,→ ANSI
// versions of FindFirst/FindNext. Without this fix, it might be possible for infinite
,→ recursion
// to occur (see PerformLoop() for more comments).
|| pattern_length + _tcslen(current_file.cFileName) >= space_remaining) // >= vs. > to
,→ reserve 1 for the backslash to be added between cFileName and
,→ naked_filename_or_pattern.
continue; // Never recurse into these.
// This will build the string CurrentDir+SubDir+FilePatternOrName.
// If FilePatternOrName doesn ' t contain a wildcard, the recursion
// process will attempt to operate on the originally-specified
// single filename or folder name if it occurs anywhere else in the
// tree, e.g. recursing C:\Temp\temp.txt would affect all occurrences
// of temp.txt both in C:\Temp and any subdirectories it might contain:
_stprintf(append_pos, _T(”%s\\%s”) // Above has ensured this won ' t overflow.
, current_file.cFileName, naked_filename_or_pattern);
failure_count += FileSetAttrib(attributes, file_path, aOperateOnFolders, aDoRecurse, true);
} while (FindNextFile(file_search, &current_file));
FindClose(file_search);
} // if (file_search != INVALID_HANDLE_VALUE)
} // if (aDoRecurse)

if (!aCalledRecursively) // i.e. Only need to do this if we ' re returning to top-level caller:


SetErrorLevelOrThrowInt(failure_count); // i.e. indicate success if there were no failures.
return failure_count;
}
 

95
FileSetTime

Changes the datetime stamp of one or more files or folders. Wildcards are supported.
 
FileSetTime [, YYYYMMDDHH24MISS, FilePattern, WhichTime, OperateOnFolders?, Recurse?]
 
Parameters

• YYYYMMDDHH24MISS If blank or omitted, it defaults to the current time. Otherwise, specify the time to use for the operation (see Remarks for
the format). Years prior to 1601 are not supported.

This parameter is an expression. Consequently, if multiple variables need to be concatenated to form a single timestamp, the dot operator should
be used instead of percent signs. For example: FileSetTime, Year . Month . Day, C:\My File.txt.

• FilePattern The name of a single file or folder, or a wildcard pattern such as C:\Temp\*.tmp. FilePattern is assumed to be in %A_WorkingDir% if
an absolute path isn’t specified.

If omitted, the current file of the innermost enclosing File-Loop will be used instead.

• WhichTime Which timestamp to set:

– M = Modification time (this is the default if the parameter is blank or omitted)


– C = Creation time
– A = Last access time

• OperateOnFolders?

– 0 (default) Folders are not operated upon (only files).


– 1 All files and folders that match the wildcard pattern are operated upon.
– 2 Only folders are operated upon (no files).

Note: If FilePattern is a single folder rather than a wildcard pattern, it will always be operated upon regardless of this setting.

This parameter can be an expression.

• Recurse? 0 (default) Subfolders are not recursed into.

1 Subfolders are recursed into so that files and folders contained therein are operated upon if they match FilePattern. All subfolders will be
recursed into, not just those whose names match FilePattern. However, files and folders with a complete path name longer than 259 characters
are skipped over as though they do not exist. Such files are rare because normally, the operating system does not allow their creation.

This parameter can be an expression.

Remarks

A file’s last access time might not be as precise on FAT16 & FAT32 volumes as it is on NTFS volumes.

The elements of the YYYYMMDDHH24MISS format are:

• YYYY: The 4-digit year


• MM: The 2-digit month (01-12)
• DD: The 2-digit day of the month (01-31)
• HH24: The 2-digit hour in 24-hour format (00-23). For example, 09 is 9am and 21 is 9pm.
• MI: The 2-digit minutes (00-59)
• SS: The 2-digit seconds (00-59)

If only a partial string is given for YYYYMMDDHH24MISS (e.g. 200403), any remaining element that has been omitted will be supplied with the following
default values:

• MM: Month 01
• DD: Day 01
• HH24: Hour 00
• MI: Minute 00
• SS: Second 00

The built-in variable A_Now contains the current local time in the above format. Similarly, A_NowUTC contains the current Coordinated Universal Time.

Note: Date-time values can be compared, added to, or subtracted from via EnvAdd and EnvSub. Also, it is best to not use greater-than or less-than to
compare times unless they are both the same string length. This is because they would be compared as numbers; for example, 20040201 is always
numerically less (but chronologically greater) than 200401010533. So instead use EnvSub to find out whether the amount of time between them is
positive or negative.

96
 
ResultType YYYYMMDDToSystemTime(LPTSTR aYYYYMMDD, SYSTEMTIME &aSystemTime, bool aDoValidate)
// Although aYYYYMMDD need not be terminated at the end of the YYYYMMDDHH24MISS string (as long as
// the string ' s capacity is at least 14), it should be terminated if only the leading part
// of the YYYYMMDDHH24MISS format is present.
// Caller must ensure that aYYYYMMDD is non-NULL. If aDoValidate is false, OK is always
// returned and aSystemTime might contain invalid elements. Otherwise, FAIL will be returned
// if the date and time contains any invalid elements, or if the year is less than 1601
// (Windows generally does not support earlier years).
{
// sscanf() is avoided because it adds 2 KB to the compressed EXE size.
TCHAR temp[16];
size_t length = _tcslen(aYYYYMMDD); // Use this rather than incrementing the pointer in case there are
,→ ever partial fields such as 20051 vs. 200501.

tcslcpy(temp, aYYYYMMDD, 5);


aSystemTime.wYear = _ttoi(temp);

if (length > 4) // It has a month component.


{
tcslcpy(temp, aYYYYMMDD + 4, 3);
aSystemTime.wMonth = _ttoi(temp); // Unlike ”struct tm”, SYSTEMTIME uses 1 for January, not 0.
// v1.0.48: Changed not to provide a default when month number is out-of-range.
// This allows callers like ”if var is time” to properly detect badly-formatted dates.
}
else
aSystemTime.wMonth = 1;

if (length > 6) // It has a day-of-month component.


{
tcslcpy(temp, aYYYYMMDD + 6, 3);
aSystemTime.wDay = _ttoi(temp);
}
else
aSystemTime.wDay = 1;

if (length > 8) // It has an hour component.


{
tcslcpy(temp, aYYYYMMDD + 8, 3);
aSystemTime.wHour = _ttoi(temp);
}
else
aSystemTime.wHour = 0; // Midnight.

if (length > 10) // It has a minutes component.


{
tcslcpy(temp, aYYYYMMDD + 10, 3);
aSystemTime.wMinute = _ttoi(temp);
}
else
aSystemTime.wMinute = 0;

if (length > 12) // It has a seconds component.


{
tcslcpy(temp, aYYYYMMDD + 12, 3);
aSystemTime.wSecond = _ttoi(temp);
}
else
aSystemTime.wSecond = 0;

97
aSystemTime.wMilliseconds = 0; // Always set to zero in this case.

// v1.0.46.07: Unlike the other date/time components, which are validated further below by the call to
// SystemTimeToFileTime(), ”month” must be validated in advance because it ' s used to access an array
// in the day-of-week code further below.
// v1.0.48.04: To fix FormatTime and possibly other things, don ' t return FAIL when month is out-of-range
,→ unless
// aDoValidate==false, and not even then (for maintainability) because a section further below handles it.
if (aSystemTime.wMonth < 1 || aSystemTime.wMonth > 12) // Month must be validated prior to accessing the
,→ array further below.
// Set an in-range default, which caller is likely to ignore if it passed true for aDoValidate
// because the validation further below will also detect the out-of-range month and return FAIL.
aSystemTime.wDayOfWeek = 1;
else // Month is in-range, which is necessary for the method below to work safely.
{
// Day-of-week code by Tomohiko Sakamoto:
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
int y = aSystemTime.wYear;
y -= aSystemTime.wMonth < 3;
aSystemTime.wDayOfWeek = (y + y/4 - y/100 + y/400 + t[aSystemTime.wMonth-1] + aSystemTime.wDay) % 7;
}

if (aDoValidate)
{
FILETIME ft;
// This will return failure if aYYYYMMDD contained any invalid elements, such as an
// explicit zero for the day of the month. It also reports failure if st.wYear is
// less than 1601, which for simplicity is enforced globally throughout the program
// since none of the Windows API calls seem to support earlier years.
return SystemTimeToFileTime(&aSystemTime, &ft) ? OK : FAIL;
// Above: The st.wDayOfWeek member is ignored by the above (but might be used by our caller), but
// that ' s okay because it shouldn ' t need validation.
}
return OK;
}

ResultType YYYYMMDDToFileTime(LPTSTR aYYYYMMDD, FILETIME &aFileTime)


{
SYSTEMTIME st;
YYYYMMDDToSystemTime(aYYYYMMDD, st, false); // ”false” because it ' s validated below.
// This will return failure if aYYYYMMDD contained any invalid elements, such as an
// explicit zero for the day of the month. It also reports failure if st.wYear is
// less than 1601, which for simplicity is enforced globally throughout the program
// since none of the Windows API calls seem to support earlier years.
return SystemTimeToFileTime(&st, &aFileTime) ? OK : FAIL; // The st.wDayOfWeek member is ignored.
}

int Line::FileSetTime(LPTSTR aYYYYMMDD, LPTSTR aFilePattern, TCHAR aWhichTime


, FileLoopModeType aOperateOnFolders, bool aDoRecurse, bool aCalledRecursively)
// Returns the number of files and folders that could not be changed due to an error.
// Current limitation: It will not recurse into subfolders unless their names also match
// aFilePattern.
{
if (!aCalledRecursively) // i.e. Only need to do this if we ' re not called by ourself:
{
if (!*aFilePattern)
{
SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);
return 0;
}

98
if (aOperateOnFolders == FILE_LOOP_INVALID) // In case runtime dereference of a var was an invalid
,→ value.
aOperateOnFolders = FILE_LOOP_FILES_ONLY; // Set default.
g->LastError = 0; // Set default. Overridden only when a failure occurs.
}

if (_tcslen(aFilePattern) >= MAX_PATH) // Checked early to simplify other things below.


{
SetErrorsOrThrow(true, ERROR_BUFFER_OVERFLOW);
return 0;
}

// Related to the comment at the top: Since the script subroutine that resulted in the call to
// this function can be interrupted during our MsgSleep(), make a copy of any params that might
// currently point directly to the deref buffer. This is done because their contents might
// be overwritten by the interrupting subroutine:
TCHAR yyyymmdd[64]; // Even do this one since its value is passed recursively in calls to self.
tcslcpy(yyyymmdd, aYYYYMMDD, _countof(yyyymmdd));
TCHAR file_pattern[MAX_PATH];
_tcscpy(file_pattern, aFilePattern); // An earlier check has ensured this won ' t overflow.

FILETIME ft, ftUTC;


if (*yyyymmdd)
{
// Convert the arg into the time struct as local (non-UTC) time:
if (!YYYYMMDDToFileTime(yyyymmdd, ft))
{
SetErrorsOrThrow(true);
return 0;
}
// Convert from local to UTC:
if (!LocalFileTimeToFileTime(&ft, &ftUTC))
{
SetErrorsOrThrow(true);
return 0;
}
}
else // User wants to use the current time (i.e. now) as the new timestamp.
GetSystemTimeAsFileTime(&ftUTC);

// This following section is very similar to that in FileSetAttrib and FileDelete:


TCHAR file_path[MAX_PATH]; // Giving +3 extra for ”*.*” seems fairly pointless because any files that
,→ actually need that extra room would fail to be retrieved by FindFirst/Next due to their inability
,→ to support paths much over 256.
_tcscpy(file_path, aFilePattern); // An earlier check has ensured this won ' t overflow.

size_t file_path_length; // The length of just the path portion of the filespec.
LPTSTR last_backslash = _tcsrchr(file_path, ' \\ ' );
if (last_backslash)
{
// Remove the filename and/or wildcard part. But leave the trailing backslash on it for
// consistency with below:
*(last_backslash + 1) = ' \0 ' ;
file_path_length = _tcslen(file_path);
}
else // Use current working directory, e.g. if user specified only *.*
{
*file_path = ' \0 ' ;
file_path_length = 0;
}

99
LPTSTR append_pos = file_path + file_path_length; // For performance, copy in the unchanging part only
,→ once. This is where the changing part gets appended.
size_t space_remaining = _countof(file_path) - file_path_length - 1; // Space left in file_path for the
,→ changing part.

// For use with aDoRecurse, get just the naked file name/pattern:
LPTSTR naked_filename_or_pattern = _tcsrchr(file_pattern, ' \\ ' );
if (naked_filename_or_pattern)
++naked_filename_or_pattern;
else
naked_filename_or_pattern = file_pattern;

if (!StrChrAny(naked_filename_or_pattern, _T(”?*”)))
// Since no wildcards, always operate on this single item even if it ' s a folder.
aOperateOnFolders = FILE_LOOP_FILES_AND_FOLDERS;

HANDLE hFile;
LONG_OPERATION_INIT
int failure_count = 0;
WIN32_FIND_DATA current_file;
HANDLE file_search = FindFirstFile(file_pattern, &current_file);

if (file_search != INVALID_HANDLE_VALUE)
{
do
{
// Since other script threads can interrupt during LONG_OPERATION_UPDATE, it ' s important that
// this command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes
// possible. This is because an interrupting thread usually changes the values to something
// inappropriate for this thread.
LONG_OPERATION_UPDATE

if (current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)


{
if (current_file.cFileName[0] == ' . ' && (!current_file.cFileName[1] // Relies on short-
,→ circuit boolean order.
|| current_file.cFileName[1] == ' . ' && !current_file.cFileName[2]) //
// Regardless of whether this folder will be recursed into, this folder ' s own timestamp
// will not be affected when the mode is files-only:
|| aOperateOnFolders == FILE_LOOP_FILES_ONLY)
continue; // Never operate upon or recurse into these.
}
else // It ' s a file, not a folder.
if (aOperateOnFolders == FILE_LOOP_FOLDERS_ONLY)
continue;

if (_tcslen(current_file.cFileName) > space_remaining)


{
// v1.0.45.03: Don ' t even try to operate upon truncated filenames in case they accidentally
// match the name of a real/existing file.
g->LastError = ERROR_BUFFER_OVERFLOW;
++failure_count;
continue;
}
// Otherwise, make file_path be the filespec of the file to operate upon:
_tcscpy(append_pos, current_file.cFileName); // Above has ensured this won ' t overflow.

// Open existing file. Uses CreateFile() rather than OpenFile for an expectation
// of greater compatibility for all files, and folder support too.
// FILE_FLAG_NO_BUFFERING might improve performance because all we ' re doing is

100
// changing one of the file ' s attributes. FILE_FLAG_BACKUP_SEMANTICS must be
// used, otherwise changing the time of a directory under NT and beyond will
// not succeed. Win95 (not sure about Win98/Me) does not support this, but it
// should be harmless to specify it even if the OS is Win95:
hFile = CreateFile(file_path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE
, (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING
, FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
g->LastError = GetLastError();
++failure_count;
continue;
}

switch (ctoupper(aWhichTime))
{
case ' C ' : // File ' s creation time.
if (!SetFileTime(hFile, &ftUTC, NULL, NULL))
{
g->LastError = GetLastError();
++failure_count;
}
break;
case ' A ' : // File ' s last access time.
if (!SetFileTime(hFile, NULL, &ftUTC, NULL))
{
g->LastError = GetLastError();
++failure_count;
}
break;
default: // ' M ' , unspecified, or some other value. Use the file ' s modification time.
if (!SetFileTime(hFile, NULL, NULL, &ftUTC))
{
g->LastError = GetLastError();
++failure_count;
}
}

CloseHandle(hFile);
} while (FindNextFile(file_search, &current_file));

FindClose(file_search);
} // if (file_search != INVALID_HANDLE_VALUE)

// This section is identical to that in FileSetAttrib() except for the recursive function
// call itself, so see comments there for details:
if (aDoRecurse && space_remaining > 2) // The space_remaining check ensures there ' s enough room to append
,→ ”*.*” (if not, just avoid recursing into it due to rarity).
{
// Testing shows that the ANSI version of FindFirstFile() will not accept a path+pattern longer
// than 256 or so, even if the pattern would match files whose names are short enough to be legal.
// Therefore, as of v1.0.25, there is also a hard limit of MAX_PATH on all these variables.
// MSDN confirms this in a vague way: ”In the ANSI version of FindFirstFile(), [plpFileName] is
// limited to MAX_PATH characters.”
_tcscpy(append_pos, _T(”*.*”)); // Above has ensured this won ' t overflow.
file_search = FindFirstFile(file_path, &current_file);

if (file_search != INVALID_HANDLE_VALUE)
{
size_t pattern_length = _tcslen(naked_filename_or_pattern);

101
do
{
LONG_OPERATION_UPDATE
if (!(current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|| current_file.cFileName[0] == ' . ' && (!current_file.cFileName[1] // Relies on short-
,→ circuit boolean order.
|| current_file.cFileName[1] == ' . ' && !current_file.cFileName[2]) //
// v1.0.45.03: Skip over folders whose full-path-names are too long to be supported by the
,→ ANSI
// versions of FindFirst/FindNext. Without this fix, it might be possible for infinite
,→ recursion
// to occur (see PerformLoop() for more comments).
|| pattern_length + _tcslen(current_file.cFileName) >= space_remaining) // >= vs. > to
,→ reserve 1 for the backslash to be added between cFileName and
,→ naked_filename_or_pattern.
continue;
_stprintf(append_pos, _T(”%s\\%s”) // Above has ensured this won ' t overflow.
, current_file.cFileName, naked_filename_or_pattern);
failure_count += FileSetTime(yyyymmdd, file_path, aWhichTime, aOperateOnFolders, aDoRecurse,
,→ true);
} while (FindNextFile(file_search, &current_file));
FindClose(file_search);
} // if (file_search != INVALID_HANDLE_VALUE)
} // if (aDoRecurse)

if (!aCalledRecursively) // i.e. Only need to do this if we ' re returning to top-level caller:


SetErrorLevelOrThrowInt(failure_count); // i.e. indicate success if there were no failures.
return failure_count;
}
 

102
IniDelete

Deletes a value from a standard format .ini file.


 
IniDelete, Filename, Section [, Key]
 
Parameters

• Filename The name of the .ini file, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

• Section The section name in the .ini file, which is the heading phrase that appears in square brackets (do not include the brackets in this parameter).

• Key The key name in the .ini file. If omitted, the entire Section will be deleted.

Remarks

A standard ini file looks like:


 
[SectionName]
Key=Value
 
 
ResultType Line::IniDelete(LPTSTR aFilespec, LPTSTR aSection, LPTSTR aKey)
// Note that aKey can be NULL, in which case the entire section will be deleted.
{
TCHAR szFileTemp[_MAX_PATH+1];
TCHAR *szFilePart;
// Get the fullpathname (ini functions need a full path)
GetFullPathName(aFilespec, _MAX_PATH, szFileTemp, &szFilePart);
BOOL result = WritePrivateProfileString(aSection, aKey, NULL, szFileTemp); // Returns zero on failure.
WritePrivateProfileString(NULL, NULL, NULL, szFileTemp); // Flush
return SetErrorLevelOrThrowBool(!result);
}
 

103
IniRead

Reads a value, section or list of section names from a standard format .ini file.
 
IniRead, OutputVar, Filename, Section, Key [, Default]
IniRead, OutputVarSection, Filename, Section
IniRead, OutputVarSectionNames, Filename
 
Parameters

• OutputVar The name of the variable in which to store the retrieved value. If the value cannot be retrieved, the variable is set to the value indicated
by the Default parameter (described below).

• OutputVarSection Omit the Key parameter to read an entire section. Comments and empty lines are omitted. Only the first 65,533 characters of
the section are retrieved.

• OutputVarSectionNames Omit the Key and Section parameters to retrieve a linefeed (‘n) delimited list of section names.

• Filename The name of the .ini file, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

• Section The section name in the .ini file, which is the heading phrase that appears in square brackets (do not include the brackets in this parameter).

• Key The key name in the .ini file.

• Default The value to store in OutputVar if the requested key is not found. If omitted, it defaults to the word ERROR. To store a blank value (empty
string), specify %A_Space%.

Remarks

The operating system automatically omits leading and trailing spaces/tabs from the retrieved string. To prevent this, enclose the string in single or double
quote marks. The outermost set of single or double quote marks is also omitted, but any spaces inside the quote marks are preserved.

Values longer than 65,535 characters are likely to yield inconsistent results.

A standard ini file looks like:


 
[SectionName]
Key=Value
 
 
ResultType Line::IniRead(LPTSTR aFilespec, LPTSTR aSection, LPTSTR aKey, LPTSTR aDefault)
{
if (!aDefault || !*aDefault)
aDefault = _T(”ERROR”); // This mirrors what AutoIt2 does for its default value.
TCHAR szFileTemp[_MAX_PATH+1];
TCHAR *szFilePart, *cp;
TCHAR szBuffer[65535] = _T(””); // Max ini file size is 65535 under 95
TCHAR szEmpty[] = _T(””);
// Get the fullpathname (ini functions need a full path):
GetFullPathName(aFilespec, _MAX_PATH, szFileTemp, &szFilePart);
if (*aKey)
{
// An access violation can occur if the following conditions are met:
// 1) aFilespec specifies a Unicode file.
// 2) aSection is a read-only string, either empty or containing only spaces.
//
// Debugging at the assembly level indicates that in this specific situation,
// it tries to write a zero at the end of aSection (which is already null-
// terminated).
//
// The second condition can ordinarily only be met if Section is omitted,
// since in all other cases aSection is writable. Although Section was a
// required parameter prior to revision 57, empty or blank section names
// are actually valid. Simply passing an empty writable buffer appears
// to work around the problem effectively:
if (!*aSection)
aSection = szEmpty;

104
GetPrivateProfileString(aSection, aKey, aDefault, szBuffer, _countof(szBuffer), szFileTemp);
}
else if (*aSection
? GetPrivateProfileSection(aSection, szBuffer, _countof(szBuffer), szFileTemp)
: GetPrivateProfileSectionNames(szBuffer, _countof(szBuffer), szFileTemp))
{
// Convert null-terminated array of null-terminated strings to newline-delimited list.
for (cp = szBuffer; ; ++cp)
if (!*cp)
{
if (!cp[1])
break;
*cp = ' \n ' ;
}
}
// The above function is supposed to set szBuffer to be aDefault if it can ' t find the
// file, section, or key. In other words, it always changes the contents of szBuffer.
return OUTPUT_VAR->Assign(szBuffer); // Avoid using the length the API reported because it might be
,→ inaccurate if the data contains any binary zeroes, or if the data is double-terminated, etc.
// Note: ErrorLevel is not changed by this command since the aDefault value is returned
// whenever there ' s an error.
}
 

105
IniWrite

Writes a value or section to a standard format .ini file.


 
IniWrite, Value, Filename, Section, Key
IniWrite, Pairs, Filename, Section
 
Parameters

• Value The string or number that will be written to the right of Key’s equal sign (=).

If the text is long, it can be broken up into several shorter lines by means of a continuation section, which might improve readability and maintain-
ability.

• Pairs The complete content of a section to write to the .ini file, excluding the [SectionName] header. Key must be omitted. Pairs must not contain
any blank lines. If the section already exists, everything up to the last key=value pair is overwritten. Pairs can contain lines without an equal sign
(=), but this may produce inconsistent results. Comments can be written to the file but are stripped out when they are read back by IniRead.

• Filename The name of the .ini file, which is assumed to be in %A_WorkingDir% if an absolute path isn’t specified.

• Section The section name in the .ini file, which is the heading phrase that appears in square brackets (do not include the brackets in this parameter).

• Key The key name in the .ini file.

Remarks

Values longer than 65,535 characters can be written to the file, but may produce inconsistent results as they usually cannot be read correctly by IniRead
or other applications.

A standard ini file looks like:


 
[SectionName]
Key=Value
 
New files are created in either the system’s default ANSI code page or UTF-16, depending on the version of AutoHotkey. UTF-16 files may appear to
begin with a blank line, as the first line contains the UTF-16 byte order mark. See below for a workaround.

Unicode: IniRead and IniWrite rely on the external functions GetPrivateProfileString and WritePrivateProfileString to read and write values. These
functions support Unicode only in UTF-16 files; all other files are assumed to use the system’s default ANSI code page. In Unicode scripts, IniWrite uses
UTF-16 for each new file. If this is undesired, ensure the file exists before calling IniWrite. For example:
 
FileAppend,, NonUnicode.ini, CP0 ; The last parameter is optional in most cases.
 
 
#ifdef UNICODE
static BOOL IniEncodingFix(LPWSTR aFilespec, LPWSTR aSection)
{
BOOL result = TRUE;
if (!DoesFilePatternExist(aFilespec))
{
HANDLE hFile;
DWORD dwWritten;

// Create a Unicode file. (UTF-16LE BOM)


hFile = CreateFile(aFilespec, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD cc = (DWORD)wcslen(aSection);
DWORD cb = (cc + 1) * sizeof(WCHAR);

aSection[cc] = '] '; // Temporarily replace the null-terminator.

// Write a UTF-16LE BOM to identify this as a Unicode file.


// Write [%aSection%] to prevent WritePrivateProfileString from inserting an empty line (for
,→ consistency and style).
if ( !WriteFile(hFile, L”\xFEFF[”, 4, &dwWritten, NULL) || dwWritten != 4

106
|| !WriteFile(hFile, aSection, cb, &dwWritten, NULL) || dwWritten != cb )
result = FALSE;

if (!CloseHandle(hFile))
result = FALSE;

aSection[cc] = ' \0 ' ; // Re-terminate.


}
}
return result;
}
#endif

ResultType Line::IniWrite(LPTSTR aValue, LPTSTR aFilespec, LPTSTR aSection, LPTSTR aKey)


{
TCHAR szFileTemp[_MAX_PATH+1];
TCHAR *szFilePart;
BOOL result;
// Get the fullpathname (INI functions need a full path)
GetFullPathName(aFilespec, _MAX_PATH, szFileTemp, &szFilePart);
#ifdef UNICODE
// WritePrivateProfileStringW() always creates INIs using the system codepage.
// IniEncodingFix() checks if the file exists and if it doesn ' t then it creates
// an empty file with a UTF-16LE BOM.
result = IniEncodingFix(szFileTemp, aSection);
if(result){
#endif
if (*aKey)
{
result = WritePrivateProfileString(aSection, aKey, aValue, szFileTemp); // Returns zero on
,→ failure.
}
else
{
size_t value_len = ArgLength(1);
TCHAR c, *cp, *szBuffer = talloca(value_len + 2); // +2 for double null-terminator.
// Convert newline-delimited list to null-terminated array of null-terminated strings.
for (cp = szBuffer; *aValue; ++cp, ++aValue)
{
c = *aValue;
*cp = (c == ' \n ' ) ? ' \0 ' : c;
}
*cp = ' \0 ' , cp[1] = ' \0 ' ; // Double null-terminator.
result = WritePrivateProfileSection(aSection, szBuffer, szFileTemp);
}
WritePrivateProfileString(NULL, NULL, NULL, szFileTemp); // Flush
#ifdef UNICODE
}
#endif
return SetErrorLevelOrThrowBool(!result);
}
 

107
Loop (files & folders)

Retrieves the specified files or folders, one at a time.


 
Loop, Files, FilePattern [, Mode] ; v1.1.21+ (recommended)
Loop, FilePattern [, IncludeFolders?, Recurse?]
 
Parameters

• Files The literal word Files (case-insensitive). This cannot be a variable or expression.

• FilePattern The name of a single file or folder, or a wildcard pattern such as C:\Temp\*.tmp. FilePattern is assumed to be in %A_WorkingDir% if
an absolute path isn’t specified.

Both asterisks and question marks are supported as wildcards. A match occurs when the pattern appears in either the file’s long/normal name or
its 8.3 short name.

If this parameter is a single file or folder (i.e. no wildcards) and Recurse is set to 1 or Mode includes R, more than one match will be found if the
specified file name appears in more than one of the folders being searched.

• Mode Zero or more of the following letters:

– D: Include directories (folders).


– F: Include files. If both F and D are omitted, files are included but not folders.
– R: Recurse into subdirectories (subfolders). If R is omitted, files and folders in subfolders are not included.

• IncludeFolders? One of the following digits, or blank to use the default:

– 0 (default): Folders are not retrieved (only files).


– 1: All files and folders that match the wildcard pattern are retrieved.
– 2: Only folders are retrieved (no files).

• Recurse? One of the following digits, or blank to use the default:

– 0 (default): Subfolders are not recursed into.


– 1: Subfolders are recursed into so that files and folders contained therein are retrieved if they match FilePattern. All subfolders will be
recursed into, not just those whose names match FilePattern.

Special Variables Available Inside a File-Loop

The following variables exist within any file-loop. If an inner file-loop is enclosed by an outer file-loop, the innermost loop’s file will take precedence:

• A_LoopFileName: The name of the file or folder currently retrieved (without the path).

• A_LoopFileExt: The file’s extension (e.g. TXT, DOC, or EXE). The period (.) is not included.

• A_LoopFileFullPath: The path and name of the file/folder currently retrieved. If FilePattern contains a relative path rather than an absolute
path, the path here will also be relative. In addition, any short (8.3) folder names in FilePattern will still be short (see next item to get the long
version).

• A_LoopFileLongPath: This is different than A_LoopFileFullPath in the following ways: 1) It always contains the absolute/complete path of
the file even if FilePattern contains a relative path; 2) Any short (8.3) folder names in FilePattern itself are converted to their long names; 3)
Characters in FilePattern are converted to uppercase or lowercase to match the case stored in the file system. This is useful for converting file
names – such as those passed into a script as command line parameters – to their exact path names as shown by Explorer.

• A_LoopFileShortPath The 8.3 short path and name of the file/folder currently retrieved. For example: C:\MYDOCU1\ADDRES 1.txt. If FilePattern
contains a relative path rather than an absolute path, the path here will also be relative.

To retrieve the complete 8.3 path and name for a single file or folder, specify its name for FilePattern as in this example:
 
Loop, C:\My Documents\Address List.txt
ShortPathName = %A_LoopFileShortPath%
 
NOTE: This variable will be blank if the file does not have a short name, which can happen on systems where NtfsDisable8dot3NameCreation
has been set in the registry. It will also be blank if FilePattern contains a relative path and the body of the loop uses SetWorkingDir to switch away
from the working directory in effect for the loop itself.

• A_LoopFileShortName: The 8.3 short name, or alternate name of the file. If the file doesn’t have one (due to the long name being shorter than
8.3 or perhaps because short-name generation is disabled on an NTFS file system), A_LoopFileName will be retrieved instead.

108
• A_LoopFileDir: The path of the directory in which A_LoopFileName resides. If FilePattern contains a relative path rather than an absolute
path, the path here will also be relative. A root directory will not contain a trailing backslash. For example: C:

• A_LoopFileTimeModified: The time the file was last modified. Format YYYYMMDDHH24MISS.

• A_LoopFileTimeCreated: The time the file was created. Format YYYYMMDDHH24MISS.

• A_LoopFileTimeAccessed: The time the file was last accessed. Format YYYYMMDDHH24MISS.

• A_LoopFileAttrib: The attributes of the file currently retrieved.

• A_LoopFileSize: The size in bytes of the file currently retrieved. Files larger than 4 gigabytes are also supported.

• A_LoopFileSizeKB: The size in Kbytes of the file currently retrieved, rounded down to the nearest integer.

• A_LoopFileSizeMB: The size in Mbytes of the file currently retrieved, rounded down to the nearest integer.

Remarks

A file-loop is useful when you want to operate on a collection of files and/or folders, one at a time.

All matching files are retrieved, including hidden files. By contrast, OS features such as the DIR command omit hidden files by default. To avoid
processing hidden, system, and/or read-only files, use something like the following inside the loop:
 
if A_LoopFileAttrib contains H,R,S ; Skip any file that is either H (Hidden), R (Read-only), or S (System).
,→ Note: No spaces in ”H,R,S”.
continue ; Skip this file and move on to the next one.
 
To retrieve files’ relative paths instead of absolute paths during a recursive search, use SetWorkingDir to change to the base folder prior to the loop, and
then omit the path from the Loop (e.g. Loop, *.*, 0, 1). That will cause A_LoopFileFullPath to contain the file’s path relative to the base folder.

A file-loop can disrupt itself if it creates or renames files or folders within its own purview. For example, if it renames files via FileMove or other means,
each such file might be found twice: once as its old name and again as its new name. To work around this, rename the files only after creating a list of
them. For example:
 
FileList =
Loop, Files, *.jpg
FileList = %FileList%%A_LoopFileName%`n
Loop, Parse, FileList, `n
FileMove, %A_LoopField%, renamed_%A_LoopField%
 
Files in an NTFS file system are probably always retrieved in alphabetical order. Files in other file systems are retrieved in no particular order.

Files and folders with a complete path name longer than 259 characters are skipped over as though they do not exist. Such files are rare because
normally, the operating system does not allow their creation.
 
ResultType Line::PerformLoopFilePattern(ExprTokenType *aResultToken, bool &aContinueMainLoop, Line *&
,→ aJumpToLine, Line *aUntil
, FileLoopModeType aFileLoopMode, bool aRecurseSubfolders, LPTSTR aFilePattern)
// Note: Even if aFilePattern is just a directory (i.e. with not wildcard pattern), it seems best
// not to append ”\\*.*” to it because the pattern might be a script variable that the user wants
// to conditionally resolve to various things at runtime. In other words, it ' s valid to have
// only a single directory be the target of the loop.
{
// Make a local copy of the path given in aFilePattern because as the lines of
// the loop are executed, the deref buffer (which is what aFilePattern might
// point to if we were called from ExecUntil()) may be overwritten --
// and we will need the path string for every loop iteration. We also need
// to determine naked_filename_or_pattern:
TCHAR file_path[MAX_PATH], naked_filename_or_pattern[MAX_PATH]; // Giving +3 extra for ”*.*” seems fairly
,→ pointless because any files that actually need that extra room would fail to be retrieved by
,→ FindFirst/Next due to their inability to support paths much over 256.
size_t file_path_length;
tcslcpy(file_path, aFilePattern, _countof(file_path));
LPTSTR last_backslash = _tcsrchr(file_path, ' \\ ' );
if (last_backslash)
{

109
_tcscpy(naked_filename_or_pattern, last_backslash + 1); // Naked filename. No danger of overflow due
,→ size of src vs. dest.
*(last_backslash + 1) = ' \0 ' ; // Convert file_path to be the file ' s path, but use +1 to retain the
,→ final backslash on the string.
file_path_length = _tcslen(file_path);
}
else
{
_tcscpy(naked_filename_or_pattern, file_path); // No danger of overflow due size of src vs. dest.
*file_path = ' \0 ' ; // There is no path, so make it empty to use current working directory.
file_path_length = 0;
}

// g->mLoopFile is the current file of the file-loop that encloses this file-loop, if any.
// The below is our own current_file, which will take precedence over g->mLoopFile if this
// loop is a file-loop:
BOOL file_found;
WIN32_FIND_DATA new_current_file;
HANDLE file_search = FindFirstFile(aFilePattern, &new_current_file);
for ( file_found = (file_search != INVALID_HANDLE_VALUE) // Convert FindFirst ' s return value into a
,→ boolean so that it ' s compatible with FindNext ' s.
; file_found && FileIsFilteredOut(new_current_file, aFileLoopMode, file_path, file_path_length)
; file_found = FindNextFile(file_search, &new_current_file));
// file_found and new_current_file have now been set for use below.
// Above is responsible for having properly set file_found and file_search.

ResultType result;
Line *jump_to_line;
global_struct &g = *::g; // Primarily for performance in this case.

for (; file_found; ++g.mLoopIteration)


{
g.mLoopFile = &new_current_file; // inner file-loop ' s file takes precedence over any outer file-loop ' s
,→ .
// Other types of loops leave g.mLoopFile unchanged so that a file-loop can enclose some other type of
// inner loop, and that inner loop will still have access to the outer loop ' s current file.

// Execute once the body of the loop (either just one statement or a block of statements).
// Preparser has ensured that every LOOP has a non-NULL next line.
if (mNextLine->mActionType == ACT_BLOCK_BEGIN) // See PerformLoop() for comments about this section.
do
result = mNextLine->mNextLine->ExecUntil(UNTIL_BLOCK_END, aResultToken, &jump_to_line);
while (jump_to_line == mNextLine);
else
result = mNextLine->ExecUntil(ONLY_ONE_LINE, aResultToken, &jump_to_line);
if (jump_to_line && !(result == LOOP_CONTINUE && jump_to_line == this)) // See comments in PerformLoop
,→ () about this section.
{
if (jump_to_line == this)
aContinueMainLoop = true;
else
aJumpToLine = jump_to_line; // Signal our caller to handle this jump.
FindClose(file_search);
return result;
}
if ( result != OK && result != LOOP_CONTINUE // i.e. result == LOOP_BREAK || result == EARLY_RETURN ||
,→ result == EARLY_EXIT || result == FAIL)
|| (aUntil && aUntil->EvaluateLoopUntil(result)) )
{
FindClose(file_search);

110
// Although ExecUntil() will treat the LOOP_BREAK result identically to OK, we
// need to return LOOP_BREAK in case our caller is another instance of this
// same function (i.e. due to recursing into subfolders):
return result;
}
// Otherwise, the result of executing the body of the loop, above, was either OK
// (the current iteration completed normally) or LOOP_CONTINUE (the current loop
// iteration was cut short). In both cases, just continue on through the loop.
// But first do end-of-iteration steps:
while ((file_found = FindNextFile(file_search, &new_current_file))
&& FileIsFilteredOut(new_current_file, aFileLoopMode, file_path, file_path_length)); // Relies on
,→ short-circuit boolean order.
// Above is a self-contained loop that keeps fetching files until there ' s no more files, or a file
// is found that isn ' t filtered out. It also sets file_found and new_current_file for use by the
// outer loop.
} // for()

// The script ' s loop is now over.


if (file_search != INVALID_HANDLE_VALUE)
FindClose(file_search);

// If aRecurseSubfolders is true, we now need to perform the loop ' s body for every subfolder to
// search for more files and folders inside that match aFilePattern. We can ' t do this in the
// first loop, above, because it may have a restricted file-pattern such as *.txt and we want to
// find and recurse into ALL folders:
if (!aRecurseSubfolders) // No need to continue into the ”recurse” section.
return OK;

// Since above didn ' t return, this is a file-loop and recursion into sub-folders has been requested.
// Append *.* to file_path so that we can retrieve all files and folders in the aFilePattern
// main folder. We ' re only interested in the folders, but we have to use *.* to ensure
// that the search will find all folder names:
if (file_path_length > _countof(file_path) - 4) // v1.0.45.03: No room to append ”*.*”, so for simplicity,
,→ skip this folder (don ' t recurse into it).
return OK; // This situation might be impossible except for 32000-capable paths because the OS seems
,→ to reserve room inside every directory for at least the maximum length of a short filename.
LPTSTR append_pos = file_path + file_path_length;
_tcscpy(append_pos, _T(”*.*”)); // Above has already verified that no overflow is possible.

file_search = FindFirstFile(file_path, &new_current_file);


if (file_search == INVALID_HANDLE_VALUE)
return OK; // Nothing more to do.
// Otherwise, recurse into any subdirectories found inside this parent directory.

size_t path_and_pattern_length = file_path_length + _tcslen(naked_filename_or_pattern); // Calculated only


,→ once for performance.
do
{
if (!(new_current_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // We only want directories (
,→ except ”.” and ”..”).
|| new_current_file.cFileName[0] == ' . ' && (!new_current_file.cFileName[1] // Relies on short
,→ -circuit boolean order.
|| new_current_file.cFileName[1] == ' . ' && !new_current_file.cFileName[2]) //
// v1.0.45.03: Skip over folders whose full-path-names are too long to be supported by the ANSI
// versions of FindFirst/FindNext. Without this fix, the section below formerly called
,→ PerformLoop()
// with a truncated full-path-name, which caused the last_backslash-finding logic to find the
,→ wrong
// backslash, which in turn caused infinite recursion and a stack overflow (i.e. caused by the
// full-path-name getting truncated in the same spot every time, endlessly).

111
|| path_and_pattern_length + _tcslen(new_current_file.cFileName) > _countof(file_path) - 2) // -2
,→ to reflect: 1) the backslash to be added between cFileName and naked_filename_or_pattern;
,→ 2) the zero terminator.
continue;
// Build the new search pattern, which consists of the original file_path + the subfolder name
// we just discovered + the original pattern:
_stprintf(append_pos, _T(”%s\\%s”), new_current_file.cFileName, naked_filename_or_pattern); //
,→ Indirectly set file_path to the new search pattern. This won ' t overflow due to the check above
,→ .
// Pass NULL for the 2nd param because it will determine its own current-file when it does
// its first loop iteration. This is because this directory is being recursed into, not
// processed itself as a file-loop item (since this was already done in the first loop,
// above, if its name matches the original search pattern):
result = PerformLoopFilePattern(aResultToken, aContinueMainLoop, aJumpToLine, aUntil, aFileLoopMode,
,→ aRecurseSubfolders, file_path);
// Above returns LOOP_CONTINUE for cases like ”continue 2” or ”continue outer_loop”, where the
// target is not this Loop but a Loop which encloses it. In those cases we want below to return:
if (result != OK) // i.e. result == LOOP_BREAK || result == EARLY_RETURN || result == EARLY_EXIT ||
,→ result == FAIL)
{
FindClose(file_search);
return result; // Return even LOOP_BREAK, since our caller can be either ExecUntil() or ourself.
}
if (aContinueMainLoop // The call to PerformLoop() above signaled us to break & return.
|| aJumpToLine)
// Above: There ' s no need to check ”aJumpToLine == this” because PerformLoop() would already have
// handled it. But if it set aJumpToLine to be non-NULL, it means we have to return and let our
,→ caller
// handle the jump.
break;
} while (FindNextFile(file_search, &new_current_file));
FindClose(file_search);

return OK;
}

VarSizeType BIV_LoopFileName(LPTSTR aBuf, LPTSTR aVarName) // Called by multiple callers.


{
LPTSTR naked_filename;
if (g->mLoopFile)
{
// The loop handler already prepended the script ' s directory in here for us:
if (naked_filename = _tcsrchr(g->mLoopFile->cFileName, ' \\ ' ))
++naked_filename;
else // No backslash, so just make it the entire file name.
naked_filename = g->mLoopFile->cFileName;
}
else
naked_filename = _T(””);
if (aBuf)
_tcscpy(aBuf, naked_filename);
return (VarSizeType)_tcslen(naked_filename);
}

VarSizeType BIV_LoopFileShortName(LPTSTR aBuf, LPTSTR aVarName)


{
LPTSTR short_filename = _T(””); // Set default.
if (g->mLoopFile)
{
if ( !*(short_filename = g->mLoopFile->cAlternateFileName) )

112
// Files whose long name is shorter than the 8.3 usually don ' t have value stored here,
// so use the long name whenever a short name is unavailable for any reason (could
// also happen if NTFS has short-name generation disabled?)
return BIV_LoopFileName(aBuf, _T(””));
}
if (aBuf)
_tcscpy(aBuf, short_filename);
return (VarSizeType)_tcslen(short_filename);
}

VarSizeType BIV_LoopFileExt(LPTSTR aBuf, LPTSTR aVarName)


{
LPTSTR file_ext = _T(””); // Set default.
if (g->mLoopFile)
{
// The loop handler already prepended the script ' s directory in here for us:
if (file_ext = _tcsrchr(g->mLoopFile->cFileName, ' . ' ))
{
++file_ext;
if (_tcschr(file_ext, ' \\ ' )) // v1.0.48.01: Disqualify periods found in the path instead of the
,→ filename; e.g. path.name\FileWithNoExtension.
file_ext = _T(””);
}
else // Reset to empty string vs. NULL.
file_ext = _T(””);
}
if (aBuf)
_tcscpy(aBuf, file_ext);
return (VarSizeType)_tcslen(file_ext);
}

VarSizeType BIV_LoopFileDir(LPTSTR aBuf, LPTSTR aVarName)


{
LPTSTR file_dir = _T(””); // Set default.
LPTSTR last_backslash = NULL;
if (g->mLoopFile)
{
// The loop handler already prepended the script ' s directory in here for us.
// But if the loop had a relative path in its FilePattern, there might be
// only a relative directory here, or no directory at all if the current
// file is in the origin/root dir of the search:
if (last_backslash = _tcsrchr(g->mLoopFile->cFileName, ' \\ ' ))
{
*last_backslash = ' \0 ' ; // Temporarily terminate.
file_dir = g->mLoopFile->cFileName;
}
else // No backslash, so there is no directory in this case.
file_dir = _T(””);
}
VarSizeType length = (VarSizeType)_tcslen(file_dir);
if (!aBuf)
{
if (last_backslash)
*last_backslash = ' \\ ' ; // Restore the original value.
return length;
}
_tcscpy(aBuf, file_dir);
if (last_backslash)
*last_backslash = ' \\ ' ; // Restore the original value.
return length;

113
}

VarSizeType BIV_LoopFileFullPath(LPTSTR aBuf, LPTSTR aVarName)


{
// The loop handler already prepended the script ' s directory in cFileName for us:
LPTSTR full_path = g->mLoopFile ? g->mLoopFile->cFileName : _T(””);
if (aBuf)
_tcscpy(aBuf, full_path);
return (VarSizeType)_tcslen(full_path);
}

VarSizeType BIV_LoopFileLongPath(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR *unused, buf[MAX_PATH] = _T(””); // Set default.
if (g->mLoopFile)
{
// GetFullPathName() is done in addition to ConvertFilespecToCorrectCase() for the following reasons:
// 1) It ' s currently the only easy way to get the full path of the directory in which a file resides.
// For example, if a script is passed a filename via command line parameter, that file could be
// either an absolute path or a relative path. If relative, of course it ' s relative to
,→ A_WorkingDir.
// The problem is, the script would have to manually detect this, which would probably take several
// extra steps.
// 2) A_LoopFileLongPath is mostly intended for the following cases, and in all of them it seems
// preferable to have the full/absolute path rather than the relative path:
// a) Files dragged onto a .ahk script when the drag-and-drop option has been enabled via the
,→ Installer.
// b) Files passed into the script via command line.
// The below also serves to make a copy because changing the original would yield
// unexpected/inconsistent results in a script that retrieves the A_LoopFileFullPath
// but only conditionally retrieves A_LoopFileLongPath.
if (!GetFullPathName(g->mLoopFile->cFileName, MAX_PATH, buf, &unused))
*buf = ' \0 ' ; // It might fail if NtfsDisable8dot3NameCreation is turned on in the registry, and
,→ possibly for other reasons.
else
// The below is called in case the loop is being used to convert filename specs that were passed
// in from the command line, which thus might not be the proper case (at least in the path
// portion of the filespec), as shown in the file system:
ConvertFilespecToCorrectCase(buf);
}
if (aBuf)
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that (even though it ' s large enough to hold
,→ the string). This is true for ReadRegString() ' s API call and may be true for other API calls
,→ like the one here.
return (VarSizeType)_tcslen(buf); // Must explicitly calculate the length rather than using the return
,→ value from GetFullPathName(), because ConvertFilespecToCorrectCase() expands 8.3 path components.
}

VarSizeType BIV_LoopFileShortPath(LPTSTR aBuf, LPTSTR aVarName)


// Unlike GetLoopFileShortName(), this function returns blank when there is no short path.
// This is done so that there ' s a way for the script to more easily tell the difference between
// an 8.3 name not being available (due to the being disabled in the registry) and the short
// name simply being the same as the long name. For example, if short name creation is disabled
// in the registry, A_LoopFileShortName would contain the long name instead, as documented.
// But to detect if that short name is really a long name, A_LoopFileShortPath could be checked
// and if it ' s blank, there is no short name available.
{
TCHAR buf[MAX_PATH] = _T(””); // Set default.
DWORD length = 0; //

114
if (g->mLoopFile)
// The loop handler already prepended the script ' s directory in cFileName for us:
if ( !(length = GetShortPathName(g->mLoopFile->cFileName, buf, MAX_PATH)) )
*buf = ' \0 ' ; // It might fail if NtfsDisable8dot3NameCreation is turned on in the registry, and
,→ possibly for other reasons.
if (aBuf)
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that (even though it ' s large enough to hold
,→ the string). This is true for ReadRegString() ' s API call and may be true for other API calls
,→ like the one here.
return (VarSizeType)length;
}

VarSizeType BIV_LoopFileTime(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR buf[64];
LPTSTR target_buf = aBuf ? aBuf : buf;
*target_buf = ' \0 ' ; // Set default.
if (g->mLoopFile)
{
FILETIME ft;
switch(ctoupper(aVarName[14])) // A_LoopFileTime[A]ccessed
{
case ' M ' : ft = g->mLoopFile->ftLastWriteTime; break;
case ' C ' : ft = g->mLoopFile->ftCreationTime; break;
default: ft = g->mLoopFile->ftLastAccessTime;
}
FileTimeToYYYYMMDD(target_buf, ft, true);
}
return (VarSizeType)_tcslen(target_buf);
}

VarSizeType BIV_LoopFileAttrib(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR buf[64];
LPTSTR target_buf = aBuf ? aBuf : buf;
*target_buf = ' \0 ' ; // Set default.
if (g->mLoopFile)
FileAttribToStr(target_buf, g->mLoopFile->dwFileAttributes);
return (VarSizeType)_tcslen(target_buf);
}

VarSizeType BIV_LoopFileSize(LPTSTR aBuf, LPTSTR aVarName)


{
// Don ' t use MAX_INTEGER_LENGTH in case user has selected a very long float format via SetFormat.
TCHAR str[128];
LPTSTR target_buf = aBuf ? aBuf : str;
*target_buf = ' \0 ' ; // Set default.
if (g->mLoopFile)
{

// UPDATE: 64-bit ints are now standard, so the following is obsolete:


// It ' s a documented limitation that the size will show as negative if
// greater than 2 gig, and will be wrong if greater than 4 gig. For files
// that large, scripts should use the KB version of this function instead.
// If a file is over 4gig, set the value to be the maximum size (-1 when
// expressed as a signed integer, since script variables are based entirely
// on 32-bit signed integers due to the use of ATOI(), etc.).
//sprintf(str, ”%d%”, g->mLoopFile->nFileSizeHigh ? -1 : (int)g->mLoopFile->nFileSizeLow);
ULARGE_INTEGER ul;

115
ul.HighPart = g->mLoopFile->nFileSizeHigh;
ul.LowPart = g->mLoopFile->nFileSizeLow;
int divider;
switch (ctoupper(aVarName[14])) // A_LoopFileSize[K/M]B
{
case ' K ' : divider = 1024; break;
case ' M ' : divider = 1024*1024; break;
default: divider = 0;
}
ITOA64((__int64)(divider ? ((unsigned __int64)ul.QuadPart / divider) : ul.QuadPart), target_buf);
}
return (VarSizeType)_tcslen(target_buf);
}
 

116
Environment

117
ClipWait

Waits until the clipboard contains data.


 
ClipWait [, SecondsToWait, 1]
 
Parameters

• SecondsToWait If omitted, the command will wait indefinitely. Otherwise, it will wait no longer than this many seconds (can contain a decimal
point or be an expression). Specifying 0 is the same as specifying 0.5.

• 1 If this parameter is omitted, the command is more selective, waiting specifically for text or files to appear (“text” includes anything that would
produce text when you paste into Notepad). If this parameter is 1 (can be an expression), the command waits for data of any kind to appear on
the clipboard.

Remarks

It’s better to use this command than a loop of your own that checks to see if this clipboard is blank. This is because the clipboard is never opened by
this command, and thus it performs better and avoids any chance of interfering with another application that may be using the clipboard.

This command considers anything convertible to text (e.g. HTML) to be text. It also considers files, such as those copied in an Explorer window via
Control-C, to be text. Such files are automatically converted to their filenames (with full path) whenever the clipboard variable (%clipboard%) is referred
to in the script.

When 1 is present as the last parameter, the command will be satisified when any data appears on the clipboard. This can be used in conjunction with
ClipboardAll to save non-textual items such as pictures.

While the command is in a waiting state, new threads can be launched via hotkey, custom menu item, or timer.
 
for (start_time = GetTickCount();;) // start_time is initialized unconditionally for use with v1.0.30.02 's
,→ new logging feature further below.
{ // Always do the first iteration so that at least one check is done.

switch(mActionType)
{
case ACT_CLIPWAIT:
// Seems best to consider CF_HDROP to be a non-empty clipboard, since we
// support the implicit conversion of that format to text:
if (any_clipboard_format)
{
if (CountClipboardFormats())
return OK;
}
else
if (IsClipboardFormatAvailable(CF_NATIVETEXT) || IsClipboardFormatAvailable(CF_HDROP))
return OK;
break;

// ...........................

// Must cast to int or any negative result will be lost due to DWORD type:
if (wait_indefinitely || (int)(sleep_duration - (GetTickCount() - start_time)) > SLEEP_INTERVAL_HALF)
{
if (MsgSleep(INTERVAL_UNSPECIFIED)) // INTERVAL_UNSPECIFIED performs better.
{
// v1.0.30.02: Since MsgSleep() launched and returned from at least one new thread, put the
// current waiting line into the line-log again to make it easy to see what the current
// thread is doing. This is especially useful for figuring out which subroutine is holding
// another thread interrupted beneath it. For example, if a timer gets interrupted by
// a hotkey that has an indefinite WinWait, and that window never appears, this will allow
// the user to find out the culprit thread by showing its line in the log (and usually
// it will appear as the very last line, since usually the script is idle and thus the
// currently active thread is the one that ' s still waiting for the window).
if (g->ListLinesIsEnabled)

118
{
sLog[sLogNext] = this;
sLogTick[sLogNext++] = start_time; // Store a special value so that Line::LogToText() can
,→ report that its ”still waiting” from earlier.
if (sLogNext >= LINE_LOG_SIZE)
sLogNext = 0;
// The lines above are the similar to those used in ExecUntil(), so the two should be
// maintained together.
}
}
}
}
}
 

119
EnvGet

Retrieves an environment variable.


 
EnvGet, OutputVar, EnvVarName
 
Parameters

• OutputVar The name of the variable in which to store the string.

• EnvVarName The name of the environment variable to retrieve. For example: EnvGet, OutputVar, Path.

Remarks

If the specified environment variable is empty or does not exist, OutputVar is made blank.

The operating system limits each environment variable to 32 KB of text.


 
ResultType Line::EnvGet(LPTSTR aEnvVarName)
{
Var *output_var = OUTPUT_VAR;
// Don ' t use a size greater than 32767 because that will cause it to fail on Win95 (tested by Robert
,→ Yalkin).
// According to MSDN, 32767 is exactly large enough to handle the largest variable plus its zero
,→ terminator.
// Update: In practice, at least on Windows 7, the limit only applies to the ANSI functions.
TCHAR buf[32767];
// GetEnvironmentVariable() could be called twice, the first time to get the actual size. But that would
// probably perform worse since GetEnvironmentVariable() is a very slow function, so it seems best to
,→ fetch
// it into a large buffer then just copy it to dest-var.
DWORD length = GetEnvironmentVariable(aEnvVarName, buf, _countof(buf));
if (length >= _countof(buf))
{
// In this case, length indicates the required buffer size, and the contents of the buffer are
,→ undefined.
// Since our buffer is 32767 characters, the var apparently exceeds the documented limit, as can
,→ happen
// if the var was set with the Unicode API.
if (!output_var->AssignString(NULL, length - 1, true))
return FAIL;
length = GetEnvironmentVariable(aEnvVarName, output_var->Contents(), length);
if (!length)
*output_var->Contents() = ' \0 ' ; // Ensure var is null-terminated.
return output_var->Close();
}
return output_var->Assign(length ? buf : _T(””), length);
}
 

120
EnvSet

Writes a value to a variable contained in the environment.


 
EnvSet, EnvVar, Value
 
Parameters

EnvVar Name of the environment variable to use, e.g. “COMSPEC” or “PATH”.

Value Value to set the environment variable to.

Remarks

The operating system limits each environment variable to 32 KB of text.

An environment variable created or changed with this command will be accessible only to programs the script launches via Run or RunWait. See
environment variables for more details.
 
case ACT_ENVSET:
// MSDN: ”If [the 2nd] parameter is NULL, the variable is deleted from the current ’processs
,→ environment.”
// My: Though it seems okay, for now, just to set it to be blank if the user omitted the 2nd param or
// left it blank (AutoIt3 does this too). Also, no checking is currently done to ensure that ARG2
// isn ' t longer than 32K, since future OSes may support longer env. vars. SetEnvironmentVariable()
// might return 0(fail) in that case anyway. Also, ARG1 may be a dereferenced variable that resolves
// to the name of an Env. Variable. In any case, this name need not correspond to any existing
// variable name within the script (i.e. script variables and env. variables aren ' t tied to each other
// in any way). This seems to be the most flexible approach, but are there any shortcomings?
// The only one I can think of is that if the script tries to fetch the value of an env. var (perhaps
// one that some other spawned program changed), and that var ' s name corresponds to the name of a
// script var, the script var ' s value (if non-blank) will be fetched instead.
// Note: It seems, at least under WinXP, that env variable names can contain spaces. So it ' s best
// not to validate ARG1 the same way we validate script variables (i.e. just let\
// SetEnvironmentVariable() ' s return value determine whether there ' s an error). However, I just
// realized that it ' s impossible to ”retrieve” the value of an env var that has spaces (until now,
// since the EnvGet command is available).
return SetErrorLevelOrThrowBool(!SetEnvironmentVariable(ARG1, ARG2));
 

121
EnvUpdate

Notifies the OS and all running applications that environment variable(s) have changed.
 
EnvUpdate
 
Remarks

Refreshes the OS environment. Similar effect as logging off and then on again.

For example, after making changes to the following registry key, EnvUpdate could be used to broadcast the change: HKEY_LOCAL_MACHINE\System
,→ \CurrentControlSet\Control\Session Manager\Environment
 
case ACT_ENVUPDATE:
{
// From the AutoIt3 source:
// AutoIt3 uses SMTO_BLOCK (which prevents our thread from doing anything during the call)
// vs. SMTO_NORMAL. Since I ' m not sure why, I ' m leaving it that way for now:
ULONG_PTR nResult;
return SetErrorLevelOrThrowBool(!SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE,
0, (LPARAM)_T(”Environment”), SMTO_BLOCK, 15000, &nResult));
}
 

122
SysGet

Retrieves screen resolution, multi-monitor info, dimensions of system objects, and other system properties.
 
SysGet, OutputVar, Sub-command [, Param3]
 
Parameters

• OutputVar The name of the variable in which to store the result.

• Sub-command See list below.

• Param3 This parameter is omitted except where noted below.

Sub-commands

MonitorCount: Retrieves the total number of monitors. Unlike SM_CMONITORS mentioned in the table below, MonitorCount includes all monitors,
even those not being used as part of the desktop.

MonitorPrimary: Retrieves the number of the primary monitor, which will be 1 in a single-monitor system.

Monitor [, N]: Retrieves the bounding coordinates of monitor number N (if N is omitted, the primary monitor is used). The information is stored in four
variables whose names all start with OutputVar. If N is too high or there is a problem retrieving the info, the variables are all made blank. For example:
 
SysGet, Mon2, Monitor, 2
MsgBox, Left: %Mon2Left% -- Top: %Mon2Top% -- Right: %Mon2Right% -- Bottom %Mon2Bottom%.
 
Within a function, to create a set of variables that is global instead of local, declare Mon2 as a global variable prior to using this command (the converse
is true for assume-global functions). However, it is often also necessary to declare each variable in the set, due to a common source of confusion.

MonitorWorkArea [, N]: Same as the above except the area is reduced to exclude the area occupied by the taskbar and other registered desktop
toolbars.

MonitorName [, N]: The operating system’s name for monitor number N (if N is omitted, the primary monitor is used).

(Numeric): Specify for Sub-command one of the numbers from the table below to retrieve the corresponding value. The following example would store
the number of mouse buttons in a variable named “MouseButtonCount”: SysGet, MouseButtonCount, 43.

Commonly Used

Number Description

80 SM_CMONITORS: Number of display monitors on the desktop (not including “non-display pseudo-monitors”).
43 SM_CMOUSEBUTTONS: Number of buttons on mouse (0 if no mouse is installed).
16, 17 SM_CXFULLSCREEN, SM_CYFULLSCREEN: Width and height of the client area for a full-screen window on the primary display monitor, in pixels.
61, 62 SM_CXMAXIMIZED, SM_CYMAXIMIZED: Default dimensions, in pixels, of a maximized top-level window on the primary display monitor.
59, 60 SM_CXMAXTRACK, SM_CYMAXTRACK: Default maximum dimensions of a window that has a caption and sizing borders, in pixels. This metric refers to
28, 29 SM_CXMIN, SM_CYMIN: Minimum width and height of a window, in pixels.
57, 58 SM_CXMINIMIZED, SM_CYMINIMIZED: Dimensions of a minimized window, in pixels.
34, 35 SM_CXMINTRACK, SM_CYMINTRACK: Minimum tracking width and height of a window, in pixels. The user cannot drag the window frame to a size sma
0, 1 SM_CXSCREEN, SM_CYSCREEN: Width and height of the screen of the primary display monitor, in pixels. These are the same as the built-in variables A
78, 79 SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN: Width and height of the virtual screen, in pixels. The virtual screen is the bounding rectangle of all
19 SM_MOUSEPRESENT: Nonzero if a mouse is installed; zero otherwise.
75 SM_MOUSEWHEELPRESENT: Nonzero if a mouse with a wheel is installed; zero otherwise.
63 SM_NETWORK: Least significant bit is set if a network is present; otherwise, it is cleared. The other bits are reserved for future use.
8193 SM_REMOTECONTROL: This system metric is used in a Terminal Services environment. Its value is nonzero if the current session is remotely controlled
4096 SM_REMOTESESSION: This system metric is used in a Terminal Services environment. If the calling process is associated with a Terminal Services cli
70 SM_SHOWSOUNDS: Nonzero if the user requires an application to present information visually in situations where it would otherwise present the inform
8192 SM_SHUTTINGDOWN: Nonzero if the current session is shutting down; zero otherwise. Windows 2000: The retrieved value is always 0.
23 SM_SWAPBUTTON: Nonzero if the meanings of the left and right mouse buttons are swapped; zero otherwise.
76, 77 SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN: Coordinates for the left side and the top of the virtual screen. The virtual screen is the bounding recta

Not Commonly Used

123
Number Description

56 SM_ARRANGE: Flags specifying how the system arranged minimized windows. See MSDN for more information.
67 SM_CLEANBOOT: Specifies how the system was started: 0 Normal boot, 1 Fail-safe boot, 2 Fail-safe with network boot
5, 6 SM_CXBORDER, SM_CYBORDER: Width and height of a window border, in pixels. This is equivalent to the SM_CXEDGE value for windows with the 3-D l
13, 14 SM_CXCURSOR, SM_CYCURSOR: Width and height of a cursor, in pixels. The system cannot create cursors of other sizes.
36, 37 SM_CXDOUBLECLK, SM_CYDOUBLECLK: Width and height of the rectangle around the location of a first click in a double-click sequence, in pixels. The
68, 69 SM_CXDRAG, SM_CYDRAG: Width and height of a rectangle centered on a drag point to allow for limited movement of the mouse pointer before a drag
45, 46 SM_CXEDGE, SM_CYEDGE: Dimensions of a 3-D border, in pixels. These are the 3-D counterparts of SM_CXBORDER and SM_CYBORDER.
7, 8 SM_CXFIXEDFRAME, SM_CYFIXEDFRAME (synonymous with SM_CXDLGFRAME, SM_CYDLGFRAME): Thickness of the frame around the perimeter of a
83, 84 SM_CXFOCUSBORDER, SM_CYFOCUSBORDER: Width (in pixels) of the left and right edges and the height of the top and bottom edges of a control’s foc
21, 3 SM_CXHSCROLL, SM_CYHSCROLL: Width of the arrow bitmap on a horizontal scroll bar, in pixels; and height of a horizontal scroll bar, in pixels.
10 SM_CXHTHUMB: Width of the thumb box in a horizontal scroll bar, in pixels.
11, 12 SM_CXICON, SM_CYICON: Default width and height of an icon, in pixels.
38, 39 SM_CXICONSPACING, SM_CYICONSPACING: Dimensions of a grid cell for items in large icon view, in pixels. Each item fits into a rectangle of this size
71, 72 SM_CXMENUCHECK, SM_CYMENUCHECK: Dimensions of the default menu check-mark bitmap, in pixels.
54, 55 SM_CXMENUSIZE, SM_CYMENUSIZE: Dimensions of menu bar buttons, such as the child window close button used in the multiple document interface
47, 48 SM_CXMINSPACING SM_CYMINSPACING: Dimensions of a grid cell for a minimized window, in pixels. Each minimized window fits into a rectangle thi
30, 31 SM_CXSIZE, SM_CYSIZE: Width and height of a button in a window’s caption or title bar, in pixels.
32, 33 SM_CXSIZEFRAME, SM_CYSIZEFRAME: Thickness of the sizing border around the perimeter of a window that can be resized, in pixels. SM_CXSIZEF
49, 50 SM_CXSMICON, SM_CYSMICON: Recommended dimensions of a small icon, in pixels. Small icons typically appear in window captions and in small ico
52, 53 SM_CXSMSIZE SM_CYSMSIZE: Dimensions of small caption buttons, in pixels.
2, 20 SM_CXVSCROLL, SM_CYVSCROLL: Width of a vertical scroll bar, in pixels; and height of the arrow bitmap on a vertical scroll bar, in pixels.
4 SM_CYCAPTION: Height of a caption area, in pixels.
18 SM_CYKANJIWINDOW: For double byte character set versions of the system, this is the height of the Kanji window at the bottom of the screen, in pixel
15 SM_CYMENU: Height of a single-line menu bar, in pixels.
51 SM_CYSMCAPTION: Height of a small caption, in pixels.
9 SM_CYVTHUMB: Height of the thumb box in a vertical scroll bar, in pixels.
42 SM_DBCSENABLED: Nonzero if User32.dll supports DBCS; zero otherwise.
22 SM_DEBUG: Nonzero if the debug version of User.exe is installed; zero otherwise.
82 SM_IMMENABLED: Nonzero if Input Method Manager/Input Method Editor features are enabled; zero otherwise. SM_IMMENABLED indicates whether t
87 SM_MEDIACENTER: Nonzero if the current operating system is the Windows XP, Media Center Edition, zero if not.
40 SM_MENUDROPALIGNMENT: Nonzero if drop-down menus are right-aligned with the corresponding menu-bar item; zero if the menus are left-aligned.
74 SM_MIDEASTENABLED: Nonzero if the system is enabled for Hebrew and Arabic languages, zero if not.
41 SM_PENWINDOWS: Nonzero if the Microsoft Windows for Pen computing extensions are installed; zero otherwise.
44 SM_SECURE: Nonzero if security is present; zero otherwise.
81 SM_SAMEDISPLAYFORMAT: Nonzero if all the display monitors have the same color format, zero otherwise. Note that two displays can have the same
86 SM_TABLETPC: Nonzero if the current operating system is the Windows XP Tablet PC edition, zero if not.

Remarks

The built-in variables A_ScreenWidth and A_ScreenHeight contain the dimensions of the primary monitor, in pixels.
 
ResultType Line::SysGet(LPTSTR aCmd, LPTSTR aValue)
// Thanks to Gregory F. Hogg of Hogg ' s Software for providing sample code on which this function
// is based.
{
// For simplicity and array look-up performance, this is done even for sub-commands that output to an
,→ array:
Var &output_var = *OUTPUT_VAR;
SysGetCmds cmd = ConvertSysGetCmd(aCmd);
// Since command names are validated at load-time, this only happens if the command name
// was contained in a variable reference. But for simplicity of design here, return
// failure in this case (unlike other functions similar to this one):
if (cmd == SYSGET_CMD_INVALID)
return LineError(ERR_PARAM2_INVALID, FAIL, aCmd);

MonitorInfoPackage mip = {0}; // Improves maintainability to initialize unconditionally, here.


mip.monitor_info_ex.cbSize = sizeof(MONITORINFOEX); // Also improves maintainability.

// EnumDisplayMonitors() must be dynamically loaded; otherwise, the app won ' t launch at all on Win95/NT.

124
typedef BOOL (WINAPI* EnumDisplayMonitorsType)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
static EnumDisplayMonitorsType MyEnumDisplayMonitors = (EnumDisplayMonitorsType)
GetProcAddress(GetModuleHandle(_T(”user32”)), ”EnumDisplayMonitors”);

switch(cmd)
{
case SYSGET_CMD_METRICS: // In this case, aCmd is the value itself.
return output_var.Assign(GetSystemMetrics(ATOI(aCmd))); // Input and output are both signed integers.

// For the next few cases, I ' m not sure if it is possible to have zero monitors. Obviously it ' s possible
// to not have a monitor turned on or not connected at all. But it seems likely that these various API
// functions will provide a ”default monitor” in the absence of a physical monitor connected to the
// system. To be safe, all of the below will assume that zero is possible, at least on some OSes or
// under some conditions. However, on Win95/NT, ”1” is assumed since there is probably no way to tell
// for sure if there are zero monitors except via GetSystemMetrics(SM_CMONITORS), which is a different
// animal as described below.
case SYSGET_CMD_MONITORCOUNT:
// Don ' t use GetSystemMetrics(SM_CMONITORS) because of this:
// MSDN: ”GetSystemMetrics(SM_CMONITORS) counts only display monitors. This is different from
// EnumDisplayMonitors, which enumerates display monitors and also non-display pseudo-monitors.”
if (!MyEnumDisplayMonitors) // Since system only supports 1 monitor, the first must be primary.
return output_var.Assign(1); // Assign as 1 vs. ”1” to use hexadecimal display if that is in
,→ effect.
mip.monitor_number_to_find = COUNT_ALL_MONITORS;
MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
return output_var.Assign(mip.count); // Will assign zero if the API ever returns a legitimate zero.

// Even if the first monitor to be retrieved by the EnumProc is always the primary (which is doubtful
// since there ' s no mention of this in the MSDN docs) it seems best to have this sub-cmd in case that
// policy ever changes:
case SYSGET_CMD_MONITORPRIMARY:
if (!MyEnumDisplayMonitors) // Since system only supports 1 monitor, the first must be primary.
return output_var.Assign(1); // Assign as 1 vs. ”1” to use hexadecimal display if that is in
,→ effect.
// The mip struct ' s values have already initialized correctly for the below:
MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
return output_var.Assign(mip.count); // Will assign zero if the API ever returns a legitimate zero.

case SYSGET_CMD_MONITORAREA:
case SYSGET_CMD_MONITORWORKAREA:
Var *output_var_left, *output_var_top, *output_var_right, *output_var_bottom;
// Make it longer than max var name so that FindOrAddVar() will be able to spot and report
// var names that are too long:
TCHAR var_name[MAX_VAR_NAME_LENGTH + 20];
// To help performance (in case the linked list of variables is huge), tell FindOrAddVar where
// to start the search. Use the base array name rather than the preceding element because,
// for example, Array19 is alphabetically less than Array2, so we can ' t rely on the
// numerical ordering:
int always_use;
always_use = output_var.IsLocal() ? FINDVAR_LOCAL : FINDVAR_GLOBAL;
if ( !(output_var_left = g_script.FindOrAddVar(var_name
, sntprintf(var_name, _countof(var_name), _T(”%sLeft”), output_var.mName)
, always_use)) )
return FAIL; // It already reported the error.
if ( !(output_var_top = g_script.FindOrAddVar(var_name
, sntprintf(var_name, _countof(var_name), _T(”%sTop”), output_var.mName)
, always_use)) )
return FAIL;
if ( !(output_var_right = g_script.FindOrAddVar(var_name
, sntprintf(var_name, _countof(var_name), _T(”%sRight”), output_var.mName)

125
, always_use)) )
return FAIL;
if ( !(output_var_bottom = g_script.FindOrAddVar(var_name
, sntprintf(var_name, _countof(var_name), _T(”%sBottom”), output_var.mName)
, always_use)) )
return FAIL;

RECT monitor_rect;
if (MyEnumDisplayMonitors)
{
mip.monitor_number_to_find = ATOI(aValue); // If this returns 0, it will default to the primary
,→ monitor.
MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
if (!mip.count || (mip.monitor_number_to_find && mip.monitor_number_to_find != mip.count))
{
// With the exception of the caller having specified a non-existent monitor number, all of
// the ways the above can happen are probably impossible in practice. Make all the variables
// blank vs. zero to indicate the problem.
output_var_left->Assign();
output_var_top->Assign();
output_var_right->Assign();
output_var_bottom->Assign();
return OK;
}
// Otherwise:
monitor_rect = (cmd == SYSGET_CMD_MONITORAREA) ? mip.monitor_info_ex.rcMonitor : mip.
,→ monitor_info_ex.rcWork;
}
else // Win95/NT: Since system only supports 1 monitor, the first must be primary.
{
if (cmd == SYSGET_CMD_MONITORAREA)
{
monitor_rect.left = 0;
monitor_rect.top = 0;
monitor_rect.right = GetSystemMetrics(SM_CXSCREEN);
monitor_rect.bottom = GetSystemMetrics(SM_CYSCREEN);
}
else // Work area
SystemParametersInfo(SPI_GETWORKAREA, 0, &monitor_rect, 0); // Get desktop rect excluding
,→ task bar.
}
output_var_left->Assign(monitor_rect.left);
output_var_top->Assign(monitor_rect.top);
output_var_right->Assign(monitor_rect.right);
output_var_bottom->Assign(monitor_rect.bottom);
return OK;

case SYSGET_CMD_MONITORNAME:
if (MyEnumDisplayMonitors)
{
mip.monitor_number_to_find = ATOI(aValue); // If this returns 0, it will default to the primary
,→ monitor.
MyEnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&mip);
if (!mip.count || (mip.monitor_number_to_find && mip.monitor_number_to_find != mip.count))
// With the exception of the caller having specified a non-existent monitor number, all of
// the ways the above can happen are probably impossible in practice. Make the variable
// blank to indicate the problem:
return output_var.Assign();
else
return output_var.Assign(mip.monitor_info_ex.szDevice);

126
}
else // Win95/NT: There is probably no way to find out the name of the monitor.
return output_var.Assign();
} // switch()

return FAIL; // Never executed (increases maintainability and avoids compiler warning).
}

BOOL CALLBACK EnumMonitorProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM lParam)
{
MonitorInfoPackage &mip = *(MonitorInfoPackage *)lParam; // For performance and convenience.
if (mip.monitor_number_to_find == COUNT_ALL_MONITORS)
{
++mip.count;
return TRUE; // Enumerate all monitors so that they can be counted.
}
// GetMonitorInfo() must be dynamically loaded; otherwise, the app won ' t launch at all on Win95/NT.
typedef BOOL (WINAPI* GetMonitorInfoType)(HMONITOR, LPMONITORINFO);
static GetMonitorInfoType MyGetMonitorInfo = (GetMonitorInfoType)
GetProcAddress(GetModuleHandle(_T(”user32”)), ”GetMonitorInfo” WINAPI_SUFFIX);
if (!MyGetMonitorInfo) // Shouldn ' t normally happen since caller wouldn ' t have called us if OS is Win95/NT
,→ .
return FALSE;
if (!MyGetMonitorInfo(hMonitor, &mip.monitor_info_ex)) // Failed. Probably very rare.
return FALSE; // Due to the complexity of needing to stop at the correct monitor number, do not
,→ continue.
// In the unlikely event that the above fails when the caller wanted us to find the primary
// monitor, the caller will think the primary is the previously found monitor (if any).
// This is just documented here as a known limitation since this combination of circumstances
// is probably impossible.
++mip.count; // So that caller can detect failure, increment only now that failure conditions have been
,→ checked.
if (mip.monitor_number_to_find) // Caller gave a specific monitor number, so don ' t search for the primary
,→ monitor.
{
if (mip.count == mip.monitor_number_to_find) // Since the desired monitor has been found, must not
,→ continue.
return FALSE;
}
else // Caller wants the primary monitor found.
// MSDN docs are unclear that MONITORINFOF_PRIMARY is a bitwise value, but the name ”dwFlags” implies
,→ so:
if (mip.monitor_info_ex.dwFlags & MONITORINFOF_PRIMARY)
return FALSE; // Primary has been found and ”count” contains its number. Must not continue the
,→ enumeration.
// Above assumes that it is impossible to not have a primary monitor in a system that has at least
// one monitor. MSDN certainly implies this through multiple references to the primary monitor.
// Otherwise, continue the enumeration:
return TRUE;
}
 

127
Clipboard

Clipboard is a built-in variable that reflects the current contents of the Windows clipboard if those contents can be expressed as text. By contrast,
ClipboardAll contains everything on the clipboard, such as pictures and formatting.

Each line of text on Clipboard typically ends with carriage return and linefeed (CR+LF), which can be expressed in the script as ‘r‘n. Files (such as
those copied from an open Explorer window via Control-C) are considered to be text: They are automatically converted to their filenames (with full path)
whenever Clipboard is referenced in the script.
 
#define CANT_OPEN_CLIPBOARD_READ _T(”Can ' t open clipboard for reading.”)
#define CANT_OPEN_CLIPBOARD_WRITE _T(”Can ' t open clipboard for writing.”)

#ifdef UNICODE
// In unicode version, always try CF_UNICODETEXT first, then CF_TEXT.
#define CF_NATIVETEXT CF_UNICODETEXT
#define CF_OTHERTEXT CF_TEXT
#else
#define CF_NATIVETEXT CF_TEXT
#define CF_OTHERTEXT CF_UNICODETEXT
#endif

class Clipboard
{
public:
HGLOBAL mClipMemNow, mClipMemNew;
LPTSTR mClipMemNowLocked, mClipMemNewLocked;
// NOTE: Both mLength and mCapacity are count in characters (NOT in bytes).
size_t mLength; // Last-known length of the clipboard contents (for internal use only because it ' s valid
,→ only during certain specific times).
UINT mCapacity; // Capacity of mClipMemNewLocked.
BOOL mIsOpen; // Whether the clipboard is physically open due to action by this class. BOOL vs. bool
,→ improves some benchmarks slightly due to this item being frequently checked.

// It seems best to default to many attempts, because a failure


// to open the clipboard may result in the early termination
// of a large script due to the fear that it ' s generally
// unsafe to continue in such cases. Update: Increased default
// number of attempts from 20 to 40 because Jason (Payam) reported
// that he was getting an error on rare occasions (but not reproducible).
ResultType Open();
HANDLE GetClipboardDataTimeout(UINT uFormat, BOOL *aNullIsOkay = NULL);

// Below: Whether the clipboard is ready to be written to. Note that the clipboard is not
// usually physically open even when this is true, unless the caller specifically opened
// it in that mode also:
bool IsReadyForWrite() {return mClipMemNewLocked != NULL;}

#define CLIPBOARD_FAILURE UINT_MAX


size_t Get(LPTSTR aBuf = NULL);

ResultType Set(LPCTSTR aBuf = NULL, UINT_PTR aLength = UINT_MAX);


LPTSTR PrepareForWrite(size_t aAllocSize);
ResultType Commit(UINT aFormat = CF_NATIVETEXT);
ResultType AbortWrite(LPTSTR aErrorMessage = _T(””));
ResultType Close(LPTSTR aErrorMessage = NULL);
LPTSTR Contents()
{
if (mClipMemNewLocked)
// Its set up for being written to, which takes precedence over the fact
// that it may be open for read also, so return the write-buffer:

128
return mClipMemNewLocked;
if (!IsClipboardFormatAvailable(CF_NATIVETEXT))
// We check for both CF_TEXT and CF_HDROP in case it ' s possible for
// the clipboard to contain both formats simultaneously. In this case,
// just do this for now, to remind the caller that in these cases, it should
// call Get(buf), providing the target buf so that we have some memory to
// transcribe the (potentially huge) list of files into:
return IsClipboardFormatAvailable(CF_HDROP) ? _T(”<<>>”) : _T(””);
else
// Some callers may rely upon receiving empty string rather than NULL on failure:
return (Get() == CLIPBOARD_FAILURE) ? _T(””) : mClipMemNowLocked;
}

Clipboard() // Constructor
: mIsOpen(false) // Assumes our app doesn ' t already have it open.
, mClipMemNow(NULL), mClipMemNew(NULL)
, mClipMemNowLocked(NULL), mClipMemNewLocked(NULL)
, mLength(0), mCapacity(0)
{}
};

size_t Clipboard::Get(LPTSTR aBuf)


// If aBuf is NULL, it returns the length of the text on the clipboard and leaves the
// clipboard open. Otherwise, it copies the clipboard text into aBuf and closes
// the clipboard (UPDATE: But only if the clipboard is still open from a prior call
// to determine the length -- see later comments for details). In both cases, the
// length of the clipboard text is returned (or the value CLIPBOARD_FAILURE if error).
// If the clipboard is still open when the next MsgSleep() is called -- presumably
// because the caller never followed up with a second call to this function, perhaps
// due to having insufficient memory -- MsgSleep() will close it so that our
// app doesn ' t keep the clipboard tied up. Note: In all current cases, the caller
// will use MsgBox to display an error, which in turn calls MsgSleep(), which will
// immediately close the clipboard.
{
// Seems best to always have done this even if we return early due to failure:
if (aBuf)
// It should be safe to do this even at its peak capacity, because caller
// would have then given us the last char in the buffer, which is already
// a zero terminator, so this would have no effect:
*aBuf = ' \0 ' ;

UINT i, file_count = 0;
BOOL clipboard_contains_text = IsClipboardFormatAvailable(CF_NATIVETEXT);
BOOL clipboard_contains_files = IsClipboardFormatAvailable(CF_HDROP);
if (!(clipboard_contains_text || clipboard_contains_files))
return 0;

if (!mIsOpen)
{
// As a precaution, don ' t give the caller anything from the clipboard
// if the clipboard isn ' t already open from the caller ' s previous
// call to determine the size of what ' s on the clipboard (no other app
// can alter its size while we have it open). The is to prevent a
// buffer overflow from happening in a scenario such as the following:
// Caller calls us and we return zero size, either because there ' s no
// CF_TEXT on the clipboard or there was a problem opening the clipboard.
// In these two cases, the clipboard isn ' t open, so by the time the
// caller calls us again, there ' s a chance (vanishingly small perhaps)
// that another app (if our thread were preempted long enough, or the
// platform is multiprocessor) will have changed the contents of the

129
// clipboard to something larger than zero. Thus, if we copy that
// into the caller ' s buffer, the buffer might overflow:
if (aBuf)
return 0;
if (!Open())
{
// Since this should be very rare, a shorter message is now used. Formerly, it was
// ”Could not open clipboard for reading after many timed attempts. Another program is probably
,→ holding it open.”
Close(CANT_OPEN_CLIPBOARD_READ);
return CLIPBOARD_FAILURE;
}
if ( !(mClipMemNow = g_clip.GetClipboardDataTimeout(clipboard_contains_text ? CF_NATIVETEXT :
,→ CF_HDROP)) )
{
// v1.0.47.04: Commented out the following that had been in effect when clipboard_contains_files==
,→ false:
// Close(”GetClipboardData”); // Short error message since so rare.
// return CLIPBOARD_FAILURE;
// This was done because there are situations when GetClipboardData can fail indefinitely.
// For example, in Firefox, pulling down the Bookmarks menu then right-clicking ”Bookmarks Toolbar
// Folder” then selecting ”Copy” puts one or more formats on the clipboard that cause this problem
,→ .
// For details, search the forum for TYMED_NULL.
//
// v1.0.42.03: For the fix below, GetClipboardDataTimeout() knows not to try more than once
// for CF_HDROP.
// Fix for v1.0.31.02: When clipboard_contains_files==true, tolerate failure, which happens
// as a normal/expected outcome when there are files on the clipboard but either:
// 1) zero of them;
// 2) the CF_HDROP on the clipboard is somehow misformatted.
// If you select the parent ”..” folder in WinRar then use the following hotkey, the script
// would previously yield a runtime error:
//#q::
//Send, ˆc
//ClipWait, 0.5, 1
//msgbox %Clipboard%
//Return
Close();
if (aBuf)
*aBuf = ' \0 ' ;
return CLIPBOARD_FAILURE; // Return this because otherwise, Contents() returns mClipMemNowLocked,
,→ which is NULL.
}
// Although GlobalSize(mClipMemNow) can yield zero in some cases -- in which case GlobalLock() should
// not be attempted -- it probably can ' t yield zero for CF_HDROP and CF_TEXT because such a thing has
// never been reported by anyone. Therefore, GlobalSize() is currently not called.
if ( !(mClipMemNowLocked = (LPTSTR)GlobalLock(mClipMemNow)) )
{
Close(_T(”GlobalLock”)); // Short error message since so rare.
return CLIPBOARD_FAILURE;
}
// Otherwise: Update length after every successful new open&lock:
// Determine the length (size - 1) of the buffer than would be
// needed to hold what ' s on the clipboard:
if (clipboard_contains_text)
{
// See below for comments.
mLength = _tcslen(mClipMemNowLocked);
}

130
else // clipboard_contains_files
{
if (file_count = DragQueryFile((HDROP)mClipMemNowLocked, 0xFFFFFFFF, _T(””), 0))
{
mLength = (file_count - 1) * 2; // Init; -1 if don ' t want a newline after last file.
for (i = 0; i < file_count; ++i)
mLength += DragQueryFile((HDROP)mClipMemNowLocked, i, NULL, 0);
}
else
mLength = 0;
}
if (mLength >= CLIPBOARD_FAILURE) // Can ' t realistically happen, so just indicate silent failure.
return CLIPBOARD_FAILURE;
}
if (!aBuf)
return mLength;
// Above: Just return the length; don ' t close the clipboard because we expect
// to be called again soon. If for some reason we aren ' t called, MsgSleep()
// will automatically close the clipboard and clean up things. It ' s done this
// way to avoid the chance that the clipboard contents (and thus its length)
// will change while we don ' t have it open, possibly resulting in a buffer
// overflow. In addition, this approach performs better because it avoids
// the overhead of having to close and reopen the clipboard.

// Otherwise:
if (clipboard_contains_text) // Fixed for v1.1.16.02: Prefer text over files if both are present.
{
// Because the clipboard is being retrieved as text, return this text even if
// the clipboard also contains files. Contents() relies on this since it only
// calls Get() once and does not provide a buffer. Contents() would be used
// in ”c := Clipboard” or ”MsgBox %Clipboard%” because ArgMustBeDereferenced()
// returns true only if the clipboard contains files but not text.
_tcscpy(aBuf, mClipMemNowLocked); // Caller has already ensured that aBuf is large enough.
}
else // clipboard_contains_files
{
if (file_count = DragQueryFile((HDROP)mClipMemNowLocked, 0xFFFFFFFF, _T(””), 0))
for (i = 0; i < file_count; ++i)
{
// Caller has already ensured aBuf is large enough to hold them all:
aBuf += DragQueryFile((HDROP)mClipMemNowLocked, i, aBuf, 999);
if (i < file_count - 1) // i.e. don ' t add newline after the last filename.
{
*aBuf++ = ' \r ' ; // These two are the proper newline sequence that the OS prefers.
*aBuf++ = ' \n ' ;
}
//else DragQueryFile() has ensured that aBuf is terminated.
}
// else aBuf has already been terminated upon entrance to this function.
}
// Fix for v1.0.37: Close() is no longer called here because it prevents the clipboard variable
// from being referred to more than once in a line. For example:
// Msgbox %Clipboard%%Clipboard%
// ToolTip % StrLen(Clipboard) . Clipboard
// Instead, the clipboard is later closed in other places (search on CLOSE_CLIPBOARD_IF_OPEN
// to find them). The alternative to fixing it this way would be to let it reopen the clipboard
// by means getting rid of the following lines above:
//if (aBuf)
// return 0;
// However, that has the risks described in the comments above those two lines.

131
return mLength;
}

ResultType Clipboard::Set(LPCTSTR aBuf, UINT_PTR aLength)


// Returns OK or FAIL.
{
// It was already open for writing from a prior call. Return failure because callers that do this
// are probably handling things wrong:
if (IsReadyForWrite()) return FAIL;

if (!aBuf)
{
aBuf = _T(””);
aLength = 0;
}
else
if (aLength == UINT_MAX) // Caller wants us to determine the length.
aLength = (UINT)_tcslen(aBuf);

if (aLength)
{
if (!PrepareForWrite(aLength + 1))
return FAIL; // It already displayed the error.
tcslcpy(mClipMemNewLocked, aBuf, aLength + 1); // Copy only a substring, if aLength specifies such.
}
// else just do the below to empty the clipboard, which is different than setting
// the clipboard equal to the empty string: it ' s not truly empty then, as reported
// by IsClipboardFormatAvailable(CF_TEXT) -- and we want to be able to make it truly
// empty for use with functions such as ClipWait:
return Commit(); // It will display any errors.
}

LPTSTR Clipboard::PrepareForWrite(size_t aAllocSize)


{
if (!aAllocSize) return NULL; // Caller should ensure that size is at least 1, i.e. room for the zero
,→ terminator.
if (IsReadyForWrite())
// It was already prepared due to a prior call. Currently, the most useful thing to do
// here is return the memory area that ' s already been reserved:
return mClipMemNewLocked;
// Note: I think GMEM_DDESHARE is recommended in addition to the usual GMEM_MOVEABLE:
// UPDATE: MSDN: ”The following values are obsolete, but are provided for compatibility
// with 16-bit Windows. They are ignored.”: GMEM_DDESHARE
if ( !(mClipMemNew = GlobalAlloc(GMEM_MOVEABLE, aAllocSize * sizeof(TCHAR))) )
{
g_script.ScriptError(_T(”GlobalAlloc”)); // Short error message since so rare.
return NULL;
}
if ( !(mClipMemNewLocked = (LPTSTR)GlobalLock(mClipMemNew)) )
{
mClipMemNew = GlobalFree(mClipMemNew); // This keeps mClipMemNew in sync with its state.
g_script.ScriptError(_T(”GlobalLock”)); // Short error message since so rare.
return NULL;
}
mCapacity = (UINT)aAllocSize; // Keep mCapacity in sync with the state of mClipMemNewLocked.
*mClipMemNewLocked = ' \0 ' ; // Init for caller.

132
return mClipMemNewLocked; // The caller can now write to this mem.
}

ResultType Clipboard::Commit(UINT aFormat)


// If this is called while mClipMemNew is NULL, the clipboard will be set to be truly
// empty, which is different from writing an empty string to it. Note: If the clipboard
// was already physically open, this function will close it as part of the commit (since
// whoever had it open before can ' t use the prior contents, since they ' re invalid).
{
if (!mIsOpen && !Open())
// Since this should be very rare, a shorter message is now used. Formerly, it was
// ”Could not open clipboard for writing after many timed attempts. Another program is probably
,→ holding it open.”
return AbortWrite(CANT_OPEN_CLIPBOARD_WRITE);
if (!EmptyClipboard())
{
Close();
return AbortWrite(_T(”EmptyClipboard”)); // Short error message since so rare.
}
if (mClipMemNew)
{
bool new_is_empty = false;
// Unlock prior to calling SetClipboardData:
if (mClipMemNewLocked) // probably always true if we ' re here.
{
// Best to access the memory while it ' s still locked, which is why this temp var is used:
// v1.0.40.02: The following was fixed to properly recognize 0x0000 as the Unicode string
,→ terminator,
// which fixes problems with Transform Unicode.
new_is_empty = aFormat == CF_UNICODETEXT ? !*(LPWSTR)mClipMemNewLocked : !*(LPSTR)
,→ mClipMemNewLocked;
GlobalUnlock(mClipMemNew); // mClipMemNew not mClipMemNewLocked.
mClipMemNewLocked = NULL; // Keep this in sync with the above action.
mCapacity = 0; // Keep mCapacity in sync with the state of mClipMemNewLocked.
}
if (new_is_empty)
// Leave the clipboard truly empty rather than setting it to be the
// empty string (i.e. these two conditions are NOT the same).
// But be sure to free the memory since we ' re not giving custody
// of it to the system:
mClipMemNew = GlobalFree(mClipMemNew);
else
if (SetClipboardData(aFormat, mClipMemNew))
// In any of the failure conditions above, Close() ensures that mClipMemNew is
// freed if it was allocated. But now that we ' re here, the memory should not be
// freed because it is owned by the clipboard (it will free it at the appropriate time).
// Thus, we relinquish the memory because we shouldn ' t be looking at it anymore:
mClipMemNew = NULL;
else
{
Close();
return AbortWrite(_T(”SetClipboardData”)); // Short error message since so rare.
}
}
// else we will close it after having done only the EmptyClipboard(), above.
// Note: Decided not to update mLength for performance reasons (in case clipboard is huge).
// Anyway, it seems rather pointless because once the clipboard is closed, our app instantly
// loses sight of how large it is, so the the value of mLength wouldn ' t be reliable unless

133
// the clipboard were going to be immediately opened again.
return Close();
}

ResultType Clipboard::AbortWrite(LPTSTR aErrorMessage)


// Always returns FAIL.
{
// Since we were called in conjunction with an aborted attempt to Commit(), always
// ensure the clipboard is physically closed because even an attempt to Commit()
// should physically close it:
Close();
if (mClipMemNewLocked)
{
GlobalUnlock(mClipMemNew); // mClipMemNew not mClipMemNewLocked.
mClipMemNewLocked = NULL;
mCapacity = 0; // Keep mCapacity in sync with the state of mClipMemNewLocked.
}
// Above: Unlock prior to freeing below.
if (mClipMemNew)
mClipMemNew = GlobalFree(mClipMemNew);
// Caller needs us to always return FAIL:
return *aErrorMessage ? g_script.ScriptError(aErrorMessage) : FAIL;
}

ResultType Clipboard::Close(LPTSTR aErrorMessage)


// Returns OK or FAIL (but it only returns FAIL if caller gave us a non-NULL aErrorMessage).
{
// Always close it ASAP so that it is free for other apps to use:
if (mIsOpen)
{
if (mClipMemNowLocked)
{
GlobalUnlock(mClipMemNow); // mClipMemNow not mClipMemNowLocked.
mClipMemNowLocked = NULL; // Keep this in sync with its state, since it ' s used as an indicator.
}
// Above: It ' s probably best to unlock prior to closing the clipboard.
CloseClipboard();
mIsOpen = false; // Even if above fails (realistically impossible?), seems best to do this.
// Must do this only after GlobalUnlock():
mClipMemNow = NULL;
}
// Do this cleanup for callers that didn ' t make it far enough to even open the clipboard.
// UPDATE: DO *NOT* do this because it is valid to have the clipboard in a ”ReadyForWrite”
// state even after we physically close it. Some callers rely on that.
//if (mClipMemNewLocked)
//{
// GlobalUnlock(mClipMemNew); // mClipMemNew not mClipMemNewLocked.
// mClipMemNewLocked = NULL;
// mCapacity = 0; // Keep mCapacity in sync with the state of mClipMemNewLocked.
//}
//// Above: Unlock prior to freeing below.
//if (mClipMemNew)
// // Commit() was never called after a call to PrepareForWrite(), so just free the memory:
// mClipMemNew = GlobalFree(mClipMemNew);
if (aErrorMessage && *aErrorMessage)
// Caller needs us to always return FAIL if an error was displayed:

134
return g_script.ScriptError(aErrorMessage);

// Seems best not to reset mLength. But it will quickly become out of date once
// the clipboard has been closed and other apps can use it.
return OK;
}

HANDLE Clipboard::GetClipboardDataTimeout(UINT uFormat, BOOL *aNullIsOkay)


// Update for v1.1.16: The comments below are obsolete; search for ”v1.1.16” for related comments.
// Same as GetClipboardData() except that it doesn ' t give up if the first call to GetClipboardData() fails.
// Instead, it continues to retry the operation for the number of milliseconds in g_ClipboardTimeout.
// This is necessary because GetClipboardData() has been observed to fail in repeatable situations (this
// is strange because our thread already has the clipboard locked open -- presumably it happens because the
// GetClipboardData() is unable to start a data stream from the application that actually serves up the data).
// If cases where the first call to GetClipboardData() fails, a subsequent call will often succeed if you give
// the owning application (such as Excel and Word) a little time to catch up. This is especially necessary in
// the OnClipboardChange label, where sometimes a clipboard-change notification comes in before the owning
// app has finished preparing its data for subsequent readers of the clipboard.
{
#ifdef DEBUG_BY_LOGGING_CLIPBOARD_FORMATS // Provides a convenient log of clipboard formats for analysis.
static FILE *fp = fopen(”c:\\debug_clipboard_formats.txt”, ”w”);
#endif

if (aNullIsOkay)
*aNullIsOkay = FALSE; // Set default.

TCHAR format_name[MAX_PATH + 1]; // MSDN ' s RegisterClipboardFormat() doesn ' t document any max length, but
,→ the ones we ' re interested in certainly don ' t exceed MAX_PATH.
if (uFormat < 0xC000 || uFormat > 0xFFFF) // It ' s a registered format (you ' re supposed to verify in-range
,→ before calling GetClipboardFormatName()). Also helps performance.
*format_name = ' \0 ' ; // Don ' t need the name if it ' s a standard/CF_* format.
else
{
// v1.0.42.04:
// Probably need to call GetClipboardFormatName() rather than comparing directly to uFormat because
// MSDN implies that OwnerLink and other registered formats might not always have the same ID under
// all OSes (past and future).
GetClipboardFormatName(uFormat, format_name, MAX_PATH);
// Since RegisterClipboardFormat() is case insensitive, the case might vary. So use stricmp() when
// comparing format_name to anything.
// ”Link Source”, ”Link Source Descriptor” , and anything else starting with ”Link Source” is likely
// to be data that should not be attempted to be retrieved because:
// 1) It causes unwanted bookmark effects in various versions of MS Word.
// 2) Tests show that these formats are on the clipboard only if MS Word is open at the time
// ClipboardAll is accessed. That implies they ' re transitory formats that aren ' t as essential
// or well suited to ClipboardAll as the other formats (but if it weren ' t for #1 above, this
// wouldn ' t be enough reason to omit it).
// 3) Although there is hardly any documentation to be found at MSDN or elsewhere about these formats,
// it seems they ' re related to OLE, with further implications that the data is transitory.
// Here are the formats that Word 2002 removes from the clipboard when it the app closes:
// 0xC002 ObjectLink >>> Causes WORD bookmarking problem.
// 0xC003 OwnerLink
// 0xC00D Link Source >>> Causes WORD bookmarking problem.
// 0xC00F Link Source Descriptor >>> Doesn ' t directly cause bookmarking, but probably goes with above
,→ .
// 0xC0DC Hyperlink
if ( !_tcsnicmp(format_name, _T(”Link Source”), 11) || !_tcsicmp(format_name, _T(”ObjectLink”))
|| !_tcsicmp(format_name, _T(”OwnerLink”))

135
v1.0.44.07: The following were added to solve interference with MS Outlook ' s MS Word editor.
//
//
If a hotkey like ˆF1::ClipboardSave:=ClipboardAll is pressed after pressing Ctrl-C in that
//
editor (perhaps only when copying HTML), two of the following error dialogs would otherwise
//
be displayed (this occurs in Outlook 2002 and probably later versions):
//
”An outgoing call cannot be made since the application is dispatching an input-synchronous call
,→ .”
|| !_tcsicmp(format_name, _T(”Native”)) || !_tcsicmp(format_name, _T(”Embed Source”)) )
return NULL;
if (!_tcsicmp(format_name, _T(”MSDEVColumnSelect”)) || !_tcsicmp(format_name, _T(”MSDEVLineSelect”)))
{
// v1.1.16: These formats are used by Visual Studio, Scintilla controls and perhaps others for
// copying whole lines and rectangular blocks. Because their very presence/absence is used as
// a boolean indicator, the data is irrelevant. Presumably for this reason, Scintilla controls
// set NULL data, though doing so and then not handling WM_RENDERFORMAT is probably invalid.
// Note newer versions of Visual Studio use ”VisualStudioEditorOperationsLineCutCopyClipboardTag”
// for line copy, but that doesn ' t need to be handled here because it has non-NULL data (and the
// latest unreleased Scintilla [as at 2014-08-19] uses both, so we can discard the long one).
// Since we just want to preserve this format ' s presence, indicate to caller that NULL is okay:
if (aNullIsOkay) // i.e. caller passed a variable for us to set.
*aNullIsOkay = TRUE;
return NULL;
}
}

#ifdef DEBUG_BY_LOGGING_CLIPBOARD_FORMATS
_ftprintf(fp, _T(”%04X\t%s\n”), uFormat, format_name); // Since fclose() is never called, the program has
,→ to exit to close/release the file.
#endif

#ifndef ENABLE_CLIPBOARDGETDATA_TIMEOUT
// v1.1.16: The timeout and retry behaviour of this function is currently disabled, since it does more
// harm than good. It previously did NO GOOD WHATSOEVER, because SLEEP_WITHOUT_INTERRUPTION indirectly
// calls g_clip.Close() via CLOSE_CLIPBOARD_IF_OPEN, so any subsequent attempts to retrieve data by us
// or our caller always fail. The main point of failure where retrying helps is OpenClipboard(), when
// another program has the clipboard open -- and that ' s handled elsewhere. If the timeout is re-enabled
// for this function, the following format will need to be excluded to prevent unnecessary delays:
// - FileContents (CSTR_FILECONTENTS): MSDN says it is used ”to transfer data as if it were a file,
// regardless of how it is actually stored”. For example, it could be a file inside a zip folder.
// However, on Windows 8 it seems to also be present for normal files. It might be possible to
// retrieve its data via OleGetClipboard(), though it could be very large.

// Just return the data, even if NULL:


return GetClipboardData(uFormat);
#else
HANDLE h;
for (DWORD start_time = GetTickCount();;)
{
// Known failure conditions:
// GetClipboardData() apparently fails when the text on the clipboard is greater than a certain size
// (Even though GetLastError() reports ”Operation completed successfully”). The data size at which
// this occurs is somewhere between 20 to 96 MB (perhaps depending on system ' s memory and CPU speed).
if (h = GetClipboardData(uFormat)) // Assign
return h;

// It failed, so act according to the type of format and the timeout that ' s in effect.
// Certain standard (numerically constant) clipboard formats are known to validly yield NULL from a
// call to GetClipboardData(). Never retry these because it would only cause unnecessary delays
// (i.e. a failure until timeout).
// v1.0.42.04: More importantly, retrying them appears to cause problems with saving a Word/Excel
// clipboard via ClipboardAll.

136
if (uFormat == CF_HDROP // This format can fail ”normally” for the reasons described at ”
,→ clipboard_contains_files”.
|| !_tcsicmp(format_name, _T(”OwnerLink”))) // Known to validly yield NULL from a call to
,→ GetClipboardData(), so don ' t retry it to avoid having to wait the full timeout period.
return NULL;

if (g_ClipboardTimeout != -1) // We were not told to wait indefinitely and...


if (!g_ClipboardTimeout // ...we were told to make only one attempt, or ...
|| (int)(g_ClipboardTimeout - (GetTickCount() - start_time)) <= SLEEP_INTERVAL_HALF) //...it
,→ timed out.
// Above must cast to int or any negative result will be lost due to DWORD type.
return NULL;

// Use SLEEP_WITHOUT_INTERRUPTION to prevent MainWindowProc() from accepting new hotkeys


// during our operation, since a new hotkey subroutine might interfere with
// what we ' re doing here (e.g. if it tries to use the clipboard, or perhaps overwrites
// the deref buffer if this object ' s caller gave it any pointers into that memory area):
SLEEP_WITHOUT_INTERRUPTION(INTERVAL_UNSPECIFIED)
}
#endif
}

ResultType Clipboard::Open()
{
if (mIsOpen)
return OK;
for (DWORD start_time = GetTickCount();;)
{
if (OpenClipboard(g_hWnd))
{
mIsOpen = true;
return OK;
}
if (g_ClipboardTimeout != -1) // We were not told to wait indefinitely...
if (!g_ClipboardTimeout // ...and we were told to make only one attempt, or ...
|| (int)(g_ClipboardTimeout - (GetTickCount() - start_time)) <= SLEEP_INTERVAL_HALF) //...it
,→ timed out.
// Above must cast to int or any negative result will be lost due to DWORD type.
return FAIL;
// Use SLEEP_WITHOUT_INTERRUPTION to prevent MainWindowProc() from accepting new hotkeys
// during our operation, since a new hotkey subroutine might interfere with
// what we ' re doing here (e.g. if it tries to use the clipboard, or perhaps overwrites
// the deref buffer if this object ' s caller gave it any pointers into that memory area):
SLEEP_WITHOUT_INTERRUPTION(INTERVAL_UNSPECIFIED)
}
}
 

137
Registry

138
Registry Utility Functions
 
// Some of these lengths and such are based on the MSDN example at
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/enumerating_registry_subkeys.
,→ asp:
// FIX FOR v1.0.48:
// OLDER (v1.0.44.07): Someone reported that a stack overflow was possible, implying that it only happens
// during extremely deep nesting of subkey names (perhaps a hundred or more nested subkeys). Upon review, it
,→ seems
// that the prior limit of 16383 for value-name-length is higher than needed; testing shows that a value name
,→ can ' t
// be longer than 259 (limit might even be 255 if API vs. RegEdit is used to create the name). Testing also
,→ shows
// that the total path name of a registry item (including item/value name but excluding the name of the root
,→ key)
// obeys the same limit BUT ONLY within the RegEdit GUI. RegEdit seems capable of importing subkeys whose
,→ names
// (even without any value name appended) are longer than 259 characters (see comments higher above).
#define MAX_REG_ITEM_SIZE 1024 // Needs to be greater than 260 (see comments above), but I couldn ' t find any
,→ documentation at MSDN or the web about the max length of a subkey name. One example at MSDN
,→ RegEnumKeyEx() uses MAX_KEY_LENGTH=255 and MAX_VALUE_NAME=16383, but clearly MAX_KEY_LENGTH should be
,→ larger.
#define REG_SUBKEY -2 // Custom type, not standard in Windows.
struct RegItemStruct
{
HKEY root_key_type, root_key; // root_key_type is always a local HKEY, whereas root_key can be a remote
,→ handle.
TCHAR subkey[MAX_REG_ITEM_SIZE]; // The branch of the registry where this subkey or value is located.
TCHAR name[MAX_REG_ITEM_SIZE]; // The subkey or value name.
DWORD type; // Value Type (e.g REG_DWORD).
FILETIME ftLastWriteTime; // Non-initialized.
void InitForValues() {ftLastWriteTime.dwHighDateTime = ftLastWriteTime.dwLowDateTime = 0;}
void InitForSubkeys() {type = REG_SUBKEY;} // To distinguish REG_DWORD and such from the subkeys
,→ themselves.
RegItemStruct(HKEY aRootKeyType, HKEY aRootKey, LPTSTR aSubKey)
: root_key_type(aRootKeyType), root_key(aRootKey), type(REG_NONE)
{
*name = ' \0 ' ;
// Make a local copy on the caller ' s stack so that if the current script subroutine is
// interrupted to allow another to run, the contents of the deref buffer is saved here:
tcslcpy(subkey, aSubKey, _countof(subkey));
// Even though the call may work with a trailing backslash, it ' s best to remove it
// so that consistent results are delivered to the user. For example, if the script
// is enumerating recursively into a subkey, subkeys deeper down will not include the
// trailing backslash when they are reported. So the user ' s own subkey should not
// have one either so that when A_ScriptSubKey is referenced in the script, it will
// always show up as the value without a trailing backslash:
size_t length = _tcslen(subkey);
if (length && subkey[length - 1] == ' \\ ' )
subkey[length - 1] = ' \0 ' ;
}
};

static HKEY RegConvertRootKey(LPTSTR aBuf, bool *aIsRemoteRegistry = NULL)


{
// Even if the computer name is a single letter, it seems like using a colon as delimiter is ok
// (e.g. a:HKEY_LOCAL_MACHINE), since we wouldn ' t expect the root key to be used as a filename
// in that exact way, i.e. a drive letter should be followed by a backslash 99% of the time in
// this context.
// Research indicates that colon is an illegal char in a computer name (at least for NT,

139
// and by extension probably all other OSes). So it should be safe to use it as a delimiter
// for the remote registry feature. But just in case, get the right-most one,
// e.g. Computer:01:HKEY_LOCAL_MACHINE ; the first colon is probably illegal on all OSes.
// Additional notes from the Internet:
// ”A Windows NT computer name can be up to 15 alphanumeric characters with no blank spaces
// and must be unique on the network. It can contain the following special characters:
// ! @ # $ % ˆ & ( ) - ' { } .

// It may not contain:


// \ * + = | : ; ” ? ,
// The following is a list of illegal characters in a computer name:
// regEx.Pattern = ”`|˜|!|@|#|\$|\ˆ|\&|\*|\(|\)|\=|\+|{|}|\\|;|:|'|<|>|/|\?|\||%”
return RegConvertKey(aBuf, REG_OLD_SYNTAX, NULL, aIsRemoteRegistry);
}
static HKEY RegConvertKey(LPTSTR aBuf, RegSyntax aSyntax, LPTSTR *aSubkey = NULL, bool *aIsRemoteRegistry =
,→ NULL)
{
const size_t COMPUTER_NAME_BUF_SIZE = 128;

LPTSTR key_name_pos = aBuf, computer_name_end = NULL;

// Check for a computer name, as in \\ComputerName\HKLM or \\ComputerName:HKLM.


if (*aBuf == ' \\ ' && aBuf[1] == ' \\ ' )
{
LPTSTR delim
= aSyntax == REG_NEW_SYNTAX ? _T(”\\”)
: aSyntax == REG_OLD_SYNTAX ? _T(”:”)
: _T(”\\:”); // REG_EITHER_SYNTAX
if ( !(computer_name_end = StrChrAny(aBuf + 2, delim))
|| (computer_name_end - aBuf) >= COMPUTER_NAME_BUF_SIZE )
return NULL;
key_name_pos = computer_name_end + 1;
if (*computer_name_end == ' : ' ) // For backward-compatibility:
key_name_pos = omit_leading_whitespace(key_name_pos);
}

// Copy root key name into temporary buffer for use by _tcsicmp().
TCHAR key_name[20];
int i;
for (i = 0; key_name_pos[i] && key_name_pos[i] != ' \\ ' ; ++i)
{
if (i == 19)
return NULL; // Too long to be valid.
key_name[i] = key_name_pos[i];
}
key_name[i] = ' \0 ' ;

if (key_name_pos[i] && aSyntax == REG_OLD_SYNTAX) // There ' s a \SubKey, but caller wasn ' t expecting one.
return NULL;

// Set output parameters for caller.


if (aSubkey)
{
if (key_name_pos[i] != ' \\ ' ) // No subkey (not even a blank one).
*aSubkey = (aSyntax == REG_NEW_SYNTAX) ? _T(””) : NULL; // In REG_EITHER_SYNTAX mode, caller wants
,→ to know it was omitted.
else
*aSubkey = key_name_pos + i + 1; // +1 for the slash.
}
if (aIsRemoteRegistry)
*aIsRemoteRegistry = (computer_name_end != NULL);

140
HKEY root_key;
if (!_tcsicmp(key_name, _T(”HKLM”)) || !_tcsicmp(key_name, _T(”HKEY_LOCAL_MACHINE”))) root_key =
,→ HKEY_LOCAL_MACHINE;
else if (!_tcsicmp(key_name, _T(”HKCR”)) || !_tcsicmp(key_name, _T(”HKEY_CLASSES_ROOT”))) root_key =
,→ HKEY_CLASSES_ROOT;
else if (!_tcsicmp(key_name, _T(”HKCC”)) || !_tcsicmp(key_name, _T(”HKEY_CURRENT_CONFIG”))) root_key =
,→ HKEY_CURRENT_CONFIG;
else if (!_tcsicmp(key_name, _T(”HKCU”)) || !_tcsicmp(key_name, _T(”HKEY_CURRENT_USER”))) root_key =
,→ HKEY_CURRENT_USER;
else if (!_tcsicmp(key_name, _T(”HKU”)) || !_tcsicmp(key_name, _T(”HKEY_USERS”))) root_key =
,→ HKEY_USERS;
else // Invalid or unsupported root key name.
return NULL;

if (!aIsRemoteRegistry || !computer_name_end) // Either caller didn ' t want it opened, or it doesn ' t need
,→ to be.
return root_key; // If it ' s a remote key, this value should only be used by the caller as an indicator
,→ .
// Otherwise, it ' s a remote computer whose registry the caller wants us to open:
// It seems best to require the two leading backslashes in case the computer name contains
// spaces (just in case spaces are allowed on some OSes or perhaps for Unix interoperability, etc.).
// Therefore, make no attempt to trim leading and trailing spaces from the computer name:
TCHAR computer_name[COMPUTER_NAME_BUF_SIZE];
tcslcpy(computer_name, aBuf, _countof(computer_name));
computer_name[computer_name_end - aBuf] = ' \0 ' ;
HKEY remote_key;
return (RegConnectRegistry(computer_name, root_key, &remote_key) == ERROR_SUCCESS) ? remote_key : NULL;
}
static LPTSTR RegConvertRootKey(LPTSTR aBuf, size_t aBufSize, HKEY aRootKey)
{
// switch() doesn ' t directly support expression of type HKEY:
if (aRootKey == HKEY_LOCAL_MACHINE) tcslcpy(aBuf, _T(”HKEY_LOCAL_MACHINE”), aBufSize);
else if (aRootKey == HKEY_CLASSES_ROOT) tcslcpy(aBuf, _T(”HKEY_CLASSES_ROOT”), aBufSize);
else if (aRootKey == HKEY_CURRENT_CONFIG) tcslcpy(aBuf, _T(”HKEY_CURRENT_CONFIG”), aBufSize);
else if (aRootKey == HKEY_CURRENT_USER) tcslcpy(aBuf, _T(”HKEY_CURRENT_USER”), aBufSize);
else if (aRootKey == HKEY_USERS) tcslcpy(aBuf, _T(”HKEY_USERS”), aBufSize);
else if (aBufSize) *aBuf = ' \0 ' ; // Make it be the empty string for anything else.
// These are either unused or so rarely used (DYN_DATA on Win9x) that they aren ' t supported:
// HKEY_PERFORMANCE_DATA, HKEY_PERFORMANCE_TEXT, HKEY_PERFORMANCE_NLSTEXT, HKEY_DYN_DATA
return aBuf;
}
static int RegConvertValueType(LPTSTR aValueType)
{
if (!_tcsicmp(aValueType, _T(”REG_SZ”))) return REG_SZ;
if (!_tcsicmp(aValueType, _T(”REG_EXPAND_SZ”))) return REG_EXPAND_SZ;
if (!_tcsicmp(aValueType, _T(”REG_MULTI_SZ”))) return REG_MULTI_SZ;
if (!_tcsicmp(aValueType, _T(”REG_DWORD”))) return REG_DWORD;
if (!_tcsicmp(aValueType, _T(”REG_BINARY”))) return REG_BINARY;
return REG_NONE; // Unknown or unsupported type.
}
static LPTSTR RegConvertValueType(LPTSTR aBuf, size_t aBufSize, DWORD aValueType)
{
switch(aValueType)
{
case REG_SZ: tcslcpy(aBuf, _T(”REG_SZ”), aBufSize); return aBuf;
case REG_EXPAND_SZ: tcslcpy(aBuf, _T(”REG_EXPAND_SZ”), aBufSize); return aBuf;
case REG_BINARY: tcslcpy(aBuf, _T(”REG_BINARY”), aBufSize); return aBuf;
case REG_DWORD: tcslcpy(aBuf, _T(”REG_DWORD”), aBufSize); return aBuf;
case REG_DWORD_BIG_ENDIAN: tcslcpy(aBuf, _T(”REG_DWORD_BIG_ENDIAN”), aBufSize); return aBuf;

141
case REG_LINK: tcslcpy(aBuf, _T(”REG_LINK”), aBufSize); return aBuf;
case REG_MULTI_SZ: tcslcpy(aBuf, _T(”REG_MULTI_SZ”), aBufSize); return aBuf;
case REG_RESOURCE_LIST: tcslcpy(aBuf, _T(”REG_RESOURCE_LIST”), aBufSize); return aBuf;
case REG_FULL_RESOURCE_DESCRIPTOR: tcslcpy(aBuf, _T(”REG_FULL_RESOURCE_DESCRIPTOR”), aBufSize); return
,→ aBuf;
case REG_RESOURCE_REQUIREMENTS_LIST: tcslcpy(aBuf, _T(”REG_RESOURCE_REQUIREMENTS_LIST”), aBufSize); return
,→ aBuf;
case REG_QWORD: tcslcpy(aBuf, _T(”REG_QWORD”), aBufSize); return aBuf;
case REG_SUBKEY: tcslcpy(aBuf, _T(”KEY”), aBufSize); return aBuf; // Custom (non-standard) type.
default: if (aBufSize) *aBuf = ' \0 ' ; return aBuf; // Make it be the empty string for REG_NONE and
,→ anything else.
}
}
static DWORD RegConvertView(LPTSTR aBuf)
{
if (!_tcsicmp(aBuf, _T(”Default”)))
return 0;
else if (!_tcscmp(aBuf, _T(”32”)))
return KEY_WOW64_32KEY;
else if (!_tcscmp(aBuf, _T(”64”)))
return KEY_WOW64_64KEY;
else
return -1;
}
 

142
RegDelete

Deletes a subkey or value from the registry.


 
RegDelete, RootKey\SubKey [, ValueName] ; v1.1.21+
RegDelete, RootKey, SubKey [, ValueName]
 
Parameters

• RootKey Must be either HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, or HKEY_CURRENT_CONFIG


(or the abbreviations for each of these, such as HKLM). To access a remote registry, prepend the computer name and a colon (or in v1.1.21+, a
slash), as in this example: \\workstation01:HKEY_LOCAL_MACHINE

• SubKey The name of the subkey (e.g. Software\SomeApplication).

• RootKey\SubKey If RootKey is followed immediately by a slash (\), RootKey and SubKey are merged into a single parameter.

• ValueName The name of the value to delete. If omitted, the entire SubKey will be deleted. To delete Subkey’s default value – which is the value
displayed as “(Default)” by RegEdit – use the phrase AHK_DEFAULT for this parameter.

Remarks

Deleting from the registry is potential dangerous - please exercise caution!

To retrieve and operate upon multiple registry keys or values, consider using a registry-loop.

For details about how to access the registry of a remote computer, see the remarks in registry-loop.

To delete entries from the 64-bit sections of the registry in a 32-bit script or vice versa, use SetRegView.
 
LONG Line::RegRemoveSubkeys(HKEY hRegKey)
{
// Removes all subkeys to the given key. Will not touch the given key.
TCHAR Name[256];
DWORD dwNameSize;
FILETIME ftLastWrite;
HKEY hSubKey;
LONG result;

for (;;)
{ // infinite loop
dwNameSize = _countof(Name)-1;
if (RegEnumKeyEx(hRegKey, 0, Name, &dwNameSize, NULL, NULL, NULL, &ftLastWrite) == ERROR_NO_MORE_ITEMS
,→ )
return ERROR_SUCCESS;
result = RegOpenKeyEx(hRegKey, Name, 0, KEY_READ | g->RegView, &hSubKey);
if (result != ERROR_SUCCESS)
break;
result = RegRemoveSubkeys(hSubKey);
RegCloseKey(hSubKey);
if (result != ERROR_SUCCESS)
break;
result = RegDeleteKey(hRegKey, Name);
if (result != ERROR_SUCCESS)
break;
}
return result;
}

ResultType Line::RegDelete(HKEY aRootKey, LPTSTR aRegSubkey, LPTSTR aValueName)


{
LONG result;

// Fix for v1.0.48: Don ' t remove the entire key if it ' s a root key! According to MSDN,
// the root key would be opened by RegOpenKeyEx() further below whenever aRegSubkey is NULL

143
// or an empty string. aValueName is also checked to preserve the ability to delete a value
// that exists directly under a root key.
if ( !aRootKey
|| (!aRegSubkey || !*aRegSubkey) && !aValueName ) // See comment above.
{
result = ERROR_INVALID_PARAMETER;
goto finish;
}

// Open the key we want


HKEY hRegKey;
result = RegOpenKeyEx(aRootKey, aRegSubkey, 0, KEY_READ | KEY_WRITE | g->RegView, &hRegKey);
if (result != ERROR_SUCCESS)
goto finish;

if (!aValueName) // Caller ' s signal to delete the entire subkey.


{
// Remove the entire Key
result = RegRemoveSubkeys(hRegKey); // Delete any subitems within the key.
RegCloseKey(hRegKey); // Close parent key. Not sure if this needs to be done only after the above.
if (result == ERROR_SUCCESS)
{
typedef LONG (WINAPI * PFN_RegDeleteKeyEx)(HKEY hKey , LPCTSTR lpSubKey , REGSAM samDesired ,
,→ DWORD Reserved );
static PFN_RegDeleteKeyEx _RegDeleteKeyEx = (PFN_RegDeleteKeyEx)GetProcAddress(GetModuleHandle(_T(
,→ ”advapi32”)), ”RegDeleteKeyEx” WINAPI_SUFFIX);
if (g->RegView && _RegDeleteKeyEx)
result = _RegDeleteKeyEx(aRootKey, aRegSubkey, g->RegView, 0);
else
result = RegDeleteKey(aRootKey, aRegSubkey);
}
}
else
{
// Remove the value.
result = RegDeleteValue(hRegKey, aValueName);
RegCloseKey(hRegKey);
}

finish:
return SetErrorsOrThrow(result != ERROR_SUCCESS, result);
} // RegDelete()
 

144
RegRead

Reads a value from the registry.


 
RegRead, OutputVar, RootKey\SubKey [, ValueName] ; v1.1.21+
RegRead, OutputVar, RootKey, SubKey [, ValueName]
 
Parameters

• OutputVar The name of the variable in which to store the retrieved value. If the value cannot be retrieved, the variable is made blank and ErrorLevel
is set to 1.

• RootKey Must be either HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, or HKEY_CURRENT_CONFIG


(or the abbreviations for each of these, such as HKLM). To access a remote registry, prepend the computer name and a colon (or in v1.1.21+, a
slash), as in this example: \\workstation01:HKEY_LOCAL_MACHINE

• SubKey The name of the subkey (e.g. Software\SomeApplication).

• RootKey\SubKey If RootKey is followed immediately by a slash (\), RootKey and SubKey are merged into a single parameter.

• ValueName The name of the value to retrieve. If omitted, Subkey’s default value will be retrieved, which is the value displayed as “(Default)” by
RegEdit. If there is no default value (that is, if RegEdit displays “value not set”), OutputVar is made blank and ErrorLevel is set to 1.

Remarks

Currently only the following value types are supported: REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD, and REG_BINARY.

REG_DWORD values are always expressed as positive decimal numbers.

When reading a REG_BINARY key the result is a string of hex characters. For example, the REG_BINARY value of 01,a9,ff,77 will be read as the
string 01A9FF77.

When reading a REG_MULTI_SZ key, each of the components ends in a linefeed character (‘n). If there are no components, OutputVar will be made
blank. See FileSelectFile for an example of how to extract the individual components from OutputVar.

REG_BINARY values larger than 64K can only be read in v1.1.10.01 and later.

To retrieve and operate upon multiple registry keys or values, consider using a registry-loop.

For details about how to access the registry of a remote computer, see the remarks in registry-loop.

To read and write entries from the 64-bit sections of the registry in a 32-bit script or vice versa, use SetRegView.
 
ResultType Line::RegRead(HKEY aRootKey, LPTSTR aRegSubkey, LPTSTR aValueName)
{
Var &output_var = *OUTPUT_VAR;
output_var.Assign(); // Init. Tell it not to free the memory by not calling without parameters.

HKEY hRegKey;
DWORD dwRes, dwBuf, dwType;
LONG result;

if (!aRootKey)
{
result = ERROR_INVALID_PARAMETER; // Indicate the error.
goto finish;
}

// Open the registry key


result = RegOpenKeyEx(aRootKey, aRegSubkey, 0, KEY_READ | g->RegView, &hRegKey);
if (result != ERROR_SUCCESS)
goto finish;

// Read the value and determine the type. If aValueName is the empty string, the key ' s default value is
,→ used.
result = RegQueryValueEx(hRegKey, aValueName, NULL, &dwType, NULL, NULL);
if (result != ERROR_SUCCESS)

145
{
RegCloseKey(hRegKey);
goto finish;
}

LPTSTR contents, cp;

// The way we read is different depending on the type of the key


switch (dwType)
{
case REG_DWORD:
dwRes = sizeof(dwBuf);
result = RegQueryValueEx(hRegKey, aValueName, NULL, NULL, (LPBYTE)&dwBuf, &dwRes);
if (result == ERROR_SUCCESS)
output_var.Assign((DWORD)dwBuf);
RegCloseKey(hRegKey);
break;

// Note: The contents of any of these types can be >64K on NT/2k/XP+ (though that is probably rare):
case REG_SZ:
case REG_EXPAND_SZ:
case REG_MULTI_SZ:
{
dwRes = 0; // Retained for backward compatibility because values >64K may cause it to fail on
,→ Win95 (unverified, and MSDN implies its value should be ignored for the following call).
// MSDN: If lpData is NULL, and lpcbData is non-NULL, the function returns ERROR_SUCCESS and
,→ stores
// the size of the data, in bytes, in the variable pointed to by lpcbData.
result = RegQueryValueEx(hRegKey, aValueName, NULL, NULL, NULL, &dwRes); // Find how large the
,→ value is.
if (result != ERROR_SUCCESS || !dwRes) // Can ' t find size (realistically might never happen), or
,→ size is zero.
{
RegCloseKey(hRegKey);
// Above realistically probably never happens; if it does and result != ERROR_SUCCESS,
// setting ErrorLevel to indicate the error seems more useful than maintaining backward-
// compatibility by faking success.
break;
}
DWORD dwCharLen = dwRes / sizeof(TCHAR);
// Set up the variable to receive the contents, enlarging it if necessary:
// Since dwRes includes the space for the zero terminator (if the MSDN docs
// are accurate), this will enlarge it to be 1 byte larger than we need,
// which leaves room for the final newline character to be inserted after
// the last item. But add 2 to the requested capacity in case the data isn ' t
// terminated in the registry, which allows double-NULL to be put in for REG_MULTI_SZ later.
if (output_var.AssignString(NULL, (VarSizeType)(dwCharLen + 2)) != OK)
{
RegCloseKey(hRegKey);
return FAIL; // FAIL is only returned when the error is a critical one such as this one.
}

contents = output_var.Contents(); // This target buf should now be large enough for the result.

result = RegQueryValueEx(hRegKey, aValueName, NULL, NULL, (LPBYTE)contents, &dwRes);


RegCloseKey(hRegKey);

if (result != ERROR_SUCCESS || !dwRes) // Relies on short-circuit boolean order.


{
*contents = ' \0 ' ; // MSDN says the contents of the buffer is undefined after the call in some

146
,→ cases, so reset it.
// Above realistically probably never happens; if it does and result != ERROR_SUCCESS,
// setting ErrorLevel to indicate the error seems more useful than maintaining backward-
// compatibility by faking success.
}
else
{
dwCharLen = dwRes / sizeof(TCHAR);
// See ReadRegString() for more comments about the following:
// The MSDN docs state that we should ensure that the buffer is NULL-terminated ourselves:
// ”If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, then lpcbData will also
// include the size of the terminating null character or characters ... If the data has the
// REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with the
// proper null-terminating characters. Applications should ensure that the string is properly
// terminated before using it, otherwise, the application may fail by overwriting a buffer.”
//
// Double-terminate so that the loop can find out the true end of the buffer.
// The MSDN docs cited above are a little unclear. The most likely interpretation is that
// dwRes contains the true size retrieved. For example, if dwRes is 1, the first char
// in the buffer is either a NULL or an actual non-NULL character that was originally
// stored in the registry incorrectly (i.e. without a terminator). In either case, do
// not change the first character, just leave it as is and add a NULL at the 2nd and
// 3rd character positions to ensure that it is double terminated in every case:
contents[dwCharLen] = contents[dwCharLen + 1] = ' \0 ' ;

if (dwType == REG_MULTI_SZ) // Convert NULL-delimiters into newline delimiters.


{
for (cp = contents;; ++cp)
{
if (!*cp)
{
// Unlike AutoIt3, it seems best to have a newline character after the
// last item in the list also. It usually makes parsing easier:
*cp = ' \n ' ; // Convert to \n for later storage in the user ' s variable.
if (!*(cp + 1)) // Buffer is double terminated, so this is safe.
// Double null terminator marks the end of the used portion of the buffer.
break;
}
}
// else the buffer is empty (see above notes for explanation). So don ' t put any newlines
// into it at all, since each newline should correspond to an item in the buffer.
}
}
output_var.SetCharLength((VarSizeType)_tcslen(contents)); // Due to conservative buffer sizes
,→ above, length is probably too large by 3. So update to reflect the true length.
if (!output_var.Close()) // Must be called after Assign(NULL, ...) or when Contents() has been
,→ altered because it updates the variable ' s attributes and properly handles VAR_CLIPBOARD.
return FAIL;
break;
}
case REG_BINARY:
{
// See case REG_SZ for comments.
dwRes = 0;
result = RegQueryValueEx(hRegKey, aValueName, NULL, NULL, NULL, &dwRes); // Find how large the
,→ value is.
if (result != ERROR_SUCCESS || !dwRes) // Can ' t find size (realistically might never happen), or
,→ size is zero.
{
RegCloseKey(hRegKey);

147
break;
}

// Set up the variable to receive the contents, enlarging it if necessary.


// AutoIt3: Each byte will turned into 2 digits, plus a final null:
if (output_var.AssignString(NULL, (VarSizeType)(dwRes * 2)) != OK)
{
RegCloseKey(hRegKey);
return FAIL;
}
contents = output_var.Contents();
*contents = ' \0 ' ;

// Read the binary data into the variable, placed so that the last byte of
// binary data will be overwritten as the hexadecimal conversion completes.
LPBYTE pRegBuffer = (LPBYTE)(contents + dwRes * 2) - dwRes;
result = RegQueryValueEx(hRegKey, aValueName, NULL, NULL, pRegBuffer, &dwRes);
RegCloseKey(hRegKey);

if (result != ERROR_SUCCESS)
break;

int j = 0;
DWORD i, n; // i and n must be unsigned to work
TCHAR szHexData[] = _T(”0123456789ABCDEF”); // Access to local vars might be faster than static
,→ ones.
for (i = 0; i < dwRes; ++i)
{
n = pRegBuffer[i]; // Get the value and convert to 2 digit hex
contents[j + 1] = szHexData[n % 16];
n /= 16;
contents[j] = szHexData[n % 16];
j += 2;
}
contents[j] = ' \0 ' ; // Terminate
if (!output_var.Close()) // Length() was already set by the earlier call to Assign().
return FAIL;
break;
}
default:
RegCloseKey(hRegKey);
result = ERROR_UNSUPPORTED_TYPE; // Indicate the error.
break;
}

finish:
return SetErrorsOrThrow(result != ERROR_SUCCESS, result);
} // RegRead()
 

148
RegWrite

Writes a value to the registry.


 
RegWrite, ValueType, RootKey\SubKey [, ValueName, Value] ; v1.1.21+
RegWrite, ValueType, RootKey, SubKey [, ValueName, Value]
 
Parameters

• ValueType Must be either REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD, or REG_BINARY.

• RootKey Must be either HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, or HKEY_CURRENT_CONFIG


(or the abbreviations for each of these, such as HKLM). To access a remote registry, prepend the computer name and a colon (or in v1.1.21+, a
slash), as in this example: \\workstation01:HKEY_LOCAL_MACHINE

• SubKey The name of the subkey (e.g. Software\SomeApplication). If SubKey does not exist, it is created (along with its ancestors, if necessary).
If SubKey is left blank, the value is written directly into RootKey (though some operating systems might refuse to write in HKEY_CURRENT_USER’s
top level).

• RootKey\SubKey If RootKey is followed immediately by a slash (\), RootKey and SubKey are merged into a single parameter.

• ValueName The name of the value that will be written to. If blank or omitted, Subkey’s default value will be used, which is the value displayed as
“(Default)” by RegEdit.

• Value The value to be written. If omitted, it will default to an empty (blank) string, or 0, depending on ValueType. If the text is long, it can be
broken up into several shorter lines by means of a continuation section, which might improve readability and maintainability.

Remarks

If ValueType is REG_DWORD, Value should be between -2147483648 and 4294967295 (0xFFFFFFFF).

When writing a REG_BINARY key, use a string of hex characters, e.g. the REG_BINARY value of 01,a9,ff,77 can be written by using the string 01A9FF77.

When writing a REG_MULTI_SZ key, you must separate each component from the next with a linefeed character (n). The last component may
,→ optionally end with a linefeed as well. No blank components are allowed. In other words, do not specify two
,→ linefeeds in a row (n‘n) because that will result in a shorter-than-expected value being written to the registry.
REG_BINARY and REG_MULTI_SZ values larger than 64K are supported in v1.1.10.01 and later. In older versions, they are truncated to 64K.

To retrieve and operate upon multiple registry keys or values, consider using a registry-loop.

For details about how to access the registry of a remote computer, see the remarks in registry-loop.

To read and write entries from the 64-bit sections of the registry in a 32-bit script or vice versa, use SetRegView.
 
ResultType Line::RegWrite(DWORD aValueType, HKEY aRootKey, LPTSTR aRegSubkey, LPTSTR aValueName, LPTSTR aValue
,→ )
// If aValueName is the empty string, the key ' s default value is used.
{
HKEY hRegKey;
DWORD dwRes, dwBuf;

TCHAR *buf;
#define SET_REG_BUF buf = aValue;

LONG result;

if (!aRootKey || aValueType == REG_NONE || aValueType == REG_SUBKEY) // Can ' t write to these.


{
result = ERROR_INVALID_PARAMETER; // Indicate the error.
goto finish;
}

// Open/Create the registry key


// The following works even on root keys (i.e. blank subkey), although values can ' t be created/written to
// HKCU ' s root level, perhaps because it ' s an alias for a subkey inside HKEY_USERS. Even when
,→ RegOpenKeyEx()

149
// is used on HKCU (which is probably redundant since it ' s a pre-opened key?), the API can ' t create values
// there even though RegEdit can.
result = RegCreateKeyEx(aRootKey, aRegSubkey, 0, _T(””), REG_OPTION_NON_VOLATILE, KEY_WRITE | g->RegView,
,→ NULL, &hRegKey, &dwRes);
if (result != ERROR_SUCCESS)
goto finish;

// Write the registry differently depending on type of variable we are writing


switch (aValueType)
{
case REG_SZ:
SET_REG_BUF
result = RegSetValueEx(hRegKey, aValueName, 0, REG_SZ, (CONST BYTE *)buf, (DWORD)(_tcslen(buf)+1) *
,→ sizeof(TCHAR));
break;

case REG_EXPAND_SZ:
SET_REG_BUF
result = RegSetValueEx(hRegKey, aValueName, 0, REG_EXPAND_SZ, (CONST BYTE *)buf, (DWORD)(_tcslen(buf)
,→ +1) * sizeof(TCHAR));
break;

case REG_MULTI_SZ:
{
size_t length = _tcslen(aValue);
// Allocate some temporary memory because aValue might not be a writable string,
// and we would need to write to it to temporarily change the newline delimiters into
// zero-delimiters. Even if we were to require callers to give us a modifiable string,
// its capacity may be 1 byte too small to handle the double termination that ' s needed
// (i.e. if the last item in the list happens to not end in a newline):
buf = tmalloc(length + 2);
if (!buf)
{
result = ERROR_OUTOFMEMORY;
break;
}
tcslcpy(buf, aValue, length + 1);
// Double-terminate:
buf[length + 1] = ' \0 ' ;

// Remove any final newline the user may have provided since we don ' t want the length
// to include it when calling RegSetValueEx() -- it would be too large by 1:
if (length > 0 && buf[length - 1] == ' \n ' )
buf[--length] = ' \0 ' ;

// Replace the script ' s delimiter char with the zero-delimiter needed by RegSetValueEx():
for (LPTSTR cp = buf; *cp; ++cp)
if (*cp == ' \n ' )
*cp = ' \0 ' ;

result = RegSetValueEx(hRegKey, aValueName, 0, REG_MULTI_SZ, (CONST BYTE *)buf


, (DWORD)(length ? length + 2 : 0) * sizeof(TCHAR));
free(buf);
break;
}

case REG_DWORD:
if (*aValue)
dwBuf = ATOU(aValue); // Changed to ATOU() for v1.0.24 so that hex values are supported.
else // Default to 0 when blank.

150
dwBuf = 0;
result = RegSetValueEx(hRegKey, aValueName, 0, REG_DWORD, (CONST BYTE *)&dwBuf, sizeof(dwBuf));
break;

case REG_BINARY:
{
int nLen = (int)_tcslen(aValue);

// String length must be a multiple of 2


if (nLen % 2)
{
result = ERROR_INVALID_PARAMETER;
break;
}

int nBytes = nLen / 2;


LPBYTE pRegBuffer = (LPBYTE) malloc(nBytes);
if (!pRegBuffer)
{
result = ERROR_OUTOFMEMORY;
break;
}

// Really crappy hex conversion


int j = 0, i = 0, nVal, nMult;
while (i < nLen && j < nBytes)
{
nVal = 0;
for (nMult = 16; nMult >= 0; nMult = nMult - 15)
{
if (aValue[i] >= ' 0 ' && aValue[i] <= ' 9 ' )
nVal += (aValue[i] - ' 0 ' ) * nMult;
else if (aValue[i] >= ' A ' && aValue[i] <= ' F ' )
nVal += (((aValue[i] - ' A ' ))+10) * nMult;
else if (aValue[i] >= ' a ' && aValue[i] <= ' f ' )
nVal += (((aValue[i] - ' a ' ))+10) * nMult;
else
{
free(pRegBuffer);
RegCloseKey(hRegKey);
result = ERROR_INVALID_PARAMETER;
goto finish;
}
++i;
}
pRegBuffer[j++] = (char)nVal;
}

result = RegSetValueEx(hRegKey, aValueName, 0, REG_BINARY, pRegBuffer, (DWORD)j);


free(pRegBuffer);
break;
}

default:
result = ERROR_INVALID_PARAMETER; // Anything other than ERROR_SUCCESS.
break;
} // switch()

RegCloseKey(hRegKey);
// Additionally, fall through to below:

151
finish:
return SetErrorsOrThrow(result != ERROR_SUCCESS, result);
} // RegWrite()
 

152
Loop (registry)

Retrieves the contents of the specified registry subkey, one item at a time.
 
Loop, Reg, RootKey[\Key, Mode] ; v1.1.21+ (recommended)
Loop, RootKey [, Key, IncludeSubkeys?, Recurse?]
 
Parameters

• Reg The literal word Reg (case-insensitive). This cannot be a variable or expression. If this keyword is present, Key must be separated from
RootKey by a slash instead of a comma, and both can be contained within a single variable. For example, Loop, Reg, HKLM\Software or
Loop, Reg, %FullPathOfKey%.

• RootKey Must be either HKEY_LOCAL_MACHINE (or HKLM), HKEY_USERS (or HKU), HKEY_CURRENT_USER (or HKCU), HKEY_CLASSES_ROOT
(or HKCR), or HKEY_CURRENT_CONFIG (or HKCC).

To access a remote registry, prepend the computer name and a colon, as in this example: \\workstation01:HKEY_LOCAL_MACHINE

• Key The name of the key (e.g. Software\SomeApplication). If blank or omitted, the contents of RootKey will be retrieved.

• Mode Zero or more of the following letters:

– K: Include keys.
– V: Include values. Values are also included if both K and V are omitted.
– R: Recurse into subkeys. If R is omitted, keys and values within subkeys of Key are not included.

• IncludeSubkeys?

– 0 (default) Subkeys contained within Key are not retrieved (only the values).
– 1 All values and subkeys are retrieved.
– 2 Only the subkeys are retrieved (not the values).

• Recurse?

– 0 (default) Subkeys are not recursed into.


– 1 Subkeys are recursed into, so that all values and subkeys contained therein are retrieved.

Remarks

A registry-loop is useful when you want to operate on a collection registry values or subkeys, one at a time. The values and subkeys are retrieved in
reverse order (bottom to top) so that RegDelete can be used inside the loop without disrupting the loop.

The following variables exist within any registry-loop. If an inner registry-loop is enclosed by an outer registry-loop, the innermost loop’s registry item will
take precedence:

• A_LoopRegName: Name of the currently retrieved item, which can be either a value name or the name of a subkey. Value names displayed by
Windows RegEdit as “(Default)” will be retrieved if a value has been assigned to them, but A_LoopRegName will be blank for them.
• A_LoopRegType: The type of the currently retrieved item, which is one of the following words: KEY (i.e. the currently retrieved item is a
subkey not a value), REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_LINK, REG_RESOURCE_LIST,
REG_FULL_RESOURCE_DESCRIPTOR, REG_RESOURCE_REQUIREMENTS_LIST, REG_DWORD_BIG_ENDIAN (probably rare on most Windows
hardware). It will be empty if the currently retrieved item is of an unknown type.
• A_LoopRegKey: The name of the root key being accessed (HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT
,→ , or HKEY_CURRENT_CONFIG). For remote registry access, this value will not include the computer name.
• A_LoopRegSubKey: Name of the current SubKey. This will be the same as the Key parameter unless the Recurse parameter is being used
to recursively explore other subkeys. In that case, it will be the full path of the currently retrieved item, not including the root key. For example:
Software\SomeApplication\My SubKey
• A_LoopRegTimeModified: The time the current subkey or any of its values was last modified. Format YYYYMMDDHH24MISS. This variable
will be empty if the currently retrieved item is not a subkey (i.e. A_LoopRegType is not the word KEY).

When used inside a registry-loop, the following commands can be used in a simplified way to indicate that the currently retrieved item should be operated
upon:

• RegRead, OutputVar: Reads the current item. If the current item is a key, ErrorLevel will be set to 1 and OutputVar will be made empty.
• RegWrite [, Value]: Writes to the current item. If Value is omitted, the item will be made 0 or blank depending on its type. If the current item
is a key, ErrorLevel will be set to 1 and there will be no other effect.
• RegDelete: Deletes the current item. If the current item is a key, it will be deleted along with any subkeys and values it contains.

When accessing a remote registry (via the RootKey parameter described above), the following notes apply:

153
• The target machine must be running the Remote Registry service.
• Access to a remote registry may fail if the target computer is not in the same domain as yours or the local or remote username lacks sufficient
permissions (however, see below for possible workarounds).
• Depending on your username’s domain, workgroup, and/or permissions, you may have to connect to a shared device, such as by mapping a
drive, prior to attempting remote registry access. Making such a connection – using a remote username and password that has permission to
access or edit the registry – may as a side-effect enable remote registry access.
• If you’re already connected to the target computer as a different user (for example, a mapped drive via user Guest), you may have to terminate
that connection to allow the remote registry feature to reconnect and re-authenticate you as your own currently logged-on username.
• For Windows Server 2003 and Windows XP Professional, MSDN states: “If the [registry server] computer is joined to a workgroup and the Force
network logons using local accounts to authenticate as Guest policy is enabled, the function fails. Note that this policy is enabled by default if the
computer is joined to a workgroup.”
• For Windows XP Home Edition, MSDN states that “this function always fails”. Home Edition lacks both the registry server and client, though the
client can be extracted from one of the OS cab files.
 
ResultType Line::PerformLoopReg(ExprTokenType *aResultToken, bool &aContinueMainLoop, Line *&aJumpToLine, Line
,→ *aUntil
, FileLoopModeType aFileLoopMode, bool aRecurseSubfolders, HKEY aRootKeyType, HKEY aRootKey, LPTSTR
,→ aRegSubkey)
// aRootKeyType is the type of root key, independent of whether it ' s local or remote.
// This is used because there ' s no easy way to determine which root key a remote HKEY
// refers to.
{
RegItemStruct reg_item(aRootKeyType, aRootKey, aRegSubkey);
HKEY hRegKey;

// Open the specified subkey. Be sure to only open with the minimum permission level so that
// the keys & values can be deleted or written to (though I ' m not sure this would be an issue
// in most cases):
if (RegOpenKeyEx(reg_item.root_key, reg_item.subkey, 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | g->
,→ RegView, &hRegKey) != ERROR_SUCCESS)
return OK;

// Get the count of how many values and subkeys are contained in this parent key:
DWORD count_subkeys;
DWORD count_values;
if (RegQueryInfoKey(hRegKey, NULL, NULL, NULL, &count_subkeys, NULL, NULL
, &count_values, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
{
RegCloseKey(hRegKey);
return OK;
}

ResultType result;
Line *jump_to_line;
DWORD i;
global_struct &g = *::g; // Primarily for performance in this case.

// See comments in PerformLoop() for details about this section.


// Note that &reg_item is passed to ExecUntil() rather than... (comment was never finished).
#define MAKE_SCRIPT_LOOP_PROCESS_THIS_ITEM \
{\
g.mLoopRegItem = &reg_item;\
if (mNextLine->mActionType == ACT_BLOCK_BEGIN)\
do\
result = mNextLine->mNextLine->ExecUntil(UNTIL_BLOCK_END, aResultToken, &jump_to_line);\
while (jump_to_line == mNextLine);\
else\
result = mNextLine->ExecUntil(ONLY_ONE_LINE, aResultToken, &jump_to_line);\
if (jump_to_line && !(result == LOOP_CONTINUE && jump_to_line == this))\
{\

154
if (jump_to_line == this)\
aContinueMainLoop = true;\
else\
aJumpToLine = jump_to_line;\
RegCloseKey(hRegKey);\
return result;\
}\
if ( result != OK && result != LOOP_CONTINUE \
|| (aUntil && aUntil->EvaluateLoopUntil(result)) ) \
{\
RegCloseKey(hRegKey);\
return result;\
}\
++g.mLoopIteration;\
}

DWORD name_size;

// First enumerate the values, which are analogous to files in the file system.
// Later, the subkeys (”subfolders”) will be done:
if (count_values > 0 && aFileLoopMode != FILE_LOOP_FOLDERS_ONLY) // The caller doesn ' t want ”files” (
,→ values) excluded.
{
reg_item.InitForValues();
// Going in reverse order allows values to be deleted without disrupting the enumeration,
// at least in some cases:
for (i = count_values - 1;; --i)
{
// Don ' t use CONTINUE in loops such as this due to the loop-ending condition being explicitly
// checked at the bottom.
name_size = _countof(reg_item.name); // Must reset this every time through the loop.
*reg_item.name = ' \0 ' ;
if (RegEnumValue(hRegKey, i, reg_item.name, &name_size, NULL, &reg_item.type, NULL, NULL) ==
,→ ERROR_SUCCESS)
MAKE_SCRIPT_LOOP_PROCESS_THIS_ITEM
// else continue the loop in case some of the lower indexes can still be retrieved successfully.
if (i == 0) // Check this here due to it being an unsigned value that we don ' t want to go
,→ negative.
break;
}
}

// If the loop is neither processing subfolders nor recursing into them, don ' t waste the performance
// doing the next loop:
if (!count_subkeys || (aFileLoopMode == FILE_LOOP_FILES_ONLY && !aRecurseSubfolders))
{
RegCloseKey(hRegKey);
return OK;
}

// Enumerate the subkeys, which are analogous to subfolders in the files system:
// Going in reverse order allows keys to be deleted without disrupting the enumeration,
// at least in some cases:
reg_item.InitForSubkeys();
TCHAR subkey_full_path[MAX_REG_ITEM_SIZE]; // But doesn ' t include the root key name, which is not only by
,→ design but testing shows that if it did, the length could go over MAX_REG_ITEM_SIZE.
for (i = count_subkeys - 1;; --i) // Will have zero iterations if there are no subkeys.
{
// Don ' t use CONTINUE in loops such as this due to the loop-ending condition being explicitly
// checked at the bottom.

155
name_size = _countof(reg_item.name); // Must be reset for every iteration.
if (RegEnumKeyEx(hRegKey, i, reg_item.name, &name_size, NULL, NULL, NULL, &reg_item.ftLastWriteTime)
,→ == ERROR_SUCCESS)
{
if (aFileLoopMode != FILE_LOOP_FILES_ONLY) // have the script ' s loop process this subkey.
MAKE_SCRIPT_LOOP_PROCESS_THIS_ITEM
if (aRecurseSubfolders) // Now recurse into the subkey, regardless of whether it was processed
,→ above.
{
// Build the new subkey name using the an area of memory on the stack that we won ' t need
// after the recursive call returns to us. Omit the leading backslash if subkey is blank,
// which supports recursively searching the contents of keys contained within a root key
// (fixed for v1.0.17):
sntprintf(subkey_full_path, _countof(subkey_full_path), _T(”%s%s%s”), reg_item.subkey
, *reg_item.subkey ? _T(”\\”) : _T(””), reg_item.name);
// This section is very similar to the one in PerformLoopFilePattern(), so see it for comments
,→ :
result = PerformLoopReg(aResultToken, aContinueMainLoop, aJumpToLine, aUntil
, aFileLoopMode , aRecurseSubfolders, aRootKeyType, aRootKey, subkey_full_path);
if (result != OK)
{
RegCloseKey(hRegKey);
return result;
}
if (aContinueMainLoop || aJumpToLine)
break;
}
}
// else continue the loop in case some of the lower indexes can still be retrieved successfully.
if (i == 0) // Check this here due to it being an unsigned value that we don ' t want to go negative.
break;
}
RegCloseKey(hRegKey);
return OK;
}

VarSizeType BIV_LoopRegType(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR buf[MAX_PATH] = _T(””); // Set default.
if (g->mLoopRegItem)
Line::RegConvertValueType(buf, MAX_PATH, g->mLoopRegItem->type);
if (aBuf)
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that due to the zero-the-unused-part
,→ behavior of strlcpy/strncpy.
return (VarSizeType)_tcslen(buf);
}

VarSizeType BIV_LoopRegKey(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR buf[MAX_PATH] = _T(””); // Set default.
if (g->mLoopRegItem)
// Use root_key_type, not root_key (which might be a remote vs. local HKEY):
Line::RegConvertRootKey(buf, MAX_PATH, g->mLoopRegItem->root_key_type);
if (aBuf)
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that due to the zero-the-unused-part
,→ behavior of strlcpy/strncpy.
return (VarSizeType)_tcslen(buf);
}

156
VarSizeType BIV_LoopRegSubKey(LPTSTR aBuf, LPTSTR aVarName)
{
LPTSTR str = g->mLoopRegItem ? g->mLoopRegItem->subkey : _T(””);
if (aBuf)
_tcscpy(aBuf, str);
return (VarSizeType)_tcslen(str);
}

VarSizeType BIV_LoopRegName(LPTSTR aBuf, LPTSTR aVarName)


{
// This can be either the name of a subkey or the name of a value.
LPTSTR str = g->mLoopRegItem ? g->mLoopRegItem->name : _T(””);
if (aBuf)
_tcscpy(aBuf, str);
return (VarSizeType)_tcslen(str);
}

VarSizeType BIV_LoopRegTimeModified(LPTSTR aBuf, LPTSTR aVarName)


{
TCHAR buf[64];
LPTSTR target_buf = aBuf ? aBuf : buf;
*target_buf = ' \0 ' ; // Set default.
// Only subkeys (not values) have a time. In addition, Win9x doesn ' t support retrieval
// of the time (nor does it store it), so make the var blank in that case:
if (g->mLoopRegItem && g->mLoopRegItem->type == REG_SUBKEY && !g_os.IsWin9x())
FileTimeToYYYYMMDD(target_buf, g->mLoopRegItem->ftLastWriteTime, true);
return (VarSizeType)_tcslen(target_buf);
}
 

157
Process

158
Process

Performs one of the following operations on a process: checks if it exists; changes its priority; closes it; waits for it to close.
 
Process, Cmd [, PID-or-Name, Param3]
 
Parameters

• Cmd One of the following words:

Exist: Sets ErrorLevel to the Process ID (PID) if a matching process exists, or 0 otherwise. If the PID-or-Name parameter is blank, the script’s
own PID is retrieved. An alternate, single-line method to retrieve the script’s PID is PID := DllCall(“GetCurrentProcessId”).

Close: If a matching process is successfully terminated, ErrorLevel is set to its former Process ID (PID). Otherwise (there was no matching
process or there was a problem terminating it), it is set to 0. Since the process will be abruptly terminated – possibly interrupting its work at a
critical point or resulting in the loss of unsaved data in its windows (if it has any) – this method should be used only if a process cannot be closed
by using WinClose on one of its windows.

List: Although List is not yet supported, the examples section demonstrates how to retrieve a list of processes via DllCall.

Priority: Changes the priority (as seen in Windows Task Manager) of the first matching process to Param3 and sets ErrorLevel to its Process ID
(PID). If the PID-or-Name parameter is blank, the script’s own priority will be changed. If there is no matching process or there was a problem
changing its priority, ErrorLevel is set to 0.

Param3 should be one of the following letters or words: L (or Low), B (or BelowNormal), N (or Normal), A (or AboveNormal), H (or High), R (or
Realtime). Note: Any process not designed to run at Realtime priority might reduce system stability if set to that level.

Wait: Waits up to Param3 seconds (can contain a decimal point) for a matching process to exist. If Param3 is omitted, the command will wait
indefinitely. If a matching process is discovered, ErrorLevel is set to its Process ID (PID). If the command times out, ErrorLevel is set to 0.

WaitClose: Waits up to Param3 seconds (can contain a decimal point) for ALL matching processes to close. If Param3 is omitted, the command
will wait indefinitely. If all matching processes are closed, ErrorLevel is set to 0. If the command times out, ErrorLevel is set to the Process ID
(PID) of the first matching process that still exists.

• PID-or-Name This parameter can be either a number (the PID) or a process name as described below. It can also be left blank to change the
priority of the script itself.

PID: The Process ID, which is a number that uniquely identifies one specific process (this number is valid only during the lifetime of that process).
The PID of a newly launched process can be determined via the Run command. Similarly, the PID of a window can be determined with WinGet.
The Process command itself can also be used to discover a PID.

Name: The name of a process is usually the same as its executable (without path), e.g. notepad.exe or winword.exe. Since a name might match
multiple running processes, only the first process will be operated upon. The name is not case sensitive.

• Param3 See Cmd above for details.

Remarks

For Wait and WaitClose: Processes are checked every 100 milliseconds; the moment the condition is satisfied, the command stops waiting. In other
words, rather than waiting for the timeout to expire, it immediately sets ErrorLevel as described above, then continues execution of the script. Also, while
the command is in a waiting state, new threads can be launched via hotkey, custom menu item, or timer.
 
DWORD ProcessExist9x2000(LPTSTR aProcess)
{
// We must dynamically load the function or program will probably not launch at all on NT4.
typedef BOOL (WINAPI *PROCESSWALK)(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
typedef HANDLE (WINAPI *CREATESNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);

static CREATESNAPSHOT lpfnCreateToolhelp32Snapshot = (CREATESNAPSHOT)GetProcAddress(GetModuleHandle(_T(”


,→ kernel32”)), ”CreateToolhelp32Snapshot”);
static PROCESSWALK lpfnProcess32First = (PROCESSWALK)GetProcAddress(GetModuleHandle(_T(”kernel32”)), ”
,→ Process32First” PROCESS_API_SUFFIX);
static PROCESSWALK lpfnProcess32Next = (PROCESSWALK)GetProcAddress(GetModuleHandle(_T(”kernel32”)), ”
,→ Process32Next” PROCESS_API_SUFFIX);

if (!lpfnCreateToolhelp32Snapshot || !lpfnProcess32First || !lpfnProcess32Next)


return 0;

159
PROCESSENTRY32 proc;
proc.dwSize = sizeof(proc);
HANDLE snapshot = lpfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
lpfnProcess32First(snapshot, &proc);

// Determine the PID if aProcess is a pure, non-negative integer (any negative number
// is more likely to be the name of a process [with a leading dash], rather than the PID).
DWORD specified_pid = IsPureNumeric(aProcess) ? ATOU(aProcess) : 0;
TCHAR szDrive[_MAX_PATH+1], szDir[_MAX_PATH+1], szFile[_MAX_PATH+1], szExt[_MAX_PATH+1];

while (lpfnProcess32Next(snapshot, &proc))


{
if (specified_pid && specified_pid == proc.th32ProcessID)
{
CloseHandle(snapshot);
return specified_pid;
}
// Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
// also be a valid name?):
// It seems that proc.szExeFile never contains a path, just the executable name.
// But in case it ever does, ensure consistency by removing the path:
_tsplitpath(proc.szExeFile, szDrive, szDir, szFile, szExt);
_tcscat(szFile, szExt);
if (!_tcsicmp(szFile, aProcess)) // lstrcmpi() is not used: 1) avoids breaking existing scripts; 2)
,→ provides consistent behavior across multiple locales; 3) performance.
{
CloseHandle(snapshot);
return proc.th32ProcessID;
}
}
CloseHandle(snapshot);
return 0; // Not found.
}

#ifdef CONFIG_WINNT4
DWORD ProcessExistNT4(LPTSTR aProcess, LPTSTR aProcessName)
{
if (aProcessName) // Init this output variable in case of early return.
*aProcessName = ' \0 ' ;
//BOOL EnumProcesses(
// DWORD *lpidProcess, // array of process identifiers
// DWORD cb, // size of array
// DWORD *cbNeeded // number of bytes returned
//);
typedef BOOL (WINAPI *MyEnumProcesses)(DWORD*, DWORD, DWORD*);

//BOOL EnumProcessModules(
// HANDLE hProcess, // handle to process
// HMODULE *lphModule, // array of module handles
// DWORD cb, // size of array
// LPDWORD lpcbNeeded // number of bytes required
//);
typedef BOOL (WINAPI *MyEnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);

//DWORD GetModuleBaseName(
// HANDLE hProcess, // handle to process
// HMODULE hModule, // handle to module

160
// LPTSTR lpBaseName, // base name buffer
// DWORD nSize // maximum characters to retrieve
//);
typedef DWORD (WINAPI *MyGetModuleBaseName)(HANDLE, HMODULE, LPTSTR, DWORD);

// We must dynamically load the function or program will probably not launch at all on Win95.
// Get a handle to the DLL module that contains EnumProcesses
HINSTANCE hinstLib = LoadLibrary(_T(”psapi”));
if (!hinstLib)
return 0;

// Not static in this case, since address can change with each new load of the library:
MyEnumProcesses lpfnEnumProcesses = (MyEnumProcesses)GetProcAddress(hinstLib, ”EnumProcesses”);
MyEnumProcessModules lpfnEnumProcessModules = (MyEnumProcessModules)GetProcAddress(hinstLib, ”
,→ EnumProcessModules”);
MyGetModuleBaseName lpfnGetModuleBaseName = (MyGetModuleBaseName)GetProcAddress(hinstLib, ”
,→ GetModuleBaseName” WINAPI_SUFFIX);

DWORD idProcessArray[512]; // 512 processes max


DWORD cbNeeded; // Bytes returned
if (!lpfnEnumProcesses || !lpfnEnumProcessModules || !lpfnGetModuleBaseName
|| !lpfnEnumProcesses(idProcessArray, sizeof(idProcessArray), &cbNeeded))
{
FreeLibrary(hinstLib);
return 0;
}

// Get the count of PIDs in the array


DWORD cProcesses = cbNeeded / sizeof(DWORD);
// Determine the PID if aProcess is a pure, non-negative integer (any negative number
// is more likely to be the name of a process [with a leading dash], rather than the PID).
DWORD specified_pid = IsPureNumeric(aProcess) ? ATOU(aProcess) : 0;
TCHAR szDrive[_MAX_PATH+1], szDir[_MAX_PATH+1], szFile[_MAX_PATH+1], szExt[_MAX_PATH+1];
TCHAR szProcessName[_MAX_PATH+1];
HMODULE hMod;
HANDLE hProcess;

for (UINT i = 0; i < cProcesses; ++i)


{
if (specified_pid && specified_pid == idProcessArray[i])
{
if (aProcessName) // Caller wanted process name also.
{
if (hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, idProcessArray[
,→ i])) // Assign
{
lpfnEnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded);
if (lpfnGetModuleBaseName(hProcess, hMod, szProcessName, _MAX_PATH))
{
// For consistency in results, use _splitpath() both here and below rather than
// something that just checks for a rightmost backslash.
_tsplitpath(szProcessName, szDrive, szDir, aProcessName, szExt);
_tcscat(aProcessName, szExt);
}
CloseHandle(hProcess);
}
}
FreeLibrary(hinstLib);
return specified_pid;
}

161
// Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
// also be a valid name?):
if (hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, idProcessArray[i])) //
,→ Assign
{
lpfnEnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded);
if (lpfnGetModuleBaseName(hProcess, hMod, szProcessName, _MAX_PATH))
{
_tsplitpath(szProcessName, szDrive, szDir, szFile, szExt);
_tcscat(szFile, szExt);
if (!_tcsicmp(szFile, aProcess)) // lstrcmpi() is not used: 1) avoids breaking existing
,→ scripts; 2) provides consistent behavior across multiple locales; 3) performance.
{
if (aProcessName) // Caller wanted process name also.
_tcscpy(aProcessName, szProcessName);
CloseHandle(hProcess);
FreeLibrary(hinstLib);
return idProcessArray[i]; // The PID.
}
}
CloseHandle(hProcess);
}
}
FreeLibrary(hinstLib);
return 0; // Not found.
}
#endif

ResultType Line::ScriptProcess(LPTSTR aCmd, LPTSTR aProcess, LPTSTR aParam3)


{
ProcessCmds process_cmd = ConvertProcessCmd(aCmd);
// Runtime error is rare since it is caught at load-time unless it ' s in a var. ref.
if (process_cmd == PROCESS_CMD_INVALID)
return LineError(ERR_PARAM1_INVALID, FAIL, aCmd);

HANDLE hProcess;
DWORD pid, priority;
BOOL result;

switch (process_cmd)
{
case PROCESS_CMD_EXIST:
return g_ErrorLevel->Assign(*aProcess ? ProcessExist(aProcess) : GetCurrentProcessId()); // The
,→ discovered PID or zero if none.

case PROCESS_CMD_CLOSE:
if (pid = ProcessExist(aProcess)) // Assign
{
if (hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid))
{
result = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
return g_ErrorLevel->Assign(result ? pid : 0); // Indicate success or failure.
}
}
// Since above didn ' t return, yield a PID of 0 to indicate failure.
return g_ErrorLevel->Assign(_T(”0”));

case PROCESS_CMD_PRIORITY:
switch (_totupper(*aParam3))

162
{
case ' L ' : priority = IDLE_PRIORITY_CLASS; break;
case ' B ' : priority = BELOW_NORMAL_PRIORITY_CLASS; break;
case ' N ' : priority = NORMAL_PRIORITY_CLASS; break;
case ' A ' : priority = ABOVE_NORMAL_PRIORITY_CLASS; break;
case ' H ' : priority = HIGH_PRIORITY_CLASS; break;
case ' R ' : priority = REALTIME_PRIORITY_CLASS; break;
default:
return g_ErrorLevel->Assign(_T(”0”)); // 0 indicates failure in this case (i.e. a PID of zero).
}
if (pid = *aProcess ? ProcessExist(aProcess) : GetCurrentProcessId()) // Assign
{
if (hProcess = OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid)) // Assign
{
// If OS doesn ' t support ”above/below normal”, seems best to default to normal rather than
,→ high/low,
// since ”above/below normal” aren ' t that dramatically different from normal:
if (!g_os.IsWin2000orLater() && (priority == BELOW_NORMAL_PRIORITY_CLASS || priority ==
,→ ABOVE_NORMAL_PRIORITY_CLASS))
priority = NORMAL_PRIORITY_CLASS;
result = SetPriorityClass(hProcess, priority);
CloseHandle(hProcess);
return g_ErrorLevel->Assign(result ? pid : 0); // Indicate success or failure.
}
}
// Otherwise, return a PID of 0 to indicate failure.
return g_ErrorLevel->Assign(_T(”0”));

case PROCESS_CMD_WAIT:
case PROCESS_CMD_WAITCLOSE:
{
// This section is similar to that used for WINWAIT and RUNWAIT:
bool wait_indefinitely;
int sleep_duration;
DWORD start_time;
if (*aParam3) // the param containing the timeout value isn ' t blank.
{
wait_indefinitely = false;
sleep_duration = (int)(ATOF(aParam3) * 1000); // Can be zero.
start_time = GetTickCount();
}
else
{
wait_indefinitely = true;
sleep_duration = 0; // Just to catch any bugs.
}
for (;;)
{ // Always do the first iteration so that at least one check is done.
pid = ProcessExist(aProcess);
if (process_cmd == PROCESS_CMD_WAIT)
{
if (pid)
return g_ErrorLevel->Assign(pid);
}
else // PROCESS_CMD_WAITCLOSE
{
// Since PID cannot always be determined (i.e. if process never existed, there was
// no need to wait for it to close), for consistency, return 0 on success.
if (!pid)
return g_ErrorLevel->Assign(_T(”0”));

163
}
// Must cast to int or any negative result will be lost due to DWORD type:
if (wait_indefinitely || (int)(sleep_duration - (GetTickCount() - start_time)) >
,→ SLEEP_INTERVAL_HALF)
MsgSleep(100); // For performance reasons, don ' t check as often as the WinWait family does.
else // Done waiting.
return g_ErrorLevel->Assign(pid);
// Above assigns 0 if ”Process Wait” times out; or the PID of the process that still exists
// if ”Process WaitClose” times out.
} // for()
} // case
} // switch()

return FAIL; // Should never be executed; just here to catch bugs.


}
 

164
Run / RunWait

Runs an external program. Unlike Run, RunWait will wait until the program finishes before continuing.
 
Run, Target [, WorkingDir, Max|Min|Hide|UseErrorLevel, OutputVarPID]
RunWait, Target [, WorkingDir, Max|Min|Hide|UseErrorLevel, OutputVarPID]
 
Parameters

• Target A document, URL, executable file (.exe, .com, .bat, etc.), shortcut (.lnk), or system verb to launch (see remarks). If Target is a local file
and no path was specified with it, A_WorkingDir will be searched first. If no matching file is found there, the system will search for and launch
the file if it is integrated (“known”), e.g. by being contained in one of the PATH folders.

To pass parameters, add them immediately after the program or document name. If a parameter contains spaces, it is safest to enclose it in
double quotes (even though it may work without them in some cases).

• WorkingDir The working directory for the launched item. Do not enclose the name in double quotes even if it contains spaces. If omitted, the
script’s own working directory (A_WorkingDir) will be used.

• Max|Min|Hide / UseErrorLevel If omitted, Target will be launched normally. Alternatively, it can contain one or more of these words:

Max: launch maximized

Min: launch minimized

Hide: launch hidden (cannot be used in combination with either of the above)

Note: Some applications (e.g. Calc.exe) do not obey the requested startup state and thus Max/Min/Hide will have no effect.

UseErrorLevel: UseErrorLevel can be specified alone or in addition to one of the above words (by separating it from the other word with a space).
If the launch fails, this option skips the warning dialog, sets ErrorLevel to the word ERROR, and allows the current thread to continue. If the launch
succeeds, RunWait sets ErrorLevel to the program’s exit code, and Run sets it to 0.

When UseErrorLevel is specified, the variable A_LastError is set to the result of the operating system’s GetLastError() function. A_LastError
is a number between 0 and 4294967295 (always formatted as decimal, not hexadecimal). Zero (0) means success, but any other number means
the launch failed. Each number corresponds to a specific error condition (to get a list, search www.microsoft.com for “system error codes”). Like
ErrorLevel, A_LastError is a per-thread setting; that is, interruptions by other threads cannot change it. However, A_LastError is also set by
DllCall.

• OutputVarPID The name of the variable in which to store the newly launched program’s unique Process ID (PID). The variable will be made blank
if the PID could not be determined, which usually happens if a system verb, document, or shortcut is launched rather than a direct executable file.
RunWait also supports this parameter, though its OutputVarPID must be checked in another thread (otherwise, the PID will be invalid because
the process will have terminated by the time the line following RunWait executes).

After the Run command retrieves a PID, any windows to be created by the process might not exist yet. To wait for at least one window to be
created, use WinWait ahk_pid %OutputVarPID%.

Remarks

Unlike Run, RunWait will wait until Target is closed or exits, at which time ErrorLevel will be set to the program’s exit code (as a signed 32-bit integer).
Some programs will appear to return immediately even though they are still running; these programs spawn another process.

If Target contains any commas, they must be escaped as shown three times in the following example:
 
Run rundll32.exe shell32.dll`,Control_RunDLL desk.cpl`,`, 3 ; Opens Control Panel > Display Properties >
,→ Settings
 
When running a program via Comspec (cmd.exe) – perhaps because you need to redirect the program’s input or output – if the path or name of the
executable contains spaces, the entire string should be enclosed in an outer pair of quotes. In the following example, the outer quotes are shown in red
and all the inner quotes are shown in black:
 
Run %comspec% /c ””C:\My Utility.exe” ”param 1” ”second param” >”C:\My File.txt””
 
If Target cannot be launched, an error window is displayed and the current thread is exited, unless the string UseErrorLevel is included in the third
parameter or the error is caught by a Try/Catch statement.

Performance may be slightly improved if Target is an exact path, e.g. Run, C:\Windows\Notepad.exe ”C:\My Documents\Test.txt” rather
than Run, C:\My Documents\Test.txt.

165
Special CLSID folders may be opened via Run. For example:
 
Run ::{20d04fe0-3aea-1069-a2d8-08002b30309d} ; Opens the ”My Computer” folder.
Run ::{645ff040-5081-101b-9f08-00aa002f954e} ; Opens the Recycle Bin.
 
System verbs correspond to actions available in a file’s right-click menu in the Explorer. If a file is launched without a verb, the default verb (usually
“open”) for that particular file type will be used. If specified, the verb should be followed by the name of the target file. The following verbs are currently
supported:

• *verb: Any system-defined or custom verb. For example: Run *Compile %A_ScriptFullPath% On Windows Vista and later, the *RunAs
verb may be used in place of the Run as administrator right-click menu item.
• properties: Displays the Explorer’s properties window for the indicated file. For example: Run, properties ”C:\My File.txt” Note:
The properties window will automatically close when the script terminates. To prevent this, use WinWait to wait for the window to appear, then
use WinWaitClose to wait for the user to close it.
• find: Opens an instance of the Explorer’s Search Companion or Find File window at the indicated folder. For example: Run, find D:\
• explore: Opens an instance of Explorer at the indicated folder. For example: Run, explore %A_ProgramFiles%.
• edit: Opens the indicated file for editing. It might not work if the indicated file’s type does not have an “edit” action associated with it. For
example: Run, edit ”C:\My File.txt”
• open: Opens the indicated file (normally not needed because it is the default action for most file types). For example: Run, open ”My File.
,→ txt”.
• print: Prints the indicated file with the associated application, if any. For example: Run, print ”My File.txt”

While RunWait is in a waiting state, new threads can be launched via hotkey, custom menu item, or timer.
 
static int ConvertRunMode(LPTSTR aBuf)
// Returns the matching WinShow mode, or SW_SHOWNORMAL if none.
// These are also the modes that AutoIt3 uses.
{
// For v1.0.19, this was made more permissive (the use of strcasestr vs. stricmp) to support
// the optional word UseErrorLevel inside this parameter:
if (!aBuf || !*aBuf) return SW_SHOWNORMAL;
if (tcscasestr(aBuf, _T(”MIN”))) return SW_MINIMIZE;
if (tcscasestr(aBuf, _T(”MAX”))) return SW_MAXIMIZE;
if (tcscasestr(aBuf, _T(”HIDE”))) return SW_HIDE;
return SW_SHOWNORMAL;
}

ResultType Script::ActionExec(LPTSTR aAction, LPTSTR aParams, LPTSTR aWorkingDir, bool aDisplayErrors


, LPTSTR aRunShowMode, HANDLE *aProcess, bool aUpdateLastError, bool aUseRunAs, Var *aOutputVar)
// Caller should specify NULL for aParams if it wants us to attempt to parse out params from
// within aAction. Caller may specify empty string (””) instead to specify no params at all.
// Remember that aAction and aParams can both be NULL, so don ' t dereference without checking first.
// Note: For the Run & RunWait commands, aParams should always be NULL. Params are parsed out of
// the aActionString at runtime, here, rather than at load-time because Run & RunWait might contain
// dereferenced variable(s), which can only be resolved at runtime.
{
HANDLE hprocess_local;
HANDLE &hprocess = aProcess ? *aProcess : hprocess_local; // To simplify other things.
hprocess = NULL; // Init output param if the caller gave us memory to store it. Even if caller didn ' t,
,→ other things below may rely on this being initialized.
if (aOutputVar) // Same
aOutputVar->Assign();

// Launching nothing is always a success:


if (!aAction || !*aAction) return OK;

// Make sure this is set to NULL because CreateProcess() won ' t work if it ' s the empty string:
if (aWorkingDir && !*aWorkingDir)
aWorkingDir = NULL;

#define IS_VERB(str) ( !_tcsicmp(str, _T(”find”)) || !_tcsicmp(str, _T(”explore”)) || !_tcsicmp(str, _T(


,→ ”open”))\

166
|| !_tcsicmp(str, _T(”edit”)) || !_tcsicmp(str, _T(”print”)) || !_tcsicmp(str, _T(”properties”)) )

// Set default items to be run by ShellExecute(). These are also used by the error
// reporting at the end, which is why they ' re initialized even if CreateProcess() works
// and there ' s no need to use ShellExecute():
LPTSTR shell_verb = NULL;
LPTSTR shell_action = aAction;
LPTSTR shell_params = NULL;

///////////////////////////////////////////////////////////////////////////////////
// This next section is done prior to CreateProcess() because when aParams is NULL,
// we need to find out whether aAction contains a system verb.
///////////////////////////////////////////////////////////////////////////////////
if (aParams) // Caller specified the params (even an empty string counts, for this purpose).
{
if (IS_VERB(shell_action))
{
shell_verb = shell_action;
shell_action = aParams;
}
else
shell_params = aParams;
}
else // Caller wants us to try to parse params out of aAction.
{
// Find out the ”first phrase” in the string to support the special ”find” and ”explore” operations.
LPTSTR phrase;
size_t phrase_len;
// Set phrase_end to be the location of the first whitespace char, if one exists:
LPTSTR phrase_end = StrChrAny(shell_action, _T(” \t”)); // Find space or tab.
if (phrase_end) // i.e. there is a second phrase.
{
phrase_len = phrase_end - shell_action;
// Create a null-terminated copy of the phrase for comparison.
phrase = tmemcpy(talloca(phrase_len + 1), shell_action, phrase_len);
phrase[phrase_len] = ' \0 ' ;
// Firstly, treat anything following ' * ' as a verb, to support custom verbs like *Compile.
if (*phrase == ' * ' )
shell_verb = phrase + 1;
// Secondly, check for common system verbs like ”find” and ”edit”.
else if (IS_VERB(phrase))
shell_verb = phrase;
if (shell_verb)
// Exclude the verb and its trailing space or tab from further consideration.
shell_action += phrase_len + 1;
// Otherwise it ' s not a verb, and may be re-parsed later.
}
// shell_action will be split into action and params at a later stage if ShellExecuteEx is to be used.
}

// This is distinct from hprocess being non-NULL because the two aren ' t always the
// same. For example, if the user does ”Run, find D:\” or ”RunWait, www.yahoo.com”,
// no new process handle will be available even though the launch was successful:
bool success = false; // Separate from last_error for maintainability.
DWORD last_error = 0;

bool use_runas = aUseRunAs && (!mRunAsUser.IsEmpty() || !mRunAsPass.IsEmpty() || !mRunAsDomain.IsEmpty());


if (use_runas && shell_verb)
{
if (aDisplayErrors)

167
ScriptError(_T(”System verbs unsupported with RunAs.”));
return FAIL;
}

size_t action_length = _tcslen(shell_action); // shell_action == aAction if shell_verb == NULL.


if (action_length >= LINE_SIZE) // Max length supported by CreateProcess() is 32 KB. But there hasn ' t been
,→ any demand to go above 16 KB, so seems little need to support it (plus it reduces risk of stack
,→ overflow).
{
if (aDisplayErrors)
ScriptError(_T(”String too long.”)); // Short msg since so rare.
return FAIL;
}

// If the caller originally gave us NULL for aParams, always try CreateProcess() before
// trying ShellExecute(). This is because ShellExecute() is usually a lot slower.
// The only exception is if the action appears to be a verb such as open, edit, or find.
// In that case, we ' ll also skip the CreateProcess() attempt and do only the ShellExecute().
// If the user really meant to launch find.bat or find.exe, for example, he should add
// the extension (e.g. .exe) to differentiate ”find” from ”find.exe”:
if (!shell_verb)
{
STARTUPINFO si = {0}; // Zero fill to be safer.
si.cb = sizeof(si);
// The following are left at the default of NULL/0 set higher above:
//si.lpReserved = si.lpDesktop = si.lpTitle = NULL;
//si.lpReserved2 = NULL;
si.dwFlags = STARTF_USESHOWWINDOW; // This tells it to use the value of wShowWindow below.
si.wShowWindow = (aRunShowMode && *aRunShowMode) ? Line::ConvertRunMode(aRunShowMode) : SW_SHOWNORMAL;
PROCESS_INFORMATION pi = {0};

// Since CreateProcess() requires that the 2nd param be modifiable, ensure that it is
// (even if this is ANSI and not Unicode; it ' s just safer):
LPTSTR command_line;
if (aParams && *aParams)
{
command_line = talloca(action_length + _tcslen(aParams) + 10); // +10 to allow room for space,
,→ terminator, and any extra chars that might get added in the future.
_stprintf(command_line, _T(”%s %s”), aAction, aParams);
}
else // We ' re running the original action from caller.
{
command_line = talloca(action_length + 1);
_tcscpy(command_line, aAction); // CreateProcessW() requires modifiable string. Although non-W
,→ version is used now, it feels safer to make it modifiable anyway.
}

if (use_runas)
{
if (!DoRunAs(command_line, aWorkingDir, aDisplayErrors, si.wShowWindow // wShowWindow (min/max/
,→ hide).
, aOutputVar, pi, success, hprocess, last_error)) // These are output parameters it will set
,→ for us.
return FAIL; // It already displayed the error, if appropriate.
}
else
{
// MSDN: ”If [lpCurrentDirectory] is NULL, the new process is created with the same
// current drive and directory as the calling process.” (i.e. since caller may have
// specified a NULL aWorkingDir). Also, we pass NULL in for the first param so that

168
// it will behave the following way (hopefully under all OSes): ”the first white-space delimited
// token of the command line specifies the module name. If you are using a long file name that
// contains a space, use quoted strings to indicate where the file name ends and the arguments
// begin (see the explanation for the lpApplicationName parameter). If the file name does not
// contain an extension, .exe is appended. Therefore, if the file name extension is .com,
// this parameter must include the .com extension. If the file name ends in a period (.) with
// no extension, or if the file name contains a path, .exe is not appended. If the file name does
// not contain a directory path, the system searches for the executable file in the following
// sequence...”.
// Provide the app name (first param) if possible, for greater expected reliability.
// UPDATE: Don ' t provide the module name because if it ' s enclosed in double quotes,
// CreateProcess() will fail, at least under XP:
//if (CreateProcess(aParams && *aParams ? aAction : NULL
if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, aWorkingDir, &si, &pi))
{
success = true;
if (pi.hThread)
CloseHandle(pi.hThread); // Required to avoid memory leak.
hprocess = pi.hProcess;
if (aOutputVar)
aOutputVar->Assign(pi.dwProcessId);
}
else
last_error = GetLastError();
}
}

// Since CreateProcessWithLogonW() was either not attempted or did not work, it ' s probably
// best to display an error rather than trying to run it without the RunAs settings.
// This policy encourages users to have RunAs in effect only when necessary:
if (!success && !use_runas) // Either the above wasn ' t attempted, or the attempt failed. So try
,→ ShellExecute().
{
SHELLEXECUTEINFO sei = {0};
// sei.hwnd is left NULL to avoid potential side-effects with having a hidden window be the parent.
// However, doing so may result in the launched app appearing on a different monitor than the
// script ' s main window appears on (for multimonitor systems). This seems fairly inconsequential
// since scripted workarounds are possible.
sei.cbSize = sizeof(sei);
// Below: ”indicate that the hProcess member receives the process handle” and not to display error
,→ dialog:
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
sei.lpDirectory = aWorkingDir; // OK if NULL or blank; that will cause current dir to be used.
sei.nShow = (aRunShowMode && *aRunShowMode) ? Line::ConvertRunMode(aRunShowMode) : SW_SHOWNORMAL;
if (shell_verb)
{
sei.lpVerb = shell_verb;
if (!_tcsicmp(shell_verb, _T(”properties”)))
sei.fMask |= SEE_MASK_INVOKEIDLIST; // Need to use this for the ”properties” verb to work
,→ reliably.
}
if (!shell_params) // i.e. above hasn ' t determined the params yet.
{
// Rather than just consider the first phrase to be the executable and the rest to be the param, we check it
// for a proper extension so that the user can launch a document name containing spaces, without having to
// enclose it in double quotes. UPDATE: Want to be able to support executable filespecs without requiring
,→ them
// to be enclosed in double quotes. Therefore, search the entire string, rather than just first_phrase, for
// the left-most occurrence of a valid executable extension. This should be fine since the user can still
// pass in EXEs and such as params as long as the first executable is fully qualified with its real extension

169
// so that we can tell that it ' s the action and not one of the params. UPDATE: Since any file type may
// potentially accept parameters (.lnk or .ahk files for instance), the first space-terminated substring which
// is either an existing file or ends in one of .exe,.bat,.com,.cmd,.hta is considered the executable and the
// rest is considered the param. Remaining shortcomings of this method include:
// - It doesn ' t handle an extensionless executable such as ”notepad test.txt”
// - It doesn ' t handle custom file types (scripts etc.) which don ' t exist in the working directory but can
// still be executed due to %PATH% and %PATHEXT% even when our caller doesn ' t supply an absolute path.
// These limitations seem acceptable since the caller can allow even those cases to work by simply wrapping
// the action in quote marks.
// Make a copy so that we can modify it (i.e. split it into action & params).
// Using talloca ensures it will stick around until the function exits:
LPTSTR parse_buf = talloca(action_length + 1);
_tcscpy(parse_buf, shell_action);
LPTSTR action_extension, action_end;
// Let quotation marks be used to remove all ambiguity:
if (*parse_buf == ' ” ' && (action_end = _tcschr(parse_buf + 1, ' ” ' )))
{
shell_action = parse_buf + 1;
*action_end = ' \0 ' ;
if (action_end[1])
{
shell_params = action_end + 1;
// Omit the space which should follow, but only one, in case spaces
// are meaningful to the target program.
if (*shell_params == ' ' )
++shell_params;
}
// Otherwise, there ' s only the action in quotation marks and no params.
}
else
{
if (aWorkingDir) // Set current directory temporarily in case the action is a relative path:
SetCurrentDirectory(aWorkingDir);
// For each space which possibly delimits the action and params:
for (action_end = parse_buf + 1; action_end = _tcschr(action_end, ' ' ); ++action_end)
{
// Find the beginning of the substring or file extension; if \ is encountered, this might
,→ be
// an extensionless filename, but it probably wouldn ' t be meaningful to pass params to
,→ such a
// file since it can ' t be associated with anything, so skip to the next space in that case
,→ .
for ( action_extension = action_end - 1;
action_extension > parse_buf && !_tcschr(_T(”\\/.”), *action_extension);
--action_extension );
if (*action_extension == ' . ' ) // Potential file extension; even if action_extension ==
,→ parse_buf since ”.ext” on its own is a valid filename.
{
*action_end = ' \0 ' ; // Temporarily terminate.
// If action_extension is a common executable extension, don ' t call GetFileAttributes
,→ () since
// the file might actually be in a location listed in %PATH% or the App Paths registry
,→ key:
if ( (action_end-action_extension == 4 && tcscasestr(_T(”.exe.bat.com.cmd.hta”),
,→ action_extension))
// Otherwise the file might still be something capable of accepting params, like a
,→ script,
// so check if what we have is the name of an existing file:
|| !(GetFileAttributes(parse_buf) & FILE_ATTRIBUTE_DIRECTORY) ) // i.e. THE FILE
,→ EXISTS and is not a directory. This works because (INVALID_FILE_ATTRIBUTES

170
,→ & FILE_ATTRIBUTE_DIRECTORY) is non-zero.
{
shell_action = parse_buf;
shell_params = action_end + 1;
break;
}
// What we have so far isn ' t an obvious executable file type or the path of an
,→ existing
// file, so assume it isn ' t a valid action. Unterminate and continue the loop:
*action_end = ' ' ;
}
}
if (aWorkingDir)
SetCurrentDirectory(g_WorkingDir); // Restore to proper value.
}
}
//else aParams!=NULL, so the extra parsing in the block above isn ' t necessary.

// Not done because it may have been set to shell_verb above:


//sei.lpVerb = NULL;
sei.lpFile = shell_action;
sei.lpParameters = shell_params; // NULL if no parameters were present.
// Above was fixed v1.0.42.06 to be NULL rather than the empty string to prevent passing an
// extra space at the end of a parameter list (this might happen only when launching a shortcut
// [.lnk file]). MSDN states: ”If the lpFile member specifies a document file, lpParameters should
// be NULL.” This implies that NULL is a suitable value for lpParameters in cases where you don ' t
// want to pass any parameters at all.

if (ShellExecuteEx(&sei)) // Relies on short-circuit boolean order.


{
typedef DWORD (WINAPI *GetProcessIDType)(HANDLE);
// GetProcessID is only available on WinXP SP1 or later, so load it dynamically.
static GetProcessIDType fnGetProcessID = (GetProcessIDType)GetProcAddress(GetModuleHandle(_T(”
,→ kernel32.dll”)), ”GetProcessId”);

if (hprocess = sei.hProcess)
{
// A new process was created, so get its ID if possible.
if (aOutputVar && fnGetProcessID)
aOutputVar->Assign(fnGetProcessID(hprocess));
}
// Even if there ' s no process handle, it ' s considered a success because some
// system verbs and file associations do not create a new process, by design.
success = true;
}
else
last_error = GetLastError();
}

if (!success) // The above attempt(s) to launch failed.


{
if (aUpdateLastError)
g->LastError = last_error;

if (aDisplayErrors)
{
TCHAR error_text[2048], verb_text[128], system_error_text[512];
GetWin32ErrorText(system_error_text, _countof(system_error_text), last_error);
if (shell_verb)
sntprintf(verb_text, _countof(verb_text), _T(”\nVerb: <%s>”), shell_verb);

171
else // Don ' t bother showing it if it ' s just ”open”.
*verb_text = ' \0 ' ;
if (!shell_params)
shell_params = _T(””); // Expected to be non-NULL below.
// Use format specifier to make sure it doesn ' t get too big for the error
// function to display:
sntprintf(error_text, _countof(error_text)
, _T(”%s\nAction: <%-0.400s%s>”)
_T(”%s”)
_T(”\nParams: <%-0.400s%s>”)
, use_runas ? _T(”Launch Error (possibly related to RunAs):”) : _T(”Failed attempt to launch
,→ program or document:”)
, shell_action, _tcslen(shell_action) > 400 ? _T(”...”) : _T(””)
, verb_text
, shell_params, _tcslen(shell_params) > 400 ? _T(”...”) : _T(””)
);
ScriptError(error_text, system_error_text);
}
return FAIL;
}

// Otherwise, success:
if (aUpdateLastError)
g->LastError = 0; // Force zero to indicate success, which seems more maintainable and reliable than
,→ calling GetLastError() right here.

// If aProcess isn ' t NULL, the caller wanted the process handle left open and so it must eventually call
// CloseHandle(). Otherwise, we should close the process if it ' s non-NULL (it can be NULL in the case of
// launching things like ”find D:\” or ”www.yahoo.com”).
if (!aProcess && hprocess)
CloseHandle(hprocess); // Required to avoid memory leak.
return OK;
}
 

172
RunAs

Specifies a set of user credentials to use for all subsequent uses of Run and RunWait.
 
RunAs [, User, Password, Domain]
 
Parameters

• User If this and the other parameters are all omitted, the RunAs feature will be turned off, which restores Run and RunWait to their default behavior.
Otherwise, this is the username under which new processes will be created.

• Password User’s password.

• Domain User’s domain. To use a local account, leave this blank. If that fails to work, try using @YourComputerName.

Remarks

If the script is running with restricted privileges due to User Account Control (UAC), any programs it launches will typically also be restricted, even if
RunAs is used. To elevate a process, use Run *RunAs instead.

This command does nothing other than notify AutoHotkey to use (or not use) alternate user credentials for all subsequent uses of Run and RunWait.

ErrorLevel is not changed by this command. If an invalid User, Password, or Domain is specified, Run and RunWait will display an error message
explaining the problem (unless their UseErrorLevel option is in effect).

While the RunAs feature is in effect, Run and RunWait will not able to launch documents, URLs, or system verbs. In other words, the file to be launched
must be an executable file.

The “Secondary Logon” service must be set to manual or automatic for this command to work (the OS should automatically start it upon demand if set
to manual).
 
ResultType Script::DoRunAs(LPTSTR aCommandLine, LPTSTR aWorkingDir, bool aDisplayErrors, WORD aShowWindow
, Var *aOutputVar, PROCESS_INFORMATION &aPI, bool &aSuccess // Output parameters we set for caller, but
,→ caller must have initialized aSuccess to false.
, HANDLE &aNewProcess, DWORD &aLastError) // Same, but initialize to NULL.
{
typedef BOOL (WINAPI *MyCreateProcessWithLogonW)(
LPCWSTR lpUsername, // user ' s name
LPCWSTR lpDomain, // user ' s domain
LPCWSTR lpPassword, // user ' s password
DWORD dwLogonFlags, // logon option
LPCWSTR lpApplicationName, // executable module name
LPWSTR lpCommandLine, // command-line string
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // new environment block
LPCWSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFOW lpStartupInfo, // startup information
LPPROCESS_INFORMATION lpProcessInfo // process information
);
// Get a handle to the DLL module that contains CreateProcessWithLogonW
HINSTANCE hinstLib = LoadLibrary(_T(”advapi32”));
if (!hinstLib)
{
if (aDisplayErrors)
ScriptError(_T(”RunAs: Missing advapi32.dll.”));
return FAIL;
}
MyCreateProcessWithLogonW lpfnDLLProc = (MyCreateProcessWithLogonW)GetProcAddress(hinstLib, ”
,→ CreateProcessWithLogonW”);
if (!lpfnDLLProc)
{
FreeLibrary(hinstLib);
if (aDisplayErrors)
ScriptError(_T(”CreateProcessWithLogonW.”)); // Short msg since it probably never happens.
return FAIL;

173
}
// Set up wide char version that we need for CreateProcessWithLogon
// init structure for running programs (wide char version)
STARTUPINFOW wsi = {0};
wsi.cb = sizeof(STARTUPINFOW);
wsi.dwFlags = STARTF_USESHOWWINDOW;
wsi.wShowWindow = aShowWindow;
// The following are left initialized to 0/NULL (initialized earlier above):
//wsi.lpReserved = NULL;
//wsi.lpDesktop = NULL;
//wsi.lpTitle = NULL;
//wsi.cbReserved2 = 0;
//wsi.lpReserved2 = NULL;

#ifndef UNICODE
// Convert to wide character format:
WCHAR command_line_wide[LINE_SIZE], working_dir_wide[MAX_PATH];
ToWideChar(aCommandLine, command_line_wide, LINE_SIZE); // Dest. size is in wchars, not bytes.
if (aWorkingDir && *aWorkingDir)
ToWideChar(aWorkingDir, working_dir_wide, MAX_PATH); // Dest. size is in wchars, not bytes.
else
*working_dir_wide = 0; // wide-char terminator.

if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0


, command_line_wide, 0, 0, *working_dir_wide ? working_dir_wide : NULL, &wsi, &aPI))
#else
if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
, aCommandLine, 0, 0, aWorkingDir && *aWorkingDir ? aWorkingDir : NULL, &wsi, &aPI))
#endif
{
aSuccess = true;
if (aPI.hThread)
CloseHandle(aPI.hThread); // Required to avoid memory leak.
aNewProcess = aPI.hProcess;
if (aOutputVar)
aOutputVar->Assign(aPI.dwProcessId);
}
else
aLastError = GetLastError(); // Caller will use this to get an error message and set g->LastError if
,→ needed.
FreeLibrary(hinstLib);
return OK;
}
 

174
Shutdown

Shuts down, restarts, or logs off the system.


 
Shutdown, Code
 
Parameters

• Code A combination of shutdown codes listed below.

Remarks

The shutdown code is a combination of the following values:

• Logoff: 0
• Shutdown: 1
• Reboot: 2
• Force: 4
• Power down: 8
• Suspend/Hibernate: See DllCall example at the bottom of this page.
• Turn monitor off: See PostMessage examples.

Add the required values together. For example, to shutdown and power down the code would be 9 (shutdown + power down = 1 + 8 = 9). Alternatively,
an expression such as 1+8 can be specified.

The “Force” value (4) forces all open applications to close. It should only be used in an emergency because it may cause any open applications to lose
data.

The “Power down” value shuts down the system and turns off the power.

On a related note, a script can detect when the system is shutting down or the user is logging off via OnExit.
 
bool Util_Shutdown(int nFlag)
// Shutdown or logoff the system.
// Returns false if the function could not get the rights to shutdown.
{
/*
flags can be a combination of:
#define EWX_LOGOFF 0
#define EWX_SHUTDOWN 0x00000001
#define EWX_REBOOT 0x00000002
#define EWX_FORCE 0x00000004
#define EWX_POWEROFF 0x00000008 */

HANDLE hToken;
TOKEN_PRIVILEGES tkp;

// If we are running NT/2k/XP, make sure we have rights to shutdown


if (g_os.IsWinNT()) // NT/2k/XP/2003 and family
{
// Get a token for this process.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false; // Don ' t have the rights

// Get the LUID for the shutdown privilege.


LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);

tkp.PrivilegeCount = 1; /* one privilege to set */


tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// Get the shutdown privilege for this process.


AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);

// Cannot test the return value of AdjustTokenPrivileges.

175
if (GetLastError() != ERROR_SUCCESS)
return false; // Don ' t have the rights
}

// if we are forcing the issue, AND this is 95/98 terminate all windows first
if ( g_os.IsWin9x() && (nFlag & EWX_FORCE) )
{
nFlag ˆ= EWX_FORCE; // remove this flag - not valid in 95
EnumWindows((WNDENUMPROC) Util_ShutdownHandler, 0);
}

// ExitWindows
if (ExitWindowsEx(nFlag, 0))
return true;
else
return false;

BOOL Util_ShutdownHandler(HWND hwnd, DWORD lParam)


{
// if the window is me, don ' t terminate!
if (hwnd != g_hWnd && hwnd != g_hWndSplash)
Util_WinKill(hwnd);

// Continue the enumeration.


return TRUE;

void Util_WinKill(HWND hWnd)


{
DWORD_PTR dwResult;
// Use WM_CLOSE vs. SC_CLOSE in this case, since the target window is slightly more likely to
// respond to that:
if (!SendMessageTimeout(hWnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 500, &dwResult)) // Wait up to 500ms.
{
// Use more force - Mwuahaha
DWORD pid = GetWindowThreadProcessId(hWnd, NULL);
HANDLE hProcess = pid ? OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) : NULL;
if (hProcess)
{
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
}
}
 

176
Built-in Variables

The following variables are built into the program and can be referenced by any script. With the exception of Clipboard, ErrorLevel, and command line
parameters, these variables are read-only; that is, their contents cannot be directly altered by the script.

177
A_WorkingDir

The script’s current working directory, which is where files will be accessed by default. The final backslash is not included unless it is the root directory.
Two examples: C:\ and C:\My Documents. Use SetWorkingDir to change the working directory.
 
VarSizeType BIV_WorkingDir(LPTSTR aBuf, LPTSTR aVarName)
{
// Use GetCurrentDirectory() vs. g_WorkingDir because any in-progress FileSelectFile()
// dialog is able to keep functioning even when it ' s quasi-thread is suspended. The
// dialog can thus change the current directory as seen by the active quasi-thread even
// though g_WorkingDir hasn ' t been updated. It might also be possible for the working
// directory to change in unusual circumstances such as a network drive being lost).
//
// Fix for v1.0.43.11: Changed size below from 9999 to MAX_PATH, otherwise it fails sometimes on Win9x.
// Testing shows that the failure is not caused by GetCurrentDirectory() writing to the unused part of the
// buffer, such as zeroing it (which is good because that would require this part to be redesigned to pass
// the actual buffer size or use a temp buffer). So there ' s something else going on to explain why the
// problem only occurs in longer scripts on Win98se, not in trivial ones such as Var=%A_WorkingDir%.
// Nor did the problem affect expression assignments such as Var:=A_WorkingDir.
TCHAR buf[MAX_PATH];
VarSizeType length = GetCurrentDirectory(MAX_PATH, buf);
if (aBuf)
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that (even though it ' s large enough to hold
,→ the string). This is true for ReadRegString() ' s API call and may be true for other API calls
,→ like the one here.
return length;
// Formerly the following, but I don ' t think it ' s as reliable/future-proof given the 1.0.47 comment above:
//return aBuf
// ? GetCurrentDirectory(MAX_PATH, aBuf)
// : GetCurrentDirectory(0, NULL); // MSDN says that this is a valid way to call it on all OSes, and
,→ testing shows that it works on WinXP and 98se.
// Above avoids subtracting 1 to be conservative and to reduce code size (due to the need to otherwise
,→ check for zero and avoid subtracting 1 in that case).
}
 

178
Date and Time

A_YYYY

Current 4-digit year (e.g. 2004). Synonymous with A_Year. Note: To retrieve a formatted time or date appropriate for your locale and language, use
FormatTime, OutputVar (time and long date) or FormatTime, OutputVar„ LongDate (retrieves long-format date).

A_MM

Current 2-digit month (01-12). Synonymous with A_Mon.

A_DD

Current 2-digit day of the month (01-31). Synonymous with A_MDay.

A_MMMM

Current month’s full name in the current user’s language, e.g. July

A_MMM

Current month’s abbreviation in the current user’s language, e.g. Jul

A_DDDD

Current day of the week’s full name in the current user’s language, e.g. Sunday

A_DDD

Current day of the week’s 3-letter abbreviation in the current user’s language, e.g. Sun

A_WDay

Current 1-digit day of the week (1-7). 1 is Sunday in all locales.

A_YDay

Current day of the year (1-366). The value is not zero-padded, e.g. 9 is retrieved, not 009. To retrieve a zero-padded value, use the following:
FormatTime, OutputVar,, YDay0.

A_YWeek

Current year and week number (e.g. 200453) according to ISO 8601. To separate the year from the week, use StringLeft, Year, A_YWeek, 4
,→ and StringRight, Week, A_YWeek, 2. Precise definition of A_YWeek: If the week containing January 1st has four or more days in the new
year, it is considered week 1. Otherwise, it is the last week of the previous year, and the next week is week 1.

A_Hour

Current 2-digit hour (00-23) in 24-hour time (for example, 17 is 5pm). To retrieve 12-hour time as well as an AM/PM indicator, follow this example:
FormatTime, OutputVar, , h:mm:ss tt

A_Min

Current 2-digit minute (00-59).

A_Sec

Current 2-digit second (00-59).

A_MSec

Current 3-digit millisecond (000-999). To remove the leading zeros, follow this example: Milliseconds := A_MSec + 0.

A_Now

The current local time in YYYYMMDDHH24MISS format. Note: Date and time math can be performed with EnvAdd and EnvSub. Also, FormatTime can
format the date and/or time according to your locale or preferences.

A_NowUTC

The current Coordinated Universal Time (UTC) in YYYYMMDDHH24MISS format. UTC is essentially the same as Greenwich Mean Time (GMT).

A_TickCount

The number of milliseconds since the computer was rebooted. By storing A_TickCount in a variable, elapsed time can later be measured by subtracting
that variable from the latest A_TickCount value. For example:

179
 
StartTime := A_TickCount
Sleep, 1000
ElapsedTime := A_TickCount - StartTime
MsgBox, %ElapsedTime% milliseconds have elapsed.
 
 
VarSizeType BIV_DateTime(LPTSTR aBuf, LPTSTR aVarName)
{
if (!aBuf)
return 6; // Since only an estimate is needed in this mode, return the maximum length of any item.

aVarName += 2; // Skip past the ”A_”.

// The current time is refreshed only if it ' s been a certain number of milliseconds since
// the last fetch of one of these built-in time variables. This keeps the variables in
// sync with one another when they are used consecutively such as this example:
// Var = %A_Hour%:%A_Min%:%A_Sec%
// Using GetTickCount() because it ' s very low overhead compared to the other time functions:
static DWORD sLastUpdate = 0; // Static should be thread + recursion safe in this case.
static SYSTEMTIME sST = {0}; // Init to detect when it ' s empty.
BOOL is_msec = !_tcsicmp(aVarName, _T(”MSec”)); // Always refresh if it ' s milliseconds, for better
,→ accuracy.
DWORD now_tick = GetTickCount();
if (is_msec || now_tick - sLastUpdate > 50 || !sST.wYear) // See comments above.
{
GetLocalTime(&sST);
sLastUpdate = now_tick;
}

if (is_msec)
return _stprintf(aBuf, _T(”%03d”), sST.wMilliseconds);

TCHAR second_letter = ctoupper(aVarName[1]);


switch(ctoupper(aVarName[0]))
{
case ' Y ' :
switch(second_letter)
{
case ' D ' : // A_YDay
return _stprintf(aBuf, _T(”%d”), GetYDay(sST.wMonth, sST.wDay, IS_LEAP_YEAR(sST.wYear)));
case ' W ' : // A_YWeek
return GetISOWeekNumber(aBuf, sST.wYear
, GetYDay(sST.wMonth, sST.wDay, IS_LEAP_YEAR(sST.wYear))
, sST.wDayOfWeek);
default: // A_Year/A_YYYY
return _stprintf(aBuf, _T(”%d”), sST.wYear);
}
// No break because all cases above return:
//break;
case ' M ' :
switch(second_letter)
{
case ' D ' : // A_MDay (synonymous with A_DD)
return _stprintf(aBuf, _T(”%02d”), sST.wDay);
case ' I ' : // A_Min
return _stprintf(aBuf, _T(”%02d”), sST.wMinute);
default: // A_MM and A_Mon (A_MSec was already completely handled higher above).
return _stprintf(aBuf, _T(”%02d”), sST.wMonth);
}
// No break because all cases above return:

180
//break;
case ' D ' : // A_DD (synonymous with A_MDay)

return _stprintf(aBuf, _T(”%02d”), sST.wDay);


case ' W ' : // A_WDay
return _stprintf(aBuf, _T(”%d”), sST.wDayOfWeek + 1);
case ' H ' : // A_Hour
return _stprintf(aBuf, _T(”%02d”), sST.wHour);
case ' S ' : // A_Sec (A_MSec was already completely handled higher above).
return _stprintf(aBuf, _T(”%02d”), sST.wSecond);
}
return 0; // Never reached, but avoids compiler warning.
}

int GetYDay(int aMon, int aDay, bool aIsLeapYear)


// Returns a number between 1 and 366.
// Caller must verify that aMon is a number between 1 and 12, and aDay is a number between 1 and 31.
{
--aMon; // Convert to zero-based.
if (aIsLeapYear)
{
int leap_offset[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
return leap_offset[aMon] + aDay;
}
else
{
int normal_offset[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
return normal_offset[aMon] + aDay;
}
}

int GetISOWeekNumber(LPTSTR aBuf, int aYear, int aYDay, int aWDay)


// Caller must ensure that aBuf is of size 7 or greater, that aYear is a valid year (e.g. 2005),
// that aYDay is between 1 and 366, and that aWDay is between 0 and 6 (day of the week).
// Produces the week number in YYYYNN format, e.g. 200501.
// Note that year is also returned because it isn ' t necessarily the same as aTime ' s calendar year.
// Based on Linux glibc source code (GPL).
{
--aYDay; // Convert to zero based.
#define ISO_WEEK_START_WDAY 1 // Monday
#define ISO_WEEK1_WDAY 4 // Thursday
#define ISO_WEEK_DAYS(yday, wday) (yday - (yday - wday + ISO_WEEK1_WDAY + ((366 / 7 + 2) * 7)) % 7 \
+ ISO_WEEK1_WDAY - ISO_WEEK_START_WDAY);

int year = aYear;


int days = ISO_WEEK_DAYS(aYDay, aWDay);

if (days < 0) // This ISO week belongs to the previous year.


{
--year;
days = ISO_WEEK_DAYS(aYDay + (365 + IS_LEAP_YEAR(year)), aWDay);
}
else
{
int d = ISO_WEEK_DAYS(aYDay - (365 + IS_LEAP_YEAR(year)), aWDay);
if (0 <= d) // This ISO week belongs to the next year.
{
++year;
days = d;

181
}
}

// Use sntprintf() for safety; that is, in case year contains a value longer than 4 digits.
// This also adds the leading zeros in front of year and week number, if needed.
return sntprintf(aBuf, 7, _T(”%04d%02d”), year, (days / 7) + 1); // Return the length of the string
,→ produced.
}
 

182
A_TimeIdle

The number of milliseconds that have elapsed since the system last received keyboard, mouse, or other input. This is useful for determining whether
the user is away. Physical input from the user as well as artificial input generated by any program or script (such as the Send or MouseMove commands)
will reset this value back to zero. Since this value tends to increase by increments of 10, do not check whether it is equal to another value. Instead,
check whether it is greater or less than another value. For example: IfGreater, A_TimeIdle, 600000, MsgBox, The last keyboard or
,→ mouse activity was at least 10 minutes ago.
 
VarSizeType BIV_TimeIdle(LPTSTR aBuf, LPTSTR aVarName) // Called by multiple callers.
{
if (!aBuf) // IMPORTANT: Conservative estimate because tick might change between 1st & 2nd calls.
return MAX_INTEGER_LENGTH;
#ifdef CONFIG_WIN9X
*aBuf = ' \0 ' ; // Set default.
if (g_os.IsWin2000orLater()) // Checked in case the function is present in the OS but ”not implemented”.
{
// Must fetch it at runtime, otherwise the program can ' t even be launched on Win9x/NT:
typedef BOOL (WINAPI *MyGetLastInputInfoType)(PLASTINPUTINFO);
static MyGetLastInputInfoType MyGetLastInputInfo = (MyGetLastInputInfoType)
GetProcAddress(GetModuleHandle(_T(”user32”)), ”GetLastInputInfo”);
if (MyGetLastInputInfo)
{
LASTINPUTINFO lii;
lii.cbSize = sizeof(lii);
if (MyGetLastInputInfo(&lii))
ITOA64(GetTickCount() - lii.dwTime, aBuf);
}
}
#else
// Not Win9x: Calling it directly should (in theory) produce smaller code size.
LASTINPUTINFO lii;
lii.cbSize = sizeof(lii);
if (GetLastInputInfo(&lii))
ITOA64(GetTickCount() - lii.dwTime, aBuf);
else
*aBuf = ' \0 ' ;
#endif
return (VarSizeType)_tcslen(aBuf);
}
 

183
A_Temp

The full path and name of the folder designated to hold temporary files (e.g. C:\DOCUME1\UserName\LOCALS 1\Temp). It is retrieved from one of the following
locations (in order): 1) the environment variables TMP, TEMP, or USERPROFILE; 2) the Windows directory.
 
VarSizeType BIV_Temp(LPTSTR aBuf, LPTSTR aVarName)
{
TCHAR buf[MAX_PATH];
VarSizeType length = GetTempPath(MAX_PATH, buf);
if (aBuf)
{
_tcscpy(aBuf, buf); // v1.0.47: Must be done as a separate copy because passing a size of MAX_PATH for
,→ aBuf can crash when aBuf is actually smaller than that (even though it ' s large enough to hold
,→ the string). This is true for ReadRegString() ' s API call and may be true for other API calls
,→ like the one here.
if (length)
{
aBuf += length - 1;
if (*aBuf == ' \\ ' ) // For some reason, it typically yields a trailing backslash, so omit it to
,→ improve friendliness/consistency.
{
*aBuf = ' \0 ' ;
--length;
}
}
}
return length;
}
 

184
A_ScreenWidth / A_ScreenHeight

The width and height of the primary monitor, in pixels (e.g. 1024 and 768).

To discover the dimensions of other monitors in a multi-monitor system, use SysGet.

To instead discover the width and height of the entire desktop (even if it spans multiple monitors), use the following example:

SysGet, VirtualWidth, 78 SysGet, VirtualHeight, 79

In addition, use SysGet to discover the work area of a monitor, which can be smaller than the monitor’s total area because the taskbar and other
registered desktop toolbars are excluded.
 
VarSizeType BIV_ScreenWidth_Height(LPTSTR aBuf, LPTSTR aVarName)
{
return aBuf
? (VarSizeType)_tcslen(ITOA(GetSystemMetrics(aVarName[13] ? SM_CYSCREEN : SM_CXSCREEN), aBuf))
: MAX_INTEGER_LENGTH;
}
 

185
A_IPAddress1 through 4

The IP addresses of the first 4 network adapters in the computer.


 
VarSizeType BIV_IPAddress(LPTSTR aBuf, LPTSTR aVarName)
{
// aaa.bbb.ccc.ddd = 15, but allow room for larger IP ' s in the future.
#define IP_ADDRESS_SIZE 32 // The maximum size of any of the strings we return, including terminator.
if (!aBuf)
return IP_ADDRESS_SIZE - 1; // -1 since we ' re returning the length of the var ' s contents, not the
,→ size.

WSADATA wsadata;
if (WSAStartup(MAKEWORD(1, 1), &wsadata)) // Failed (it returns 0 on success).
{
*aBuf = ' \0 ' ;
return 0;
}

char host_name[256];
gethostname(host_name, _countof(host_name));
HOSTENT *lpHost = gethostbyname(host_name);

// au3: How many adapters have we?


int adapter_count = 0;
while (lpHost->h_addr_list[adapter_count])
++adapter_count;

int adapter_index = aVarName[11] - ' 1 ' ; // A_IPAddress[1-4]


if (adapter_index >= adapter_count)
_tcscpy(aBuf, _T(”0.0.0.0”));
else
{
IN_ADDR inaddr;
memcpy(&inaddr, lpHost->h_addr_list[adapter_index], 4);
tcslcpy(aBuf, CStringTCharFromCharIfNeeded(inet_ntoa(inaddr)), IP_ADDRESS_SIZE);
}

WSACleanup();
return (VarSizeType)_tcslen(aBuf);
}
 

186
A_Cursor

The type of mouse cursor currently being displayed. It will be one of the following words: AppStarting, Arrow, Cross, Help, IBeam, Icon, No, Size, SizeAll,
SizeNESW, SizeNS, SizeNWSE, SizeWE, UpArrow, Wait, Unknown. The acronyms used with the size-type cursors are compass directions, e.g. NESW
= NorthEast+SouthWest. The hand-shaped cursors (pointing and grabbing) are classified as Unknown.
 
VarSizeType BIV_Cursor(LPTSTR aBuf, LPTSTR aVarName)
{
if (!aBuf)
return SMALL_STRING_LENGTH; // We ' re returning the length of the var ' s contents, not the size.

// Must fetch it at runtime, otherwise the program can ' t even be launched on Windows 95:
typedef BOOL (WINAPI *MyGetCursorInfoType)(PCURSORINFO);
static MyGetCursorInfoType MyGetCursorInfo = (MyGetCursorInfoType)GetProcAddress(GetModuleHandle(_T(”
,→ user32”)), ”GetCursorInfo”);

HCURSOR current_cursor;
if (MyGetCursorInfo) // v1.0.42.02: This method is used to avoid ATTACH_THREAD_INPUT, which interferes
,→ with double-clicking if called repeatedly at a high frequency.
{
CURSORINFO ci;
ci.cbSize = sizeof(CURSORINFO);
current_cursor = MyGetCursorInfo(&ci) ? ci.hCursor : NULL;
}
else // Windows 95 and old-service-pack versions of NT4 require the old method.
{
POINT point;
GetCursorPos(&point);
HWND target_window = WindowFromPoint(point);

// MSDN docs imply that threads must be attached for GetCursor() to work.
// A side-effect of attaching threads or of GetCursor() itself is that mouse double-clicks
// are interfered with, at least if this function is called repeatedly at a high frequency.
ATTACH_THREAD_INPUT
current_cursor = GetCursor();
DETACH_THREAD_INPUT
}

if (!current_cursor)
{
#define CURSOR_UNKNOWN _T(”Unknown”)
tcslcpy(aBuf, CURSOR_UNKNOWN, SMALL_STRING_LENGTH + 1);
return (VarSizeType)_tcslen(aBuf);
}

// Static so that it ' s initialized on first use (should help performance after the first time):
static HCURSOR sCursor[] = {LoadCursor(NULL, IDC_APPSTARTING), LoadCursor(NULL, IDC_ARROW)
, LoadCursor(NULL, IDC_CROSS), LoadCursor(NULL, IDC_HELP), LoadCursor(NULL, IDC_IBEAM)
, LoadCursor(NULL, IDC_ICON), LoadCursor(NULL, IDC_NO), LoadCursor(NULL, IDC_SIZE)
, LoadCursor(NULL, IDC_SIZEALL), LoadCursor(NULL, IDC_SIZENESW), LoadCursor(NULL, IDC_SIZENS)
, LoadCursor(NULL, IDC_SIZENWSE), LoadCursor(NULL, IDC_SIZEWE), LoadCursor(NULL, IDC_UPARROW)
, LoadCursor(NULL, IDC_WAIT)}; // If IDC_HAND were added, it would break existing scripts that rely on
,→ Unknown being synonymous with Hand. If ever added, IDC_HAND should return NULL on Win95/NT.
// The order in the below array must correspond to the order in the above array:
static LPTSTR sCursorName[] = {_T(”AppStarting”), _T(”Arrow”)
, _T(”Cross”), _T(”Help”), _T(”IBeam”)
, _T(”Icon”), _T(”No”), _T(”Size”)
, _T(”SizeAll”), _T(”SizeNESW”), _T(”SizeNS”) // NESW = NorthEast+SouthWest
, _T(”SizeNWSE”), _T(”SizeWE”), _T(”UpArrow”)
, _T(”Wait”), CURSOR_UNKNOWN}; // The last item is used to mark end-of-array.

187
static const size_t cursor_count = _countof(sCursor);

int i;
for (i = 0; i < cursor_count; ++i)
if (sCursor[i] == current_cursor)
break;

tcslcpy(aBuf, sCursorName[i], SMALL_STRING_LENGTH + 1); // If a is out-of-bounds, ”Unknown” will be used.


return (VarSizeType)_tcslen(aBuf);
}
 

188

Вам также может понравиться