[PATCH v4 0/1] MR10518: wscript: Implement error messages, usage output, and //nologo banner.
Report user-visible error messages to stderr matching Windows behavior for unknown command-line options and missing script file. Display usage information with Wine version when invoked with no arguments. Implement //nologo and //logo flags to control the banner displayed before script execution. Add FIXME stubs for other recognized but unimplemented options (//X, //S, //E:, //H:, //Job:, //T:). -- v4: wscript: Implement error messages, usage output, and //nologo banner. https://gitlab.winehq.org/wine/wine/-/merge_requests/10518
From: Francis De Brabandere <francisdb@gmail.com> Report user-visible "Input Error" messages to stdout matching Windows behavior for missing script files and nonexistent files. Display usage information with Wine version when invoked with no arguments. Script runtime errors (OnScriptError) remain on stderr. Implement //nologo and //logo flags to control the banner displayed before script execution. Treat unrecognized options as filenames to support Unix-style paths on Wine. Add FIXME stubs for other recognized but unimplemented options (//X, //S, //E:, //H:, //Job:, //T:). --- programs/wscript/main.c | 119 ++++++++++++++---- programs/wscript/tests/run.c | 232 +++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 26 deletions(-) diff --git a/programs/wscript/main.c b/programs/wscript/main.c index 2f1e517d419..e63def3334d 100644 --- a/programs/wscript/main.c +++ b/programs/wscript/main.c @@ -52,6 +52,8 @@ WCHAR scriptFullName[MAX_PATH]; ITypeInfo *host_ti; ITypeInfo *arguments_ti; +static BOOL nologo; + static HRESULT query_interface(REFIID,void**); static HRESULT WINAPI ActiveScriptSite_QueryInterface(IActiveScriptSite *iface, @@ -120,20 +122,15 @@ static HRESULT WINAPI ActiveScriptSite_OnStateChange(IActiveScriptSite *iface, return S_OK; } -static void print_error(const WCHAR *string) +static void write_to_handle(HANDLE handle, const WCHAR *string) { DWORD count, ret, len, lena; char *buf; - if(wshInteractive) { - MessageBoxW(NULL, string, L"Windows Script Host", MB_OK); - return; - } - len = lstrlenW(string); - ret = WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), string, len, &count, NULL); + ret = WriteConsoleW(handle, string, len, &count, NULL); if(ret) { - WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"\r\n", 2, &count, NULL); + WriteConsoleW(handle, L"\r\n", 2, &count, NULL); return; } @@ -143,9 +140,38 @@ static void print_error(const WCHAR *string) return; WideCharToMultiByte(GetOEMCP(), 0, string, len, buf, lena, NULL, NULL); - WriteFile(GetStdHandle(STD_ERROR_HANDLE), buf, lena, &count, FALSE); + WriteFile(handle, buf, lena, &count, FALSE); free(buf); - WriteFile(GetStdHandle(STD_ERROR_HANDLE), "\r\n", 2, &count, FALSE); + WriteFile(handle, "\r\n", 2, &count, FALSE); +} + +static void print_error(const WCHAR *string) +{ + if(wshInteractive) { + MessageBoxW(NULL, string, L"Windows Script Host", MB_OK); + return; + } + + write_to_handle(GetStdHandle(STD_ERROR_HANDLE), string); +} + +static void print_string(const WCHAR *string) +{ + write_to_handle(GetStdHandle(STD_OUTPUT_HANDLE), string); +} + +static void print_banner(void) +{ + const char * (CDECL *wine_get_version)(void); + WCHAR header[64]; + + wine_get_version = (void *)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "wine_get_version"); + if(wine_get_version) + swprintf(header, ARRAY_SIZE(header), L"Wine %S Script Host", wine_get_version()); + else + swprintf(header, ARRAY_SIZE(header), L"Wine Script Host"); + print_string(header); + print_string(L""); } static HRESULT WINAPI ActiveScriptSite_OnScriptError(IActiveScriptSite *iface, @@ -446,16 +472,27 @@ static BOOL set_host_properties(const WCHAR *prop) else if(wcsicmp(prop, L"b") == 0) wshInteractive = VARIANT_FALSE; else if(wcsicmp(prop, L"nologo") == 0) - WINE_FIXME("ignored %s switch\n", debugstr_w(L"nologo")); + nologo = TRUE; + else if(wcsicmp(prop, L"logo") == 0) + nologo = FALSE; else if(wcsicmp(prop, L"d") == 0) - WINE_FIXME("ignoring /d\n"); + WINE_FIXME("ignoring //d\n"); + else if(wcsicmp(prop, L"x") == 0) + WINE_FIXME("ignoring //x\n"); else if(wcsicmp(prop, L"u") == 0) - WINE_FIXME("ignoring /u\n"); + WINE_FIXME("ignoring //u\n"); + else if(wcsicmp(prop, L"s") == 0) + WINE_FIXME("ignoring //s\n"); + else if(wcsnicmp(prop, L"e:", 2) == 0) + WINE_FIXME("ignoring //e:\n"); + else if(wcsnicmp(prop, L"h:", 2) == 0) + WINE_FIXME("ignoring //h:\n"); + else if(wcsnicmp(prop, L"job:", 4) == 0) + WINE_FIXME("ignoring //job:\n"); + else if(wcsnicmp(prop, L"t:", 2) == 0) + WINE_FIXME("ignoring //t:\n"); else - { - WINE_FIXME("unsupported switch %s\n", debugstr_w(prop)); return FALSE; - } return TRUE; } @@ -476,25 +513,55 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm return 1; for(i=1; i<argc; i++) { - if(*argv[i] == '/' || *argv[i] == '-') { - if(!set_host_properties(argv[i])) - return 1; - }else { - filename = argv[i]; - argums = argv+i+1; - numOfArgs = argc-i-1; - break; - } + if((*argv[i] == '/' || *argv[i] == '-') && set_host_properties(argv[i])) + continue; + filename = argv[i]; + argums = argv+i+1; + numOfArgs = argc-i-1; + break; + } + + if(!filename && argc == 1) { + if(!nologo) + print_banner(); + print_string(L"Usage: CScript scriptname.extension [option...] [arguments...]"); + print_string(L""); + print_string(L"Options:"); + print_string(L" //B Batch mode: Suppresses script errors and prompts from displaying"); + print_string(L" //D Enable Active Debugging"); + print_string(L" //E:engine Use engine for executing script"); + print_string(L" //H:CScript Changes the default script host to CScript.exe"); + print_string(L" //H:WScript Changes the default script host to WScript.exe (default)"); + print_string(L" //I Interactive mode (default, opposite of //B)"); + print_string(L" //Job:xxxx Execute a WSF job"); + print_string(L" //Logo Display logo (default)"); + print_string(L" //Nologo Prevent logo display: No banner will be shown at execution time"); + print_string(L" //S Save current command line options for this user"); + print_string(L" //T:nn Time out in seconds: Maximum time a script is permitted to run"); + print_string(L" //X Execute script in debugger"); + print_string(L" //U Use Unicode for redirected I/O from the console"); + return 0; } + if(!nologo) + print_banner(); + if(!filename) { - WINE_FIXME("No file name specified\n"); + print_string(L"Input Error: There is no script file specified."); return 1; } + res = GetFullPathNameW(filename, ARRAY_SIZE(scriptFullName), scriptFullName, &filepart); if(!res || res > ARRAY_SIZE(scriptFullName)) return 1; + if(GetFileAttributesW(scriptFullName) == INVALID_FILE_ATTRIBUTES) { + WCHAR buf[MAX_PATH + 64]; + swprintf(buf, ARRAY_SIZE(buf), L"Input Error: Can not find script file \"%s\".", scriptFullName); + print_string(buf); + return 1; + } + ext = wcsrchr(filepart, '.'); if(!ext || !get_engine_clsid(ext, &clsid)) { WINE_FIXME("Could not find engine for %s\n", wine_dbgstr_w(ext)); diff --git a/programs/wscript/tests/run.c b/programs/wscript/tests/run.c index 9aae91ddc7a..dc8df2bcea1 100644 --- a/programs/wscript/tests/run.c +++ b/programs/wscript/tests/run.c @@ -526,6 +526,231 @@ cleanup: DeleteFileA(file_name); } +static DWORD run_cscript(const char *args, char *stdout_buf, size_t stdout_buf_size, + char *stderr_buf, size_t stderr_buf_size) +{ + char command[MAX_PATH]; + SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; + STARTUPINFOA si = {sizeof(si)}; + PROCESS_INFORMATION pi; + HANDLE stdout_read, stdout_write; + HANDLE stderr_read, stderr_write; + DWORD exit_code, size; + BOOL bres; + + bres = CreatePipe(&stderr_read, &stderr_write, &sa, 0); + ok(bres, "CreatePipe failed: %lu\n", GetLastError()); + if(!bres) + return ~0u; + + bres = CreatePipe(&stdout_read, &stdout_write, &sa, 0); + ok(bres, "CreatePipe failed: %lu\n", GetLastError()); + if(!bres) { + CloseHandle(stderr_read); + CloseHandle(stderr_write); + return ~0u; + } + + SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0); + + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = stdout_write; + si.hStdError = stderr_write; + + sprintf(command, "cscript.exe %s", args); + bres = CreateProcessA(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); + CloseHandle(stderr_write); + CloseHandle(stdout_write); + if(!bres) { + win_skip("cscript.exe is not available\n"); + CloseHandle(stderr_read); + CloseHandle(stdout_read); + return ~0u; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + bres = GetExitCodeProcess(pi.hProcess, &exit_code); + ok(bres, "GetExitCodeProcess failed: %lu\n", GetLastError()); + + memset(stdout_buf, 0, stdout_buf_size); + ReadFile(stdout_read, stdout_buf, stdout_buf_size - 1, &size, NULL); + stdout_buf[size] = 0; + + memset(stderr_buf, 0, stderr_buf_size); + ReadFile(stderr_read, stderr_buf, stderr_buf_size - 1, &size, NULL); + stderr_buf[size] = 0; + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + CloseHandle(stdout_read); + CloseHandle(stderr_read); + return exit_code; +} + +static void run_cscript_unknown_option_test(void) +{ + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + exit_code = run_cscript("//nologo /unknownoption", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 1, "exit_code = %lu, expected 1\n", exit_code); + /* On Windows, cscript reports "Unknown option" on stdout. On Wine, + * unrecognized options are treated as filenames to allow Unix-style + * paths (/tmp/foo.vbs), so the error will be about the file instead. */ + ok(strstr(stdout_buf, "Unknown option") != NULL || strstr(stdout_buf, "Can not find") != NULL, + "expected error on stdout, got: %s\n", stdout_buf); +} + +static void run_cscript_no_file_test(void) +{ + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + exit_code = run_cscript("//nologo", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 1, "exit_code = %lu, expected 1\n", exit_code); + ok(strstr(stdout_buf, "no script file") != NULL || strstr(stdout_buf, "No script file") != NULL, + "expected 'no script file' in stdout, got: %s\n", stdout_buf); +} + +static void run_cscript_usage_test(void) +{ + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + exit_code = run_cscript("", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 0, "exit_code = %lu, expected 0\n", exit_code); + ok(strstr(stdout_buf, "Usage") != NULL, + "expected 'Usage' in stdout, got: %s\n", stdout_buf); +} + +static BOOL create_temp_vbs(const char *file_name, const char *data) +{ + HANDLE file; + DWORD size; + BOOL bres; + + file = CreateFileA(file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + ok(file != INVALID_HANDLE_VALUE, "CreateFile failed: %lu\n", GetLastError()); + if(file == INVALID_HANDLE_VALUE) + return FALSE; + + bres = WriteFile(file, data, strlen(data), &size, NULL); + CloseHandle(file); + ok(bres, "Could not write to file: %lu\n", GetLastError()); + return bres; +} + +static void run_cscript_logo_test(void) +{ + static const char empty_script[] = "\n"; + char file_name[] = "test_logo.vbs"; + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + size_t len; + + if(!create_temp_vbs(file_name, empty_script)) + goto cleanup; + + /* Without //nologo: banner should be shown on stdout */ + exit_code = run_cscript(file_name, stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + goto cleanup; + ok(exit_code == 0, "exit_code = %lu, expected 0\n", exit_code); + ok(strstr(stdout_buf, "Script Host") != NULL, + "expected 'Script Host' banner in stdout, got: %s\n", stdout_buf); + + /* Banner should be followed by a blank line */ + ok(strstr(stdout_buf, "\r\n\r\n") != NULL, + "expected blank line after banner, got: %s\n", stdout_buf); + + /* Output should end with a blank line (\r\n\r\n at the end) */ + len = strlen(stdout_buf); + ok(len >= 4 && !memcmp(stdout_buf + len - 4, "\r\n\r\n", 4), + "expected trailing blank line, got: %s\n", stdout_buf); + +cleanup: + DeleteFileA(file_name); +} + +static void run_cscript_nologo_test(void) +{ + static const char empty_script[] = "\n"; + char file_name[] = "test_nologo.vbs"; + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + if(!create_temp_vbs(file_name, empty_script)) + goto cleanup; + + /* With //nologo: no banner, no output for empty script */ + exit_code = run_cscript("//nologo test_nologo.vbs", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + goto cleanup; + ok(exit_code == 0, "exit_code = %lu, expected 0\n", exit_code); + ok(stdout_buf[0] == 0, "expected no stdout output with //nologo, got: %s\n", stdout_buf); + +cleanup: + DeleteFileA(file_name); +} + +static void run_cscript_nonexistent_file_test(void) +{ + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + /* Nonexistent file with //nologo: error on stdout */ + exit_code = run_cscript("//nologo nonexistent_file.vbs", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 1, "exit_code = %lu, expected 1\n", exit_code); + ok(strstr(stdout_buf, "Can not find") != NULL || strstr(stdout_buf, "nonexistent_file") != NULL, + "expected file not found error on stdout, got: %s\n", stdout_buf); + + /* Nonexistent file without //nologo: banner + error on stdout */ + exit_code = run_cscript("nonexistent_file.vbs", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 1, "exit_code = %lu, expected 1\n", exit_code); + ok(strstr(stdout_buf, "Script Host") != NULL, + "expected banner on stdout before error, got: %s\n", stdout_buf); + ok(strstr(stdout_buf, "Can not find") != NULL || strstr(stdout_buf, "nonexistent_file") != NULL, + "expected file not found error on stdout, got: %s\n", stdout_buf); +} + +static void run_cscript_error_on_stdout_test(void) +{ + char stdout_buf[4096], stderr_buf[4096]; + DWORD exit_code; + + /* Unknown option without //nologo: banner + error on stdout */ + exit_code = run_cscript("/unknownoption", stdout_buf, sizeof(stdout_buf), + stderr_buf, sizeof(stderr_buf)); + if(exit_code == ~0u) + return; + ok(exit_code == 1, "exit_code = %lu, expected 1\n", exit_code); + ok(strstr(stdout_buf, "Script Host") != NULL, + "expected banner on stdout, got: %s\n", stdout_buf); + /* Error message should be on stdout, not stderr */ + ok(stderr_buf[0] == 0, "expected no stderr output, got: %s\n", stderr_buf); +} + START_TEST(run) { char **argv; @@ -551,6 +776,13 @@ START_TEST(run) "winetest.ok(false, 'not quit?');\n", 3); run_cscript_error_test(); + run_cscript_unknown_option_test(); + run_cscript_no_file_test(); + run_cscript_usage_test(); + run_cscript_logo_test(); + run_cscript_nologo_test(); + run_cscript_nonexistent_file_test(); + run_cscript_error_on_stdout_test(); } init_registry(FALSE); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10518
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)