From 108db8f007fa845f344e7d415fbbbe66c6e88c84 Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Sun, 19 May 2024 13:57:47 +0200 Subject: [PATCH] [CSCRIPT][WSCRIPT][BOOTDATA] Basic .wsf support (#6140) Support for .wsf files with a single script block --- .../cmdutils/cscript/CMakeLists.txt | 1 + .../cmdutils/wscript/CMakeLists.txt | 1 + base/applications/cmdutils/wscript/main.c | 378 ++++++++++++++++++ boot/bootdata/hivecls.inf | 11 + modules/rostests/apitests/CMakeLists.txt | 1 + .../rostests/apitests/wscript/CMakeLists.txt | 10 + modules/rostests/apitests/wscript/testlist.c | 10 + modules/rostests/apitests/wscript/wsf.c | 170 ++++++++ 8 files changed, 582 insertions(+) create mode 100644 modules/rostests/apitests/wscript/CMakeLists.txt create mode 100644 modules/rostests/apitests/wscript/testlist.c create mode 100644 modules/rostests/apitests/wscript/wsf.c diff --git a/base/applications/cmdutils/cscript/CMakeLists.txt b/base/applications/cmdutils/cscript/CMakeLists.txt index 5e0b98d08cb..4d639fc7b63 100644 --- a/base/applications/cmdutils/cscript/CMakeLists.txt +++ b/base/applications/cmdutils/cscript/CMakeLists.txt @@ -15,6 +15,7 @@ add_typelib(ihost.idl) set_source_files_properties(rsrc.rc PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ihost.tlb) target_link_libraries(cscript uuid wine) set_module_type(cscript win32cui UNICODE) +add_delay_importlibs(cscript shlwapi) add_importlibs(cscript shell32 oleaut32 ole32 advapi32 user32 msvcrt kernel32 ntdll) add_dependencies(cscript stdole2 cscript_idlheader) add_pch(cscript ${wscript_folder}/precomp.h SOURCE) diff --git a/base/applications/cmdutils/wscript/CMakeLists.txt b/base/applications/cmdutils/wscript/CMakeLists.txt index a574ff5fb55..d177b8b9948 100644 --- a/base/applications/cmdutils/wscript/CMakeLists.txt +++ b/base/applications/cmdutils/wscript/CMakeLists.txt @@ -13,6 +13,7 @@ add_typelib(ihost.idl) set_source_files_properties(rsrc.rc PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ihost.tlb) target_link_libraries(wscript uuid wine) set_module_type(wscript win32gui UNICODE) +add_delay_importlibs(wscript shlwapi) add_importlibs(wscript shell32 oleaut32 ole32 user32 advapi32 msvcrt kernel32 ntdll) add_dependencies(wscript stdole2 wscript_idlheader) add_pch(wscript precomp.h SOURCE) diff --git a/base/applications/cmdutils/wscript/main.c b/base/applications/cmdutils/wscript/main.c index 030243984df..8e88ac70179 100644 --- a/base/applications/cmdutils/wscript/main.c +++ b/base/applications/cmdutils/wscript/main.c @@ -60,6 +60,141 @@ ITypeInfo *arguments_ti; static HRESULT query_interface(REFIID,void**); +#ifdef __REACTOS__ +#include + +typedef struct { + UINT itemsize, count; + void *mem; +} SIMPLEVECTOR; + +static void SVect_Free(SIMPLEVECTOR *pV) +{ + if (pV->mem) + LocalFree(pV->mem); + pV->mem = NULL; +} + +static void* SVect_Add(SIMPLEVECTOR *pV) +{ + void *p = NULL; + if (pV->mem) + { + p = LocalReAlloc(pV->mem, pV->itemsize * (pV->count + 1), LMEM_FIXED | LMEM_MOVEABLE); + if (p) + { + pV->mem = p; + p = (char*)p + (pV->count * pV->itemsize); + pV->count++; + } + } + else + { + p = pV->mem = LocalAlloc(LMEM_FIXED, pV->itemsize); + if (p) + { + pV->count = 1; + } + } + return p; +} + +#define SVect_Delete(pV, pItem) ( (pV), (pItem) ) /* Should not be required for global items */ + +static void* SVect_Get(SIMPLEVECTOR *pV, UINT i) +{ + return pV->mem && i < pV->count ? (char*)pV->mem + (i * pV->itemsize) : NULL; +} + +typedef struct { + BSTR name; + IUnknown *punk; +} GLOBAL_ITEM; + +SIMPLEVECTOR g_global_items = { sizeof(GLOBAL_ITEM) }; + +static void free_globals(void) +{ + UINT i; + for (i = 0;; ++i) + { + GLOBAL_ITEM *p = (GLOBAL_ITEM*)SVect_Get(&g_global_items, i); + if (!p) + break; + IUnknown_Release(p->punk); + SysFreeString(p->name); + } + SVect_Free(&g_global_items); +} + +static HRESULT add_globalitem(IActiveScript *script, BSTR name, IUnknown *punk, DWORD siflags) +{ + GLOBAL_ITEM *item; + HRESULT hr; + + name = SysAllocString(name); + if (!name) + return E_OUTOFMEMORY; + + item = SVect_Add(&g_global_items); + if (item) + { + item->name = name; + item->punk = punk; + hr = IActiveScript_AddNamedItem(script, name, siflags); + if (SUCCEEDED(hr)) + { + IUnknown_AddRef(punk); + return hr; + } + SVect_Delete(&g_global_items, item); + } + SysFreeString(name); + return E_OUTOFMEMORY; +} + +static HRESULT add_globalitem_from_clsid(IActiveScript *script, BSTR name, REFCLSID clsid, DWORD siflags) +{ + IUnknown *punk; + HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void**)&punk); + if (SUCCEEDED(hr)) + { + hr = add_globalitem(script, name, punk, siflags); + IUnknown_Release(punk); + } + return hr; +} + +static HRESULT get_globalitem_info(LPCOLESTR Name, DWORD Mask, IUnknown **ppunk, ITypeInfo **ppti, BOOL *pHandled) +{ + HRESULT hr = S_FALSE; + UINT i; + for (i = 0;; ++i) + { + GLOBAL_ITEM *p = (GLOBAL_ITEM*)SVect_Get(&g_global_items, i); + if (!p) + break; + if (!lstrcmpiW(Name, p->name)) + { + if (ppti) + *ppti = NULL; + if (Mask & SCRIPTINFO_IUNKNOWN) + { + *ppunk = p->punk; + if (p->punk) + { + IUnknown_AddRef(p->punk); + *pHandled = TRUE; + } + return S_OK; + } + break; + } + } + return hr; +} +#endif + static HRESULT WINAPI ActiveScriptSite_QueryInterface(IActiveScriptSite *iface, REFIID riid, void **ppv) { @@ -89,6 +224,15 @@ static HRESULT WINAPI ActiveScriptSite_GetItemInfo(IActiveScriptSite *iface, { WINE_TRACE("(%s %x %p %p)\n", wine_dbgstr_w(pstrName), dwReturnMask, ppunkItem, ppti); +#ifdef __REACTOS__ + { + BOOL handled = FALSE; + HRESULT hr = get_globalitem_info(pstrName, dwReturnMask, ppunkItem, ppti, &handled); + if (handled) + return hr; + } +#endif + if(lstrcmpW(pstrName, wshW) && lstrcmpW(pstrName, wscriptW)) return E_FAIL; @@ -388,6 +532,231 @@ static void run_script(const WCHAR *filename, IActiveScript *script, IActiveScri WINE_FIXME("SetScriptState failed: %08x\n", hres); } +#ifdef __REACTOS__ +#include +#include + +static HRESULT xmldomnode_getattributevalue(IXMLDOMNode *pnode, LPCWSTR name, BSTR *pout) +{ + IXMLDOMNamedNodeMap *pmap; + HRESULT hr = E_OUTOFMEMORY; + BSTR bsname = SysAllocString(name); + *pout = NULL; + if (bsname && SUCCEEDED(hr = IXMLDOMNode_get_attributes(pnode, &pmap))) + { + if (SUCCEEDED(hr = IXMLDOMNamedNodeMap_getNamedItem(pmap, bsname, &pnode))) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + if (pnode) + { + hr = IXMLDOMNode_get_text(pnode, pout); + if (SUCCEEDED(hr) && !*pout) + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + IXMLDOMNode_Release(pnode); + } + } + IXMLDOMNamedNodeMap_Release(pmap); + } + SysFreeString(bsname); + return hr; +} + +static HRESULT xmldomelem_getelembytag(IXMLDOMElement *pelem, LPCWSTR name, long index, IXMLDOMNode**ppout) +{ + HRESULT hr = E_OUTOFMEMORY; + IXMLDOMNodeList *pnl; + BSTR bsname = SysAllocString(name); + *ppout = NULL; + if (bsname && SUCCEEDED(hr = IXMLDOMElement_getElementsByTagName(pelem, bsname, &pnl))) + { + hr = IXMLDOMNodeList_get_item(pnl, index, ppout); + if (SUCCEEDED(hr) && !*ppout) + hr = HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); + IUnknown_Release(pnl); + } + SysFreeString(bsname); + return hr; +} + +static HRESULT xmldomelem_getelembytagasdomelem(IXMLDOMElement *pelem, LPCWSTR name, long index, IXMLDOMElement**ppout) +{ + IXMLDOMNode *pnode; + HRESULT hr = xmldomelem_getelembytag(pelem, name, index, &pnode); + *ppout = NULL; + if (SUCCEEDED(hr)) + { + hr = IUnknown_QueryInterface(pnode, &IID_IXMLDOMElement, (void**)ppout); + IUnknown_Release(pnode); + } + return hr; +} + +static void wsf_addobjectfromnode(IActiveScript *script, IXMLDOMNode *obj) +{ + BSTR bsid, bsclsid = NULL; + if (SUCCEEDED(xmldomnode_getattributevalue(obj, L"id", &bsid))) + { + CLSID clsid; + HRESULT hr; + hr = xmldomnode_getattributevalue(obj, L"clsid", &bsclsid); + if (FAILED(hr) || FAILED(CLSIDFromString(bsclsid, &clsid))) + { + SysFreeString(bsclsid); + if (SUCCEEDED(hr = xmldomnode_getattributevalue(obj, L"progid", &bsclsid))) + { + hr = CLSIDFromProgID(bsclsid, &clsid); + SysFreeString(bsclsid); + } + } + if (SUCCEEDED(hr)) + { + hr = add_globalitem_from_clsid(script, bsid, &clsid, SCRIPTITEM_ISVISIBLE); + } + SysFreeString(bsid); + } +} + +static HRESULT run_wsfjob(IXMLDOMElement *jobtag) +{ + // FIXME: We are supposed to somehow handle multiple languages in the same IActiveScript. + IActiveScript *script; + LPCWSTR deflang = L"JScript"; + IXMLDOMNode *scripttag; + HRESULT hr = S_OK; + if (SUCCEEDED(xmldomelem_getelembytag(jobtag, L"script", 0, &scripttag))) + { + CLSID clsid; + IActiveScriptParse *parser; + BSTR lang, code; + if (FAILED(xmldomnode_getattributevalue(scripttag, L"language", &lang))) + lang = NULL; + hr = CLSIDFromProgID(lang ? lang : deflang, &clsid); + SysFreeString(lang); + + if (SUCCEEDED(hr)) + { + hr = E_FAIL; + if (create_engine(&clsid, &script, &parser)) + { + if (init_engine(script, parser)) + { + long index; + for (index = 0; index < 0x7fffffff; ++index) + { + IXMLDOMNode *obj; + if (SUCCEEDED(xmldomelem_getelembytag(jobtag, L"object", index, &obj))) + { + wsf_addobjectfromnode(script, obj); + IUnknown_Release(obj); + } + else + { + break; + } + } + + if (SUCCEEDED(hr = IXMLDOMNode_get_text(scripttag, &code))) + { + hr = IActiveScriptParse_ParseScriptText(parser, code, NULL, NULL, NULL, 1, 1, + SCRIPTTEXT_HOSTMANAGESSOURCE|SCRIPTITEM_ISVISIBLE, + NULL, NULL); + if (SUCCEEDED(hr)) + { + hr = IActiveScript_SetScriptState(script, SCRIPTSTATE_STARTED); + IActiveScript_Close(script); + } + SysFreeString(code); + } + ITypeInfo_Release(host_ti); + } + IUnknown_Release(parser); + IUnknown_Release(script); + } + } + IUnknown_Release(scripttag); + } + return hr; +} + +/* +.WSF files can contain a single job, or multiple jobs if contained in a package. +Jobs are identified by their id and if no id is specified, the first job is used. +Each job can contain multiple script tags and all scripts are merged into one. + + +or + + + +or + + + + + +*/ +static HRESULT run_wsf(LPCWSTR xmlpath) +{ + WCHAR url[ARRAY_SIZE("file://") + max(ARRAY_SIZE(scriptFullName), MAX_PATH)]; + DWORD cch = ARRAY_SIZE(url); + IXMLDOMDocument *pdoc; + HRESULT hr = UrlCreateFromPathW(xmlpath, url, &cch, 0), hrCom; + if (FAILED(hr)) + return hr; + + hrCom = CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER, + &IID_IXMLDOMDocument, (void**)&pdoc); + if (SUCCEEDED(hr)) + { + VARIANT_BOOL succ = VARIANT_FALSE; + IXMLDOMElement *pdocelm; + BSTR bsurl = SysAllocString(url); + VARIANT v; + V_VT(&v) = VT_BSTR; + V_BSTR(&v) = bsurl; + if (!bsurl || (hr = IXMLDOMDocument_load(pdoc, v, &succ)) > 0 || (SUCCEEDED(hr) && !succ)) + { + hr = E_FAIL; + } + if (SUCCEEDED(hr) && SUCCEEDED(hr = IXMLDOMDocument_get_documentElement(pdoc, &pdocelm))) + { + BSTR tagName = NULL; + if (SUCCEEDED(hr = IXMLDOMElement_get_tagName(pdocelm, &tagName))) + { + if (lstrcmpiW(tagName, L"package") == 0) + { + // FIXME: Accept job id as a function parameter and find the job here + IXMLDOMElement *p; + if (SUCCEEDED(hr = xmldomelem_getelembytagasdomelem(pdocelm, L"job", 0, &p))) + { + IUnknown_Release(pdocelm); + pdocelm = p; + } + } + else if (lstrcmpiW(tagName, L"job") != 0) + { + hr = 0x800400C0ul; + } + SysFreeString(tagName); + } + if (SUCCEEDED(hr)) + { + // FIXME: Only support CDATA blocks if the xml tag is present? + hr = run_wsfjob(pdocelm); + } + IUnknown_Release(pdocelm); + } + VariantClear(&v); + IUnknown_Release(pdoc); + } + free_globals(); + if (SUCCEEDED(hrCom)) + CoUninitialize(); + return hr; +} +#endif + static BOOL set_host_properties(const WCHAR *prop) { static const WCHAR nologoW[] = {'n','o','l','o','g','o',0}; @@ -453,6 +822,11 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm return 1; ext = wcsrchr(filepart, '.'); +#ifdef __REACTOS__ + if (ext && !lstrcmpiW(ext, L".wsf")) { + return run_wsf(scriptFullName); + } +#endif if(!ext || !get_engine_clsid(ext, &clsid)) { WINE_FIXME("Could not find engine for %s\n", wine_dbgstr_w(ext)); return 1; @@ -477,6 +851,10 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR cmdline, int cm IActiveScript_Release(script); IActiveScriptParse_Release(parser); +#ifdef __REACTOS__ + free_globals(); +#endif + CoUninitialize(); return 0; diff --git a/boot/bootdata/hivecls.inf b/boot/bootdata/hivecls.inf index 19861ab539b..cd1eedbb2d4 100644 --- a/boot/bootdata/hivecls.inf +++ b/boot/bootdata/hivecls.inf @@ -555,6 +555,16 @@ HKCR,"AVIFile","",0x00000000,%AVIFILE% HKCR,"AVIFile\DefaultIcon","",0x00020000,"%SystemRoot%\system32\shell32.dll,-224" HKCR,"AVIFile\shell\open\command","",0x00020000,"%SystemRoot%\system32\mplay32.exe ""%1""" +; WSH +HKCR,".wsf","",0x00000000,"WSFFile" +HKCR,".wsf","PerceivedType",0x00000000,"text" +HKCR,"WSFFile","",0x00000000,%WSFFILE% +HKCR,"WSFFile\DefaultIcon","",0x00020000,"%SystemRoot%\system32\shell32.dll,-153" +HKCR,"WSFFile\shell\Open\command","",0x00020000,"""%SystemRoot%\system32\WScript.exe"" ""%1"" %*" +HKCR,"WSFFile\shell\Open2","",0x00000000,"Open &with Command Prompt" +HKCR,"WSFFile\shell\Open2\command","",0x00020000,"""%SystemRoot%\system32\CScript.exe"" ""%1"" %*" + + HKCR,"CLSID",,0x00000012 ; For Shell32.dll @@ -791,6 +801,7 @@ AVIFILE="Video Clip" CSSFILE="Cascading Style Sheet" SCFFILE="ReactOS Explorer Command" WMZFILE="Compressed Enhanced Metafile" +WSFFILE="Windows Script File" ;; For .reg files, right-click menu MERGE="Merge" diff --git a/modules/rostests/apitests/CMakeLists.txt b/modules/rostests/apitests/CMakeLists.txt index 7302666c064..18dfedb9538 100644 --- a/modules/rostests/apitests/CMakeLists.txt +++ b/modules/rostests/apitests/CMakeLists.txt @@ -66,4 +66,5 @@ add_subdirectory(winprint) add_subdirectory(winspool) add_subdirectory(wlanapi) add_subdirectory(ws2_32) +add_subdirectory(wscript) add_subdirectory(zipfldr) diff --git a/modules/rostests/apitests/wscript/CMakeLists.txt b/modules/rostests/apitests/wscript/CMakeLists.txt new file mode 100644 index 00000000000..00b7c1b379e --- /dev/null +++ b/modules/rostests/apitests/wscript/CMakeLists.txt @@ -0,0 +1,10 @@ + +list(APPEND SOURCE + wsf.c + testlist.c) + +add_executable(wscript_apitest ${SOURCE}) +target_link_libraries(wscript_apitest wine ${PSEH_LIB}) +set_module_type(wscript_apitest win32cui) +add_importlibs(wscript_apitest shlwapi msvcrt user32 kernel32) +add_rostests_file(TARGET wscript_apitest) diff --git a/modules/rostests/apitests/wscript/testlist.c b/modules/rostests/apitests/wscript/testlist.c new file mode 100644 index 00000000000..9642ed87485 --- /dev/null +++ b/modules/rostests/apitests/wscript/testlist.c @@ -0,0 +1,10 @@ +#define STANDALONE +#include + +extern void func_wsf(void); + +const struct test winetest_testlist[] = +{ + { "wsf", func_wsf }, + { 0, 0 } +}; diff --git a/modules/rostests/apitests/wscript/wsf.c b/modules/rostests/apitests/wscript/wsf.c new file mode 100644 index 00000000000..0aa3b78eaab --- /dev/null +++ b/modules/rostests/apitests/wscript/wsf.c @@ -0,0 +1,170 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: Tests for wscript.exe + * COPYRIGHT: Whindmar Saksit (whindsaks@proton.me) + */ + +#include +#include +#include +#include + +#define MYGUID "{898AC78E-BFC7-41FF-937D-EDD01E666707}" + +static DWORD getregdw(HKEY hKey, LPCSTR sub, LPCSTR name, DWORD *out, DWORD defval) +{ + LRESULT e; + DWORD size = sizeof(*out); + *out = 0; + e = SHGetValueA(hKey, sub, name, NULL, out, &size); + if (e) + *out = defval; + return e; +} + +static BOOL makestringfile(LPWSTR path, SIZE_T cchpath, LPCSTR ext, LPCSTR string, const BYTE *map) +{ + UINT cch = GetTempPathW(cchpath, path); + UINT16 i = 0; + if (!cch || cch > cchpath) + return FALSE; + while (++i) + { + HANDLE hFile; + if (_snwprintf(path + cch, cchpath - cch, L"~%u.%hs", i, ext ? ext : "tmp") >= cchpath - cch) + return FALSE; + hFile = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_NEW, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + BOOL succ = TRUE; + for (; *string && succ; ++string) + { + BYTE ch = *string; + DWORD j; + for (j = 0; map && map[j + 0]; j += 2) + { + if (ch == map[j + 0]) + ch = map[j + 1]; + } + succ = WriteFile(hFile, &ch, 1, &j, NULL); + } + CloseHandle(hFile); + return succ; + } + } + return FALSE; +} + +static DWORD runscriptfile(LPCWSTR path, LPCWSTR engine) +{ + STARTUPINFOW si; + PROCESS_INFORMATION pi; + LPCWSTR exe = engine ? engine : L"wscript.exe"; + WCHAR cmd[MAX_PATH * 2]; + + if (_snwprintf(cmd, _countof(cmd), L"\"%s\" //nologo \"%s\"", exe, path) >= _countof(cmd)) + return ERROR_BUFFER_OVERFLOW; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + if (CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + DWORD code = ERROR_INTERNAL_ERROR; + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return code; + } + return GetLastError(); +} + +static DWORD runscript(LPCSTR ext, LPCSTR script, const BYTE *map) +{ + DWORD code; + WCHAR file[MAX_PATH]; + if (!makestringfile(file, _countof(file), ext, script, map)) + { + skip("Unable to create script\n"); + return ERROR_FILE_NOT_FOUND; + } + code = runscriptfile(file, NULL); + DeleteFileW(file); + return code; +} + +static void test_defaultscriptisjs(void) +{ + LPCSTR script = "" + "" + "" + ""; + + ok(runscript("wsf", script, NULL) == 42, "Script failed\n"); +} + +static void test_simplevb(void) +{ + LPCSTR script = "" + "" + "" + ""; + + ok(runscript("wsf", script, NULL) == 42, "Script failed\n"); +} + +static void test_defpackagejob(void) +{ + LPCSTR script = "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ok(runscript("wsf", script, NULL) == 42, "Script failed\n"); +} + +static void test_objecttag(void) +{ + DWORD dw; + static const BYTE map[] = { '#', '\"', '$', '\\', 0, 0 }; + LPCSTR script = "" + "" + "" + "" + "" /* Placing the object tag after the script just for fun */ + ""; + + ok(runscript("wsf", script, map) == 0, "Script failed\n"); + + getregdw(HKEY_CURRENT_USER, "Software", MYGUID, &dw, 0); + ok(dw == 42, "Value does not match\n"); + SHDeleteValueA(HKEY_CURRENT_USER, "Software", MYGUID); +} + +START_TEST(wsf) +{ + test_defaultscriptisjs(); + test_simplevb(); + test_defpackagejob(); + test_objecttag(); +}