diff --git a/base/applications/kbswitch/kbswitch.c b/base/applications/kbswitch/kbswitch.c index 287eb532b97..c8411c4b99b 100644 --- a/base/applications/kbswitch/kbswitch.c +++ b/base/applications/kbswitch/kbswitch.c @@ -8,6 +8,7 @@ */ #include "kbswitch.h" #include +#include #include #include #include diff --git a/base/applications/rapps/loaddlg.cpp b/base/applications/rapps/loaddlg.cpp index fe74da7f2dd..60452453cdf 100644 --- a/base/applications/rapps/loaddlg.cpp +++ b/base/applications/rapps/loaddlg.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include #undef SubclassWindow diff --git a/dll/win32/browseui/shellfind/CSearchBar.cpp b/dll/win32/browseui/shellfind/CSearchBar.cpp index 732d650faec..8fbd125f2c8 100644 --- a/dll/win32/browseui/shellfind/CSearchBar.cpp +++ b/dll/win32/browseui/shellfind/CSearchBar.cpp @@ -480,7 +480,7 @@ LOCATIONITEM* CSearchBar::GetAddressEditBoxLocations(WCHAR *szPath) DWORD dwAttributes = GetFileAttributesW(pszPath); if (dwAttributes != INVALID_FILE_ATTRIBUTES && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) && - PathIsAbsolute(pszPath)) + FindFiles_PathIsAbsolute(pszPath)) { return BuildLocationList(&pszPath, 1); } diff --git a/dll/win32/browseui/shellfind/shellfind.h b/dll/win32/browseui/shellfind/shellfind.h index 1f42f99d7c1..d9b2991b9f8 100644 --- a/dll/win32/browseui/shellfind/shellfind.h +++ b/dll/win32/browseui/shellfind/shellfind.h @@ -79,7 +79,7 @@ static inline BOOL PathIsOnUnc(PCWSTR Path) return PathIsUNCW(Path); // FIXME: Verify the path starts with <\\Server\Share>[\] } -static inline bool PathIsAbsolute(PCWSTR Path) +static inline bool FindFiles_PathIsAbsolute(PCWSTR Path) { // Note: PathIsRelativeW is too forgiving return PathIsOnDrive(Path) || PathIsOnUnc(Path); diff --git a/dll/win32/shell32/CMakeLists.txt b/dll/win32/shell32/CMakeLists.txt index 98c80a32fb5..c02f60eedfb 100644 --- a/dll/win32/shell32/CMakeLists.txt +++ b/dll/win32/shell32/CMakeLists.txt @@ -1,5 +1,6 @@ PROJECT(SHELL) +add_subdirectory(evalcmd) add_subdirectory(shelldesktop) add_subdirectory(shellmenu) add_subdirectory(shellrecyclebin) @@ -131,6 +132,9 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options(shell32 PRIVATE "$<$:-Wno-overloaded-virtual>") endif() target_link_libraries(shell32 shellmenu shelldesktop wine uuid recyclebin cpprt atl_classes oldnames) +if(DLL_EXPORT_VERSION GREATER_EQUAL 0x600) + target_link_libraries(shell32 evalcmd) +endif() add_delay_importlibs(shell32 powrprof shdocvw devmgr winspool.drv winmm mpr uxtheme ole32 oleaut32 userenv browseui version fmifs netapi32 secur32) add_importlibs(shell32 advapi32 gdi32 user32 comctl32 comdlg32 shlwapi shcore msvcrt kernel32 ntdll) add_dependencies(shell32 stdole2) # shell32_shldisp.tlb needs stdole2.tlb diff --git a/dll/win32/shell32/evalcmd/CMakeLists.txt b/dll/win32/shell32/evalcmd/CMakeLists.txt new file mode 100644 index 00000000000..cd849596fd5 --- /dev/null +++ b/dll/win32/shell32/evalcmd/CMakeLists.txt @@ -0,0 +1,11 @@ + +add_library(evalcmd STATIC evalcmd.cpp) +add_dependencies(evalcmd psdk) +target_link_libraries(evalcmd PRIVATE pathcch_static) +target_compile_definitions(evalcmd PRIVATE STATIC_PATHCCH) + +if(DLL_EXPORT_VERSION GREATER_EQUAL 0x600) + target_compile_definitions(evalcmd PRIVATE _SHELL32_) +else() + target_compile_definitions(evalcmd PRIVATE _SHLWAPI_) +endif() diff --git a/dll/win32/shell32/evalcmd/evalcmd.cpp b/dll/win32/shell32/evalcmd/evalcmd.cpp new file mode 100644 index 00000000000..a52c08e3429 --- /dev/null +++ b/dll/win32/shell32/evalcmd/evalcmd.cpp @@ -0,0 +1,302 @@ +/* + * PROJECT: ReactOS Shell + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: SHEvaluateSystemCommandTemplate + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PATH_VALID_CHARS \ + (PATH_CHAR_CLASS_DOT | PATH_CHAR_CLASS_SEMICOLON | PATH_CHAR_CLASS_COMMA | \ + PATH_CHAR_CLASS_SPACE | PATH_CHAR_CLASS_OTHER_VALID) + +/** + * Get the position of the arguments of a command line string as CreateProcess does. + */ +static PCWSTR _PathGetArgsLikeCreateProcess(PCWSTR lpString) +{ + PCWSTR pch; + if (*lpString == L'"') + { + pch = StrChrW(lpString + 1, L'"'); + if (pch) + { + ++pch; + if (*pch == L' ') + ++pch; + return pch; + } + } + else + { + pch = StrChrW(lpString, L' '); + if (pch) + return pch + 1; + } + return &lpString[lstrlenW(lpString)]; +} + +static HRESULT +_PathCopyExeAndTrimWhiteSpaces(PWSTR pszBuff, size_t cchBuff, PCWSTR pszSrc, size_t cchSrc) +{ + HRESULT hr = StringCchCopyNW(pszBuff, cchBuff, pszSrc, cchSrc); + if (SUCCEEDED(hr)) + StrTrimW(pszBuff, L" \t"); + return hr; +} + +/** + * @brief "Program Files" contains space. It needs special handling. This function will detect it. + */ +static BOOL _PathMatchesSuspicious(PCWSTR lpString) +{ + WCHAR pszPath[MAX_PATH]; + SHGetFolderPathW(NULL, CSIDL_PROGRAM_FILES, NULL, 0, pszPath); + INT cch = lstrlenW(pszPath); + return StrCmpNIW(lpString, pszPath, cch) == 0; +} + +// This function attempts to find where the "arguments" portion of a command-line path string +static PCWSTR _PathGuessNextBestArgs(PCWSTR pszPath) +{ + PCWSTR pSpaceStart = NULL; + BOOL bValid = TRUE; + + for (; *pszPath && bValid; ++pszPath) + { + switch (*pszPath) + { + case L' ': + if (!pSpaceStart) + pSpaceStart = pszPath; + break; + + case L'"': + case L'%': + bValid = FALSE; + break; + + case L'\\': + bValid = !PathIsUNCW(pszPath); + if (bValid) + pSpaceStart = NULL; + break; + + default: + bValid = PathIsValidCharW(*pszPath, PATH_VALID_CHARS); + break; + } + } + + if (pSpaceStart) + { + while (*pSpaceStart == L' ') + ++pSpaceStart; + return pSpaceStart; + } + + return bValid ? pszPath : NULL; +} + +static VOID _MakeAppPathKey(PCWSTR pszPath, PWSTR pszDest, UINT cchDest) +{ + HRESULT hr = PathCchCombineEx(pszDest, cchDest, + L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths", + pszPath, PATHCCH_NONE); + if (SUCCEEDED(hr)) + PathCchAddExtension(pszDest, cchDest, L".exe"); +} + +static BOOL _GetAppPath(PCWSTR pszPath, PWSTR pszValue, DWORD cchValue) +{ + WCHAR szSubKey[MAX_PATH]; + _MakeAppPathKey(pszPath, szSubKey, _countof(szSubKey)); + DWORD cbData = cchValue * sizeof(WCHAR); + LSTATUS error = SHGetValueW(HKEY_LOCAL_MACHINE, szSubKey, NULL, NULL, pszValue, &cbData); + return error == ERROR_SUCCESS; +} + +static HRESULT _PathExeExists(_In_ PCWSTR pszPath) +{ + WCHAR szPath[MAX_PATH]; + StringCchCopyW(szPath, _countof(szPath), pszPath); + + DWORD dwWhich = WHICH_PIF | WHICH_COM | WHICH_EXE | WHICH_BAT | WHICH_CMD | WHICH_OPTIONAL; + DWORD attrs; + if (!PathFileExistsDefExtAndAttributesW(szPath, dwWhich, &attrs) || + (attrs & FILE_ATTRIBUTE_DIRECTORY)) + { + return CO_E_APPNOTFOUND; + } + return S_OK; +} + +static HRESULT +_PathFindInFolder(_In_ INT csidl, _In_ PCWSTR pszSrc, _Out_ PWSTR pszPath, _In_ UINT cchPath) +{ + WCHAR szDir[MAX_PATH]; + HRESULT hr = SHGetFolderPathW(0, csidl, 0, 0, szDir); + if (FAILED(hr)) + return hr; + + hr = PathCchCombineEx(pszPath, cchPath, szDir, pszSrc, PATHCCH_NONE); + if (FAILED(hr)) + return hr; + + return _PathExeExists(pszPath); +} + +static HRESULT _PathFindInSystem(_Inout_ PWSTR pszPath, _In_ UINT cchPath) +{ + WCHAR szPath[MAX_PATH]; + HRESULT hr = _PathFindInFolder(CSIDL_SYSTEM, pszPath, szPath, _countof(szPath)); + if (FAILED(hr)) + hr = _PathFindInFolder(CSIDL_WINDOWS, pszPath, szPath, _countof(szPath)); + if (FAILED(hr)) + return hr; + return StringCchCopyW(pszPath, cchPath, szPath); +} + +/************************************************************************* + * SHEvaluateSystemCommandTemplate [SHELL32.@] (Vista+) + * SHEvaluateSystemCommandTemplate [SHLWAPI.552] (XP SP1 and SP2) + * + * https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shevaluatesystemcommandtemplate + * https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.16299.0/um/shellapi.h#L525 + */ +EXTERN_C +HRESULT WINAPI +SHEvaluateSystemCommandTemplate( + _In_ PCWSTR pszCmdTemplate, + _Outptr_ PWSTR *ppszApplication, + _Outptr_opt_ PWSTR *ppszCommandLine, + _Outptr_opt_ PWSTR *ppszParameters) +{ + HRESULT hr; + BOOL bQuoted; + WCHAR szExe[MAX_PATH], szProgram[MAX_PATH]; + + PCWSTR pszArgs = _PathGetArgsLikeCreateProcess(pszCmdTemplate); + UINT cchArgs = (UINT)(pszArgs - pszCmdTemplate); + hr = _PathCopyExeAndTrimWhiteSpaces(szExe, _countof(szExe), pszCmdTemplate, cchArgs); + if (FAILED(hr)) + goto Exit; + + // Unquote if necessary + bQuoted = (szExe[0] == L'"'); + if (bQuoted) + PathUnquoteSpacesW(szExe); + + hr = StringCchCopyW(szProgram, _countof(szProgram), szExe); + assert(SUCCEEDED(hr)); + + if (PathIsAbsolute(szExe)) + { + if (bQuoted) + { + hr = _PathExeExists(szExe); + } + else // Not quoted + { + if (_PathMatchesSuspicious(szExe)) // ProgramFiles-likely? + hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + else + hr = _PathExeExists(szExe); + } + + // Detect where the full executable path ends and the arguments start + while (FAILED(hr)) + { + if (bQuoted || !*pszArgs) + break; + + pszArgs = _PathGuessNextBestArgs(pszArgs); + if (!pszArgs) + break; + + cchArgs = (UINT)(pszArgs - pszCmdTemplate); + hr = _PathCopyExeAndTrimWhiteSpaces(szExe, _countof(szExe), pszCmdTemplate, cchArgs); + if (FAILED(hr)) + break; + + hr = _PathExeExists(szExe); + } + } + else + { + if (!PathIsFileSpecW(szExe)) + { + hr = E_ACCESSDENIED; + goto Exit; + } + + if (_GetAppPath(szExe, szExe, _countof(szExe))) + { + hr = StringCchCopyW(szProgram, _countof(szProgram), PathFindFileNameW(szExe)); + } + else if (SHWindowsPolicyEx(POLID_UsePathEnvVarForCommandTemplates, FALSE)) + { + hr = PathFindOnPathExW(szExe, NULL, PATH_VALID_CHARS) ? S_OK : CO_E_APPNOTFOUND; + } + else + { + hr = _PathFindInSystem(szExe, _countof(szExe)); + } + } + +Exit: + *ppszApplication = NULL; + if (ppszCommandLine) + *ppszCommandLine = NULL; + if (ppszParameters) + *ppszParameters = NULL; + + if (!pszArgs) + pszArgs = L""; + + // Create output strings + if (SUCCEEDED(hr)) + hr = SHStrDupW(szExe, ppszApplication); + + if (SUCCEEDED(hr) && ppszCommandLine) + { + size_t cch = lstrlenW(szProgram) + lstrlenW(pszArgs) + 4; // 4 for '"', '"', ' ', NUL + hr = SHCoAlloc(cch * sizeof(WCHAR), (PVOID*)ppszCommandLine); + if (SUCCEEDED(hr)) + hr = StringCchPrintfW(*ppszCommandLine, cch, L"\"%s\" %s", szProgram, pszArgs); + } + + if (SUCCEEDED(hr) && ppszParameters) + hr = SHStrDupW(pszArgs, ppszParameters); + + if (FAILED(hr)) + { + // Clean up + if (*ppszApplication) + { + CoTaskMemFree(*ppszApplication); + *ppszApplication = NULL; + } + if (ppszCommandLine && *ppszCommandLine) + { + CoTaskMemFree(*ppszCommandLine); + *ppszCommandLine = NULL; + } + if (ppszParameters && *ppszParameters) + { + CoTaskMemFree(*ppszParameters); + *ppszParameters = NULL; + } + } + + return hr; +} diff --git a/dll/win32/shell32/shell32.spec b/dll/win32/shell32/shell32.spec index b8f8457b9a4..100557c4ed8 100644 --- a/dll/win32/shell32/shell32.spec +++ b/dll/win32/shell32/shell32.spec @@ -505,7 +505,7 @@ @ stdcall SHEmptyRecycleBinW(long wstr long) # 2k3:285, Vista:344 @ stub SHEnableServiceObject # 2k3:286, Vista:345 @ stdcall SHEnumerateUnreadMailAccountsW(ptr long ptr long) # 2k3:287, Vista:346 -@ stub -version=0x600+ SHEvaluateSystemCommandTemplate # Vista:347 +@ stdcall -version=0x600+ SHEvaluateSystemCommandTemplate(wstr ptr ptr ptr) # Vista:347 @ stdcall SHExtractIconsW(wstr long long long ptr ptr long long) user32.PrivateExtractIconsW # 2k3:288, Vista:348 @ stdcall SHFileOperation(ptr) SHFileOperationA # 2k3:289, Vista:349 @ stdcall SHFileOperationA(ptr) # 2k3:290, Vista:350 diff --git a/dll/win32/shell32/wine/shpolicy.c b/dll/win32/shell32/wine/shpolicy.c index 06388e3cd50..c9b74c225ab 100644 --- a/dll/win32/shell32/wine/shpolicy.c +++ b/dll/win32/shell32/wine/shpolicy.c @@ -39,8 +39,8 @@ #include #include #include -#include #include +#include #include #include "shell32_main.h" diff --git a/dll/win32/shlwapi/CMakeLists.txt b/dll/win32/shlwapi/CMakeLists.txt index 618f64a36c1..42700dc0ae0 100644 --- a/dll/win32/shlwapi/CMakeLists.txt +++ b/dll/win32/shlwapi/CMakeLists.txt @@ -69,6 +69,9 @@ add_dependencies(shlwapi_autocomp psdk) set_module_type(shlwapi win32dll UNICODE) target_link_libraries(shlwapi uuid wine crtheap cppstl) +if(DLL_EXPORT_VERSION LESS 0x600) + target_link_libraries(shlwapi evalcmd) +endif() add_delay_importlibs(shlwapi userenv oleaut32 ole32 comctl32 comdlg32 mpr mlang urlmon shell32 winmm version) add_importlibs(shlwapi shcore user32 gdi32 advapi32 wininet msvcrt kernelbase kernel32 kernel32_vista ntdll) #add_pch(shlwapi precomp.h "${PCH_SKIP_SOURCE}") diff --git a/dll/win32/shlwapi/shlwapi.spec b/dll/win32/shlwapi/shlwapi.spec index 0c802433574..eca38f3164c 100644 --- a/dll/win32/shlwapi/shlwapi.spec +++ b/dll/win32/shlwapi/shlwapi.spec @@ -545,7 +545,7 @@ 549 stdcall -noname SHCoCreateInstanceAC(ptr ptr long ptr ptr) 550 stub -noname GetTemplateInfoFromHandle 551 stdcall -noname IShellFolder_CompareIDs(ptr ptr ptr ptr) -552 stdcall -stub -noname -version=0x501-0x502 SHEvaluateSystemCommandTemplate(wstr ptr ptr ptr) +552 stdcall -noname -version=0x501-0x502 SHEvaluateSystemCommandTemplate(wstr ptr ptr ptr) 553 stdcall IsInternetESCEnabled() 554 stdcall -noname -stub SHGetAllAccessSA() diff --git a/dll/win32/shlwapi/zonechk.c b/dll/win32/shlwapi/zonechk.c index 098e127195f..b568172ad76 100644 --- a/dll/win32/shlwapi/zonechk.c +++ b/dll/win32/shlwapi/zonechk.c @@ -9,6 +9,7 @@ #include #include #define NO_SHLWAPI_REG +#include #include #include diff --git a/modules/rostests/apitests/shell32/CMakeLists.txt b/modules/rostests/apitests/shell32/CMakeLists.txt index 2686f23eebb..596374dafa8 100644 --- a/modules/rostests/apitests/shell32/CMakeLists.txt +++ b/modules/rostests/apitests/shell32/CMakeLists.txt @@ -32,6 +32,7 @@ list(APPEND SOURCE SHCreateDataObject.cpp SHCreateFileDataObject.cpp SHCreateFileExtractIconW.cpp + SHEvaluateSystemCommandTemplate.cpp SHGetComputerDisplayNameW.cpp SHGetUnreadMailCountW.cpp SHIsBadInterfacePtr.cpp diff --git a/modules/rostests/apitests/shell32/SHEvaluateSystemCommandTemplate.cpp b/modules/rostests/apitests/shell32/SHEvaluateSystemCommandTemplate.cpp new file mode 100644 index 00000000000..0b995e3ff14 --- /dev/null +++ b/modules/rostests/apitests/shell32/SHEvaluateSystemCommandTemplate.cpp @@ -0,0 +1,167 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Tests for SHEvaluateSystemCommandTemplate + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#include "shelltest.h" +#include +#include + +typedef HRESULT (WINAPI *FN_SHEvaluateSystemCommandTemplate)(PCWSTR, PWSTR*, PWSTR*, PWSTR*); +static FN_SHEvaluateSystemCommandTemplate g_fnSHEvaluateSystemCommandTemplate = NULL; + +#define ok_wstri(x, y) \ + ok(lstrcmpiW(x, y) == 0, "Wrong string. Expected %s, got %s\n", wine_dbgstr_w(y), wine_dbgstr_w(x)) + +static BOOL TEST_Init(void) +{ + HINSTANCE hShell32 = GetModuleHandleA("shell32"); + g_fnSHEvaluateSystemCommandTemplate = (FN_SHEvaluateSystemCommandTemplate) + GetProcAddress(hShell32, "SHEvaluateSystemCommandTemplate"); + if (g_fnSHEvaluateSystemCommandTemplate) + return TRUE; + + HINSTANCE hSHLWAPI = GetModuleHandleA("shlwapi"); + g_fnSHEvaluateSystemCommandTemplate = (FN_SHEvaluateSystemCommandTemplate) + GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(552)); + if (g_fnSHEvaluateSystemCommandTemplate) + { + trace("shlwapi has SHEvaluateSystemCommandTemplate\n"); + return TRUE; + } + + skip("SHEvaluateSystemCommandTemplate not found\n"); + return FALSE; +} + +static void TEST_notepad(void) +{ + HRESULT hr; + PWSTR app, cmdline, params; + WCHAR szPath[MAX_PATH], szQuoted[MAX_PATH], szNotepad[MAX_PATH]; + WCHAR szAnswer1[MAX_PATH], szAnswer2[MAX_PATH], szAnswer3[MAX_PATH]; + + GetSystemDirectoryW(szNotepad, _countof(szNotepad)); + StringCchCatW(szNotepad, _countof(szNotepad), L"\\notepad.exe"); + StringCchPrintfW(szAnswer1, _countof(szAnswer1), L"\"%s\"", szNotepad); + StringCchPrintfW(szAnswer2, _countof(szAnswer2), L"\"%s\" ", szNotepad); + StringCchPrintfW(szAnswer3, _countof(szAnswer3), L"\"%s\" /A /P", szNotepad); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"notepad.exe", &app, NULL, NULL); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + CoTaskMemFree(app); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"notepad.exe", &app, &cmdline, NULL); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, L"\"notepad.exe\" "); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"notepad.exe", &app, &cmdline, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, L"\"notepad.exe\" "); + ok_wstri(params, L""); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"notepad.exe", &app, NULL, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(params, L""); + CoTaskMemFree(app); + CoTaskMemFree(params); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"system32\\notepad.exe", &app, &cmdline, ¶ms); + ok_hr(hr, E_ACCESSDENIED); + ok(app == NULL, "app was %s\n", wine_dbgstr_w(app)); + ok(cmdline == NULL, "cmdline was %s\n", wine_dbgstr_w(cmdline)); + ok(params == NULL, "params was %s\n", wine_dbgstr_w(params)); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"notepad.exe /A /P", &app, &cmdline, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, L"\"notepad.exe\" /A /P"); + ok_wstri(params, L"/A /P"); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + StringCchCopyW(szPath, _countof(szPath), szNotepad); + hr = g_fnSHEvaluateSystemCommandTemplate(szPath, &app, NULL, NULL); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + CoTaskMemFree(app); + + StringCchCopyW(szPath, _countof(szPath), szNotepad); + hr = g_fnSHEvaluateSystemCommandTemplate(szPath, &app, NULL, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(params, L""); + CoTaskMemFree(app); + CoTaskMemFree(params); + + StringCchCopyW(szPath, _countof(szPath), szNotepad); + hr = g_fnSHEvaluateSystemCommandTemplate(szPath, &app, &cmdline, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, szAnswer2); + ok_wstri(params, L""); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + StringCchPrintfW(szQuoted, _countof(szQuoted), L"\"%s\"", szNotepad); + hr = g_fnSHEvaluateSystemCommandTemplate(szQuoted, &app, &cmdline, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, szAnswer2); + ok_wstri(params, L""); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + StringCchPrintfW(szQuoted, _countof(szQuoted), L"\"%s\" /A /P", szNotepad); + hr = g_fnSHEvaluateSystemCommandTemplate(szQuoted, &app, &cmdline, ¶ms); + ok_hr(hr, S_OK); + ok_wstri(app, szNotepad); + ok_wstri(cmdline, szAnswer3); + ok_wstri(params, L"/A /P"); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"_invalid_\\_path_", &app, &cmdline, ¶ms); + ok_hr(hr, E_ACCESSDENIED); + ok(app == NULL, "app was %s\n", wine_dbgstr_w(app)); + ok(cmdline == NULL, "cmdline was %s\n", wine_dbgstr_w(cmdline)); + ok(params == NULL, "params was %s\n", wine_dbgstr_w(params)); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); + + hr = g_fnSHEvaluateSystemCommandTemplate(L"%SystemRoot%\\system32\\notepad.exe", &app, &cmdline, ¶ms); + ok_hr(hr, E_ACCESSDENIED); + ok(app == NULL, "app was %s\n", wine_dbgstr_w(app)); + ok(cmdline == NULL, "cmdline was %s\n", wine_dbgstr_w(cmdline)); + ok(params == NULL, "params was %s\n", wine_dbgstr_w(params)); + CoTaskMemFree(app); + CoTaskMemFree(cmdline); + CoTaskMemFree(params); +} + +START_TEST(SHEvaluateSystemCommandTemplate) +{ + if (!TEST_Init()) + return; + + TEST_notepad(); +} diff --git a/modules/rostests/apitests/shell32/testlist.c b/modules/rostests/apitests/shell32/testlist.c index a74410e1c25..479dd474ca3 100644 --- a/modules/rostests/apitests/shell32/testlist.c +++ b/modules/rostests/apitests/shell32/testlist.c @@ -37,6 +37,7 @@ extern void func_SHCreateFileDataObject(void); extern void func_SHCreateFileExtractIconW(void); extern void func_SHDefExtractIcon(void); extern void func_SHEnumerateUnreadMailAccountsW(void); +extern void func_SHEvaluateSystemCommandTemplate(void); extern void func_She(void); extern void func_ShellExec_RunDLL(void); extern void func_ShellExecCmdLine(void); @@ -95,6 +96,7 @@ const struct test winetest_testlist[] = { "SHCreateFileExtractIconW", func_SHCreateFileExtractIconW }, { "SHDefExtractIcon", func_SHDefExtractIcon }, { "SHEnumerateUnreadMailAccountsW", func_SHEnumerateUnreadMailAccountsW }, + { "SHEvaluateSystemCommandTemplate", func_SHEvaluateSystemCommandTemplate }, { "She", func_She }, //{ "ShellExec_RunDLL", func_ShellExec_RunDLL }, Broke on Windows //{ "ShellExecCmdLine", func_ShellExecCmdLine }, Broke on Windows diff --git a/sdk/include/psdk/shellapi.h b/sdk/include/psdk/shellapi.h index 331da984854..b92cb7690a9 100644 --- a/sdk/include/psdk/shellapi.h +++ b/sdk/include/psdk/shellapi.h @@ -572,6 +572,14 @@ ShellExecuteW( BOOL WINAPI ShellExecuteExA(_Inout_ LPSHELLEXECUTEINFOA); BOOL WINAPI ShellExecuteExW(_Inout_ LPSHELLEXECUTEINFOW); + +HRESULT WINAPI +SHEvaluateSystemCommandTemplate( + _In_ PCWSTR pszCmdTemplate, + _Outptr_ PWSTR *ppszApplication, + _Outptr_opt_ PWSTR *ppszCommandLine, + _Outptr_opt_ PWSTR *ppszParameters); + int WINAPI SHFileOperationA(_Inout_ LPSHFILEOPSTRUCTA); int WINAPI SHFileOperationW(_Inout_ LPSHFILEOPSTRUCTW); void WINAPI SHFreeNameMappings(_In_opt_ HANDLE); diff --git a/sdk/include/reactos/shlwapi_undoc.h b/sdk/include/reactos/shlwapi_undoc.h index 02c49bd578b..63b8743a3eb 100644 --- a/sdk/include/reactos/shlwapi_undoc.h +++ b/sdk/include/reactos/shlwapi_undoc.h @@ -10,6 +10,10 @@ #include // For REGSAM +#if !defined(_INC_SHLWAPI) && !defined(__WINE_SHLWAPI_H) +#error Please #include first +#endif + #ifdef __cplusplus extern "C" { #endif @@ -536,6 +540,27 @@ SHWindowsPolicyGetValue( #define E_DATATYPE_MISMATCH HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH) +static inline BOOL +PathIsAbsolute(_In_ PCWSTR pszPath) +{ + return (PathGetDriveNumberW(pszPath) != -1 && pszPath[2] == L'\\') || PathIsUNCW(pszPath); +} + +static inline HRESULT +SHCoAlloc(_In_ SIZE_T cb, _Outptr_ PVOID* ppData) +{ + *ppData = CoTaskMemAlloc(cb); + return *ppData ? S_OK : E_OUTOFMEMORY; +} + +static inline DWORD +SHWindowsPolicyEx(_In_ REFGUID rpolid, _In_ DWORD dwDefaultValue) +{ + DWORD dwData, cbData = sizeof(dwData); + HRESULT hr = SHWindowsPolicyGetValue(rpolid, &dwData, &cbData); + return (SUCCEEDED(hr) ? dwData : dwDefaultValue); +} + /***************************************************************************** * ZoneCheck* */