From: Francis De Brabandere <francisdb@gmail.com> 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:). --- 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