# W.I.P
Code for parsing AUTOEXEC.BAT for environment variables was originally added in Windows NT 4.0. It's primary purpose was to make it so that Win32-based apps originally designed for Windows 9x that set environment variables using the file (as many did) would still work properly under NT. However, despite the fact that the last release of Windows 9x came out over 25 years ago, the feature still remains in Windows to this day. As is indicated by the period the feature was introduced during, it's mostly useful for running legacy software from around the time of the 9x to NT transition, but since it was never removed it's entirely possible that some newer software makes use of it as well. The feature is also toggleable using the ParseAutoexec value in the HCKU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon registry key.
Note that this feature is entirely unrelated to the AUTOEXEC.NT file intended for use with NTVDM.
**Todo**
* [x] Add ParseAutoexec value to wine.inf. * [x] Write code to check ParseAutoexec value. * [x] Write code to parse Autoexec.bat for environment variables. * [ ] Add code to concatenate the path to NTDLL.
-- v2: ntdll: Added autoexec.bat parsing code and ParseAutoexec value check. [1/2] wine.inf: add ParseAutoexec to HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
From: Gibson Pilconis gibsonpil@protonmail.com
--- loader/wine.inf.in | 1 + 1 file changed, 1 insertion(+)
diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 06551152f3a..ae0642ac6f8 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -357,6 +357,7 @@ HKLM,System\CurrentControlSet\Control\Class{745a17a0-74d3-11d0-b6fe-00a0c90f57d [CurrentVersion] HKCU,%CurrentVersion%\Run,,16 HKCU,%CurrentVersionNT%\Winlogon,,16 +HKCU,%CurrentVersionNT%\Winlogon,"ParseAutoexec",0,1 HKLM,%CurrentVersion%,"CommonFilesDir",,"%16427%" HKLM,%CurrentVersion%,"FirstInstallDateTime",1,21,81,7c,23 HKLM,%CurrentVersion%,"ProductId",,"12345-oem-0000001-54321"
From: Gibson Pilconis gibsonpil@protonmail.com
--- dlls/ntdll/unix/env.c | 167 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+)
diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index ad9ab0dc220..0bc663bf80f 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -25,6 +25,7 @@
#include "config.h"
+#include <strings.h> #include <assert.h> #include <errno.h> #include <locale.h> @@ -1916,6 +1917,171 @@ static void init_peb( RTL_USER_PROCESS_PARAMETERS *params, void *module ) } }
+/************************************************************************* + * is_parseautoexec_enabled + * + * Checks if ParseAutoexec is enabled in the registry. + */ +static BOOL is_parseautoexec_enabled(void) { + BOOL result = FALSE; + NTSTATUS ns; + HANDLE winlogon_key = NULL; + WCHAR parseautoexec_nameW[] = {'P','a','r','s','e','A','u','t','o','e','x','e','c',0}; + WCHAR parseautoexec_on[] = {'1',0}; + UNICODE_STRING parseautoexec_name; + KEY_VALUE_PARTIAL_INFORMATION *parseautoexec = NULL; + DWORD parseautoexec_size; + WCHAR* parseautoexec_value; + + ns = open_hkcu_key("Software\Microsoft\Windows NT\CurrentVersion\Winlogon", &winlogon_key); + if(FAILED(ns)) { + ERR("couldn't get Winlogon key from the registry.\n"); + goto end; + } + + init_unicode_string(&parseautoexec_name, parseautoexec_nameW); + ns = NtQueryValueKey(winlogon_key, &parseautoexec_name, KeyValuePartialInformation, NULL, 0, &parseautoexec_size); + if(ns != STATUS_BUFFER_TOO_SMALL && ns != STATUS_BUFFER_OVERFLOW && ns != STATUS_SUCCESS) { + ERR("failed to get ParseAutoexec size. maybe it doesn't exist?\n"); + goto end; + } + + parseautoexec = (KEY_VALUE_PARTIAL_INFORMATION*)malloc(parseautoexec_size); + ns = NtQueryValueKey(winlogon_key, &parseautoexec_name, KeyValuePartialInformation, parseautoexec, parseautoexec_size, &parseautoexec_size); + if(ns) { + ERR("failed to get ParseAutoexec value.\n"); + goto end; + } + parseautoexec_value = (WCHAR*)(parseautoexec->Data); + + /* Check if Autoexec parsing is on. */ + if(ntdll_wcsicmp(parseautoexec_value, parseautoexec_on) != 0) { + result = TRUE; + } + +end: + if(winlogon_key) NtClose(winlogon_key); + if(parseautoexec) free(parseautoexec); + return result; +} + +/************************************************************************* + * parse_autoexec + * + * Parses and adds environment variables defined in AUTOEXEC.bat. + */ +static BOOL parse_autoexec( WCHAR **env, SIZE_T *pos, SIZE_T *size ) { + NTSTATUS ns; + BOOL result = FALSE; + + /* File IO variables. */ + LARGE_INTEGER autoexec_offset = {0}; + WCHAR autoexec_pathW[] = {'\','?','?','\','C',':','\','a','u','t','o','e','x','e','c','.','b','a','t',0}; + UNICODE_STRING autoexec_path; + HANDLE autoexec = NULL; + OBJECT_ATTRIBUTES autoexec_attributes; + IO_STATUS_BLOCK isb; + FILE_STANDARD_INFORMATION autoexec_info = {0}; + char* autoexec_buffer = NULL; + int autoexec_buffer_length; + + /* Parsing variables. */ + char variable_name[256] = {0}; + char variable_text[32768] = {0}; + BOOL first_run = TRUE; + BOOL skip = FALSE; + BOOL after_equals = FALSE; + + init_unicode_string(&autoexec_path, autoexec_pathW); + InitializeObjectAttributes(&autoexec_attributes, &autoexec_path, 0, NULL, NULL); + ns = NtOpenFile(&autoexec, GENERIC_READ, &autoexec_attributes, &isb, NULL, FILE_SYNCHRONOUS_IO_NONALERT); + if(ns) { + if(ns == STATUS_NO_SUCH_FILE) { + /* The user simply doesn't have an autoexec. */ + result = TRUE; + goto end; + } + ERR("unable to open autoexec.bat.\n"); + goto end; + } + + ns = NtQueryInformationFile(autoexec, &isb, &autoexec_info, sizeof(autoexec_info), FileStandardInformation); + if(ns) { + ERR("unable to query autoexec.bat information.\n"); + goto end; + } + autoexec_buffer_length = autoexec_info.EndOfFile.QuadPart + 1; + + autoexec_buffer = (char*)malloc(autoexec_buffer_length); + ns = NtReadFile(autoexec, NULL, NULL, NULL, &isb, autoexec_buffer, autoexec_buffer_length, &autoexec_offset, NULL); + if(ns) { + ERR("unable to read autoexec.bat.\n"); + goto end; + } + + /* Through testing I found that when parsing autoexec files, modern version of + Windows only look at environment variable declarations (i.e. "SET <var>=") and + plain path declarations (i.e. "Path=C:"). No commands are actually run. */ + for(char* i = autoexec_buffer; *i != '\0'; i++) { + /* Check for delimiters. */ + if(*i == '\n' || *i == '\r' || *i == '&' || first_run == TRUE) { + if(!first_run) { + /* Save last variable. */ + /* TODO: Add variable concat code! */ + i++; /* Move past delimiter */ + } + + *variable_name = 0; + *variable_text = 0; + + /* Hop whitespaces. */ + while(*i == ' ') i++; + + /* Reset state. */ + first_run = FALSE; + after_equals = FALSE; + skip = FALSE; + + if(strncasecmp(i, "SET", 3) == 0) { /* SET directive. */ + i += 3; /* Skip directive. */ + continue; + } else if(strncasecmp(i, "Path", 4) == 0) { /* Path directive. */ + after_equals = TRUE; + strcpy(variable_name, "PATH"); + i += 4; /* Skip Path directive. */ + continue; + } else { + skip = TRUE; + continue; + } + } else if(skip) { + continue; + } else if(*i == '=') { + after_equals = TRUE; + continue; + } + + /* Bounds checking. */ + if(strlen(variable_name) >= 255 || strlen(variable_text) >= 32767) { + ERR("buffer filled up while parsing autoexec.bat.\n"); + goto end; + } + + /* Add text to buffers. */ + if(after_equals) { + strncat(variable_text, i, 1); + } else { + strncat(variable_name, i, 1); + } + } + + result = TRUE; + +end: + if(autoexec) NtClose(autoexec); + if(autoexec_buffer) free(autoexec_buffer); + return result; +}
/************************************************************************* * build_initial_params @@ -1950,6 +2116,7 @@ static RTL_USER_PROCESS_PARAMETERS *build_initial_params( void **module ) is_prefix_bootstrap = !!bootstrap; free( bootstrap ); add_registry_environment( &env, &env_pos, &env_size ); + if(is_parseautoexec_enabled()) parse_autoexec( &env, &env_pos, &env_size ); env[env_pos++] = 0;
status = load_main_exe( NULL, main_argv[1], curdir, 0, &image, module );
As is indicated by the period the feature was introduced during, it's mostly useful for running legacy software from around the time of the 9x to NT transition, but since it was never removed it's entirely possible that some newer software makes use of it as well.
That's very unlikely, and we don't add features in Wine unless there's a demonstrated need. Please explain which actual application requires this.
On Tue Feb 6 14:04:42 2024 +0000, Alexandre Julliard wrote:
As is indicated by the period the feature was introduced during, it's
mostly useful for running legacy software from around the time of the 9x to NT transition, but since it was never removed it's entirely possible that some newer software makes use of it as well. That's very unlikely, and we don't add features in Wine unless there's a demonstrated need. Please explain which actual application requires this.
I've seen some 90s compilers make use of it. I'll close this pull request until I get the chance to find some examples.
This merge request was closed by Gibson Pilconis.