[CSCRIPT][WSCRIPT][BOOTDATA] Basic .wsf support (#6140)

Support for .wsf files with a single script block
This commit is contained in:
Whindmar Saksit 2024-05-19 13:57:47 +02:00 committed by GitHub
parent 09965760e0
commit 108db8f007
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 582 additions and 0 deletions

View file

@ -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)

View file

@ -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)

View file

@ -60,6 +60,141 @@ ITypeInfo *arguments_ti;
static HRESULT query_interface(REFIID,void**);
#ifdef __REACTOS__
#include <commctrl.h>
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 <msxml2.h>
#include <shlwapi.h>
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.
<job><script language="JScript">WScript.Echo("JS");</script></job>
or
<package>
<job><script language="JScript">WScript.Echo("JS");</script></job>
</package>
or
<?xml version="1.0" ?>
<job>
<script language="JScript"><![CDATA[function JS(s) {WScript.Echo(s)}]]></script>
<script language="VBScript">JS "VB2JS"</script>
</job>
*/
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;

View file

@ -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"

View file

@ -66,4 +66,5 @@ add_subdirectory(winprint)
add_subdirectory(winspool)
add_subdirectory(wlanapi)
add_subdirectory(ws2_32)
add_subdirectory(wscript)
add_subdirectory(zipfldr)

View file

@ -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)

View file

@ -0,0 +1,10 @@
#define STANDALONE
#include <apitest.h>
extern void func_wsf(void);
const struct test winetest_testlist[] =
{
{ "wsf", func_wsf },
{ 0, 0 }
};

View file

@ -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 <apitest.h>
#include <windows.h>
#include <shlwapi.h>
#include <stdio.h>
#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 = ""
"<job>"
"<script>" /* No language attribute should default to Javascript */
"var x = 42;"
"WScript.Quit(x);"
"</script>"
"</job>";
ok(runscript("wsf", script, NULL) == 42, "Script failed\n");
}
static void test_simplevb(void)
{
LPCSTR script = ""
"<job>"
"<script language=\"VBScript\">"
"Dim x\n"
"x = 42\n"
"WScript.Quit x\n"
"</script>"
"</job>";
ok(runscript("wsf", script, NULL) == 42, "Script failed\n");
}
static void test_defpackagejob(void)
{
LPCSTR script = ""
"<package>"
"<job id=\"PickMePlease\">"
"<script language=\"VBScript\">"
"WScript.Quit 42"
"</script>"
"</job>"
"<job id=\"DontExecuteMe\">"
"<script language=\"VBScript\">"
"WScript.Quit 33"
"</script>"
"</job>"
"</package>";
ok(runscript("wsf", script, NULL) == 42, "Script failed\n");
}
static void test_objecttag(void)
{
DWORD dw;
static const BYTE map[] = { '#', '\"', '$', '\\', 0, 0 };
LPCSTR script = ""
"<job>"
"<object id=#ws1# clsid=#{72C24DD5-D70A-438B-8A42-98424B88AFB8}# />"
"<script language=#JScript#>"
"var dontcare = ws1.ExpandEnvironmentStrings(#SystemRoot#);"
"var p = #HKCU/Software/" MYGUID "#.replace(/$//g,'$$');"
"ws2.RegWrite(p, 42, #REG_DWORD#);"
"</script>"
"<object id=#ws2# progid=#WScript.Shell# />" /* Placing the object tag after the script just for fun */
"</job>";
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();
}