[PATCH 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:). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10518
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 | 93 ++++++++++++++++-- programs/wscript/tests/run.c | 186 ++++++++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 9 deletions(-) diff --git a/programs/wscript/main.c b/programs/wscript/main.c index 2f1e517d419..a486958f20d 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, @@ -148,6 +150,43 @@ static void print_error(const WCHAR *string) WriteFile(GetStdHandle(STD_ERROR_HANDLE), "\r\n", 2, &count, FALSE); } +static void print_string(const WCHAR *string) +{ + DWORD count, ret, len, lena; + char *buf; + + len = lstrlenW(string); + ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), string, len, &count, NULL); + if(ret) { + WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"\r\n", 2, &count, NULL); + return; + } + + lena = WideCharToMultiByte(GetOEMCP(), 0, string, len, NULL, 0, NULL, NULL); + buf = malloc(lena); + if(!buf) + return; + + WideCharToMultiByte(GetOEMCP(), 0, string, len, buf, lena, NULL, NULL); + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, lena, &count, FALSE); + free(buf); + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\r\n", 2, &count, FALSE); +} + +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, IActiveScriptError *pscripterror) { @@ -446,16 +485,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("ignored //d switch\n"); + else if(wcsicmp(prop, L"x") == 0) + WINE_FIXME("ignored //x switch\n"); else if(wcsicmp(prop, L"u") == 0) - WINE_FIXME("ignoring /u\n"); + WINE_FIXME("ignored //u switch\n"); + else if(wcsicmp(prop, L"s") == 0) + WINE_FIXME("ignored //s switch\n"); + else if(wcsnicmp(prop, L"e:", 2) == 0) + WINE_FIXME("ignored //e: switch\n"); + else if(wcsnicmp(prop, L"h:", 2) == 0) + WINE_FIXME("ignored //h: switch\n"); + else if(wcsnicmp(prop, L"job:", 4) == 0) + WINE_FIXME("ignored //job: switch\n"); + else if(wcsnicmp(prop, L"t:", 2) == 0) + WINE_FIXME("ignored //t: switch\n"); else - { - WINE_FIXME("unsupported switch %s\n", debugstr_w(prop)); return FALSE; - } return TRUE; } @@ -477,8 +527,12 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm for(i=1; i<argc; i++) { if(*argv[i] == '/' || *argv[i] == '-') { - if(!set_host_properties(argv[i])) + if(!set_host_properties(argv[i])) { + WCHAR buf[256]; + swprintf(buf, ARRAY_SIZE(buf), L"Input Error: Unknown option \"%s\" specified.", argv[i]); + print_error(buf); return 1; + } }else { filename = argv[i]; argums = argv+i+1; @@ -488,7 +542,27 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm } if(!filename) { - WINE_FIXME("No file name specified\n"); + if(argc == 1) { + 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; + } + print_error(L"Input Error: There is no script file specified."); return 1; } res = GetFullPathNameW(filename, ARRAY_SIZE(scriptFullName), scriptFullName, &filepart); @@ -503,6 +577,9 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm CoInitialize(NULL); + if(!nologo) + print_banner(); + if(!create_engine(&clsid, &script, &parser)) { WINE_FIXME("Could not create script engine\n"); CoUninitialize(); diff --git a/programs/wscript/tests/run.c b/programs/wscript/tests/run.c index 9aae91ddc7a..f612b7ac8d9 100644 --- a/programs/wscript/tests/run.c +++ b/programs/wscript/tests/run.c @@ -526,6 +526,185 @@ 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); + ok(strstr(stderr_buf, "Unknown option") != NULL, + "expected 'Unknown option' in error output, got: %s\n", stderr_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(stderr_buf, "no script file") != NULL || strstr(stderr_buf, "No script file") != NULL, + "expected 'no script file' in error output, got: %s\n", stderr_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); +} + START_TEST(run) { char **argv; @@ -551,7 +730,12 @@ 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(); + } init_registry(FALSE); CoUninitialize(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10518
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)