Professional Documents
Culture Documents
File, Directory and Disk
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
// 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)
// 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.
}
2
FlushWriteBuffer();
_Close();
}
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());
}
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).
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);
}
DWORD mFlags;
DWORD mLength; // The length of available data in the buffer, in bytes.
DWORD mLastRead;
UINT mCodePage;
CPINFO mCodePageInfo;
TCHAR mLastWriteChar;
5
public:
TextFile() : mFile(INVALID_HANDLE_VALUE) {}
virtual ˜TextFile() { FlushWriteBuffer(); _Close(); }
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;
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.
// 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;
// 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.
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;
}
}
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 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;
}
return target_used;
}
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;
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.
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.
}
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));
// 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);
void TextFile::_Close()
{
if (mFile != INVALID_HANDLE_VALUE) {
if ((mFlags & ACCESS_MODE_MASK) != USEHANDLE)
CloseHandle(mFile);
mFile = INVALID_HANDLE_VALUE;
}
}
17
ReadFile(mFile, aBuffer, aBufSize, &dwRead, NULL);
return dwRead;
}
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:
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:
• Encoding Overrides the default encoding set by FileEncoding, where Encoding follows the same format.
Remarks
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);
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);
// 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;
}
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 function fails, the return value is 0 and A_LastError contains an error code.
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);
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;
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;
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)
{
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[].
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;
26
FileRead
• 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.
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();
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);
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);
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);
}
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
• 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;
// ..........
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);
}
// 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
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;
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
• 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.
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);
}
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;
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, §ors_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
• 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.
// 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, §ors_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));
}
error:
return SetErrorLevelOrThrow();
}
45
FileCopy
• 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:
• Flag (optional) this flag determines whether to overwrite files if they already exist:
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.
// Get local version of our source/dest with full path names, strip trailing \s
Util_GetFullPathName(szInputSource, szSource);
Util_GetFullPathName(szInputDest, szDest);
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.
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;
// 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
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.
// 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 ' ;
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);
// 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
• 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:
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;
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
• 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, ¤t_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.
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, ¤t_file));
FindClose(file_search);
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
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
• 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;
}
if (!aFilespec || !*aFilespec)
return SetErrorsOrThrow(true, ERROR_INVALID_PARAMETER);
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();
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
• 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.
// 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
}
65
FileGetTime
• 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.
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);
}
67
FileGetVersion
• 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.
free(pInfo);
68
FileMove
• 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:
• Flag (optional) this flag determines whether to overwrite files if they already exist:
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.
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];
// 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]);
// 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);
// Now add the drive and directory bit back onto the dest
_tcscat(szExpandedDest, szFileTemp);
// 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);
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.
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;
// 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).
– 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];
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);
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 ' ;
75
return !SHFileOperation(&FileOp);
}
76
FileRecycle
• 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.
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];
FileOp.pFrom = szFileTemp;
FileOp.wFunc = FO_DELETE;
FileOp.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_WANTNUKEWARNING;
77
FileRecycleEmpty
• DriveLetter If omitted, the recycle bin for all drives is emptied. Otherwise, specify a drive letter such as C:\
Remarks
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];
// 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 ' ;
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.
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: 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).
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.”));
}
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.
}
// 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;
}
// 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
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.
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
• 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):
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.
• 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.
}
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;
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
• 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?
Note: If FilePattern is a single folder rather than a wildcard pattern, it will always be operated upon regardless of this setting.
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.
Remarks
Operators:
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.
}
// 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, ¤t_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
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, ¤t_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, ¤t_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, ¤t_file));
FindClose(file_search);
} // if (file_search != INVALID_HANDLE_VALUE)
} // if (aDoRecurse)
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.
• OperateOnFolders?
Note: If FilePattern is a single folder rather than a wildcard pattern, it will always be operated upon regardless of this setting.
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.
Remarks
A file’s last access time might not be as precise on FAT16 & FAT32 volumes as it is on NTFS volumes.
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.
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;
}
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.
}
// 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.
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, ¤t_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
// 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, ¤t_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, ¤t_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, ¤t_file));
FindClose(file_search);
} // if (file_search != INVALID_HANDLE_VALUE)
} // if (aDoRecurse)
102
IniDelete
• 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
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).
• 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.
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
• 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).
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.
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;
106
|| !WriteFile(hFile, aSection, cb, &dwWritten, NULL) || dwWritten != cb )
result = FALSE;
if (!CloseHandle(hFile))
result = FALSE;
107
Loop (files & folders)
• 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.
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_LoopFileTimeAccessed: The time the file was last accessed. Format YYYYMMDDHH24MISS.
• 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.
// 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()
// 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.
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;
}
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);
}
113
}
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;
}
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
• 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
• 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.
120
EnvSet
Remarks
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
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
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);
// 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.
// 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;}
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)
{}
};
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;
}
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.
}
132
return mClipMemNewLocked; // The caller can now write to this mem.
}
133
// the clipboard were going to be immediately opened again.
return Close();
}
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;
}
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.
// 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;
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 ' ;
}
};
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:
// ! @ # $ % ˆ & ( ) - ' { } .
// 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;
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
• 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
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;
}
// 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;
}
finish:
return SetErrorsOrThrow(result != ERROR_SUCCESS, result);
} // RegDelete()
144
RegRead
• 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\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.
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;
}
// 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;
}
// 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.
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 ' ;
147
break;
}
// 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
• 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
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;
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;
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 ' ;
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);
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.
– 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?
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.
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, ®_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, ®_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;
}
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);
}
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
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.
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);
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];
#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);
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
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()
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:
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;
}
// Make sure this is set to NULL because CreateProcess() won ' t work if it ' s the empty string:
if (aWorkingDir && !*aWorkingDir)
aWorkingDir = NULL;
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;
167
ScriptError(_T(”System verbs unsupported with RunAs.”));
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.
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 (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.
• 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.
174
Shutdown
Remarks
• 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;
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;
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
A_DD
A_MMMM
Current month’s full name in the current user’s language, e.g. July
A_MMM
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
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
A_Sec
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.
// 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);
180
//break;
case ' D ' : // A_DD (synonymous with A_MDay)
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 instead discover the width and height of the entire desktop (even if it spans multiple monitors), use the following example:
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
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);
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;
188