[SHELL32] Fix handling of multiple parameters in the 'Run' dialog (#665)
[reactos.git] / dll / win32 / shell32 / shlexec.cpp
index 555c098..55b3d05 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Copyright 1998 Marcus Meissner
  * Copyright 2002 Eric Pouech
+ * Copyright 2018 Katayama Hirofumi MZ
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  */
 
 #include "precomp.h"
+#include <undocshell.h>
 
 WINE_DEFAULT_DEBUG_CHANNEL(exec);
 
 static const WCHAR wszOpen[] = L"open";
 static const WCHAR wszExe[] = L".exe";
+static const WCHAR wszCom[] = L".com";
 
 #define SEE_MASK_CLASSALL (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY)
 
@@ -178,7 +181,7 @@ static void ParseTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used,
  *     - use rules from here http://www.autohotkey.net/~deleyd/parameters/parameters.htm
  */
 
-static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len)
+static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len, const WCHAR* lpDir)
 {
     WCHAR   xlpFile[1024];
     BOOL    done = FALSE;
@@ -265,7 +268,7 @@ static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR*
                     if (!done || (*fmt == '1'))
                     {
                         /*FIXME Is the call to SearchPathW() really needed? We already have separated out the parameter string in args. */
-                        if (SearchPathW(NULL, lpFile, wszExe, sizeof(xlpFile) / sizeof(WCHAR), xlpFile, NULL))
+                        if (SearchPathW(lpDir, lpFile, wszExe, sizeof(xlpFile) / sizeof(WCHAR), xlpFile, NULL))
                             cmd = xlpFile;
                         else
                             cmd = lpFile;
@@ -576,7 +579,15 @@ static BOOL SHELL_TryAppPathW( LPCWSTR szName, LPWSTR lpResult, WCHAR **env)
     wcscpy(buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
     wcscat(buffer, szName);
     res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
-    if (res) goto end;
+    if (res) 
+    {
+        // Add ".exe" extension, if extension does not exists
+        if (PathAddExtensionW(buffer, wszExe))
+        {
+            res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
+        }        
+        if (res) goto end;
+    }
 
     len = MAX_PATH * sizeof(WCHAR);
     res = RegQueryValueW(hkApp, NULL, lpResult, &len);
@@ -820,7 +831,7 @@ static UINT SHELL_FindExecutable(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpVerb,
         if (retval > 32)
         {
             DWORD finishedLen;
-            SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen);
+            SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen, lpPath);
             if (finishedLen > resultLen)
                 ERR("Argify buffer not large enough.. truncated\n");
             /* Remove double quotation marks and command line arguments */
@@ -1053,11 +1064,11 @@ static unsigned dde_connect(const WCHAR* key, const WCHAR* start, WCHAR* ddeexec
         }
     }
 
-    SHELL_ArgifyW(static_res, sizeof(static_res)/sizeof(WCHAR), exec, lpFile, pidl, szCommandline, &resultLen);
+    SHELL_ArgifyW(static_res, sizeof(static_res)/sizeof(WCHAR), exec, lpFile, pidl, szCommandline, &resultLen, NULL);
     if (resultLen > sizeof(static_res)/sizeof(WCHAR))
     {
         res = dynamic_res = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, resultLen * sizeof(WCHAR)));
-        SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL);
+        SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL, NULL);
     }
     else
         res = static_res;
@@ -1123,7 +1134,8 @@ static UINT_PTR execute_from_key(LPCWSTR key, LPCWSTR lpFile, WCHAR *env,
         if (cmdlen >= sizeof(cmd) / sizeof(WCHAR))
             cmdlen = sizeof(cmd) / sizeof(WCHAR) - 1;
         cmd[cmdlen] = '\0';
-        SHELL_ArgifyW(param, sizeof(param) / sizeof(WCHAR), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen);
+        SHELL_ArgifyW(param, sizeof(param) / sizeof(WCHAR), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen,
+                      (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
         if (resultLen > sizeof(param) / sizeof(WCHAR))
             ERR("Argify buffer not large enough, truncating\n");
     }
@@ -1201,6 +1213,7 @@ HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpR
 {
     UINT_PTR retval = SE_ERR_NOASSOC;
     WCHAR old_dir[1024];
+    WCHAR res[MAX_PATH];
 
     TRACE("File %s, Dir %s\n", debugstr_w(lpFile), debugstr_w(lpDirectory));
 
@@ -1214,7 +1227,9 @@ HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpR
         SetCurrentDirectoryW(lpDirectory);
     }
 
-    retval = SHELL_FindExecutable(lpDirectory, lpFile, wszOpen, lpResult, MAX_PATH, NULL, NULL, NULL, NULL);
+    retval = SHELL_FindExecutable(lpDirectory, lpFile, wszOpen, res, MAX_PATH, NULL, NULL, NULL, NULL);
+    if (retval > 32)
+        strcpyW(lpResult, res);
 
     TRACE("returning %s\n", debugstr_w(lpResult));
     if (lpDirectory)
@@ -1274,8 +1289,8 @@ static HKEY ShellExecute_GetClassKey(const SHELLEXECUTEINFOW *sei)
 static IDataObject *shellex_get_dataobj( LPSHELLEXECUTEINFOW sei )
 {
     LPCITEMIDLIST pidllast = NULL;
-    IDataObject *dataobj = NULL;
-    IShellFolder *shf = NULL;
+    CComPtr<IDataObject> dataobj;
+    CComPtr<IShellFolder> shf;
     LPITEMIDLIST pidl = NULL;
     HRESULT r;
 
@@ -1303,15 +1318,13 @@ static IDataObject *shellex_get_dataobj( LPSHELLEXECUTEINFOW sei )
 end:
     if (pidl != sei->lpIDList)
         ILFree(pidl);
-    if (shf)
-        shf->Release();
-    return dataobj;
+    return dataobj.Detach();
 }
 
 static HRESULT shellex_run_context_menu_default(IShellExtInit *obj,
         LPSHELLEXECUTEINFOW sei)
 {
-    IContextMenu *cm = NULL;
+    CComPtr<IContextMenu> cm = NULL;
     CMINVOKECOMMANDINFOEX ici;
     MENUITEMINFOW info;
     WCHAR string[0x80];
@@ -1374,13 +1387,12 @@ static HRESULT shellex_run_context_menu_default(IShellExtInit *obj,
 end:
     if (hmenu)
         DestroyMenu( hmenu );
-    if (cm)
-        cm->Release();
     return r;
 }
 
 static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei)
 {
+    // Can not use CComPtr here because of CoUninitialize at the end, before the destructors would run.
     IDataObject *dataobj = NULL;
     IObjectWithSite *ows = NULL;
     IShellExtInit *obj = NULL;
@@ -1404,6 +1416,7 @@ static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECU
     if (!dataobj)
     {
         ERR("failed to get data object\n");
+        r = E_FAIL;
         goto end;
     }
 
@@ -1502,7 +1515,8 @@ static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEIN
         TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName));
 
         wcmd[0] = '\0';
-        done = SHELL_ArgifyW(wcmd, sizeof(wcmd) / sizeof(WCHAR), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, NULL, &resultLen);
+        done = SHELL_ArgifyW(wcmd, sizeof(wcmd) / sizeof(WCHAR), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, NULL, &resultLen,
+                             (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
         if (!done && wszApplicationName[0])
         {
             strcatW(wcmd, L" ");
@@ -1568,7 +1582,8 @@ static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters
                                            sei->lpVerb,
                                            buffer, sizeof(buffer))) {
                 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen,
-                              buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen);
+                              buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen,
+                              (sei->lpDirectory && *sei->lpDirectory) ? sei->lpDirectory : NULL);
                 if (resultLen > dwApplicationNameLen)
                     ERR("Argify buffer not large enough... truncating\n");
                 appKnownSingular = FALSE;
@@ -1658,7 +1673,6 @@ static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
     DWORD error_code;
 
     error_code = GetLastError();
-
     if (retval == SE_ERR_NOASSOC)
         LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, sizeof(msg) / sizeof(WCHAR));
     else
@@ -1673,6 +1687,26 @@ static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
     MessageBoxW(hwnd, msg, NULL, MB_ICONERROR);
 }
 
+static WCHAR *expand_environment( const WCHAR *str )
+{
+    WCHAR *buf;
+    DWORD len;
+
+    len = ExpandEnvironmentStringsW(str, NULL, 0);
+    if (!len) return NULL;
+
+    buf = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+    if (!buf) return NULL;
+
+    len = ExpandEnvironmentStringsW(str, buf, len);
+    if (!len)
+    {
+        HeapFree(GetProcessHeap(), 0, buf);
+        return NULL;
+    }
+    return buf;
+}
+
 /*************************************************************************
  *    SHELL_execute [Internal]
  */
@@ -1716,17 +1750,16 @@ static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
         wszApplicationName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwApplicationNameLen * sizeof(WCHAR));
         *wszApplicationName = '\0';
     }
-    else if (*sei_tmp.lpFile == '\"')
+    else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"')
     {
-        DWORD l = strlenW(sei_tmp.lpFile + 1);
-        if(l >= dwApplicationNameLen)
-            dwApplicationNameLen = l + 1;
+        if(len-1 >= dwApplicationNameLen)
+            dwApplicationNameLen = len;
 
         wszApplicationName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwApplicationNameLen * sizeof(WCHAR));
-        memcpy(wszApplicationName, sei_tmp.lpFile + 1, (l + 1)*sizeof(WCHAR));
+        memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR));
 
-        if (wszApplicationName[l-1] == L'\"')
-            wszApplicationName[l-1] = L'\0';
+        if(len > 2)
+            wszApplicationName[len-2] = '\0';
         appKnownSingular = TRUE;
 
         TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName));
@@ -1804,6 +1837,27 @@ static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
         TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
     }
 
+    if (sei_tmp.fMask & SEE_MASK_DOENVSUBST)
+    {
+        WCHAR *tmp;
+
+        tmp = expand_environment(sei_tmp.lpFile);
+        if (!tmp)
+        {
+            return FALSE;
+        }
+        HeapFree(GetProcessHeap(), 0, wszApplicationName);
+        sei_tmp.lpFile = wszApplicationName = tmp;
+
+        tmp = expand_environment(sei_tmp.lpDirectory);
+        if (!tmp)
+        {
+            return FALSE;
+        }
+        if (wszDir != dirBuffer) HeapFree(GetProcessHeap(), 0, wszDir);
+        sei_tmp.lpDirectory = wszDir = tmp;
+    }
+
     if (ERROR_SUCCESS == ShellExecute_FromContextMenu(&sei_tmp))
     {
         sei->hInstApp = (HINSTANCE) 33;
@@ -1865,7 +1919,6 @@ static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
         }
 
         HeapFree(GetProcessHeap(), 0, wszApplicationName);
-        dwApplicationNameLen = lstrlenW(buf) + 1;
         wszApplicationName = buf;
         sei_tmp.lpFile = wszApplicationName;
     }
@@ -1879,7 +1932,6 @@ static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
 
             ExpandEnvironmentStringsW(sei_tmp.lpFile, buf, len + 1);
             HeapFree(GetProcessHeap(), 0, wszApplicationName);
-            dwApplicationNameLen = len + 1;
             wszApplicationName = buf;
             /* appKnownSingular unmodified */
 
@@ -1926,7 +1978,7 @@ static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
             {
                 end = ++src;
 
-                while(isspace(*src))
+                while(isspaceW(*src))
                     ++src;
             }
             else
@@ -2242,17 +2294,211 @@ EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFil
 }
 
 /*************************************************************************
- * OpenAs_RunDLLA          [SHELL32.@]
+ * OpenAs_RunDLLW          [SHELL32.@]
  */
-EXTERN_C void WINAPI OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
+EXTERN_C void WINAPI
+OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
 {
-    FIXME("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
+    OPENASINFO info;
+    TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
+
+    ZeroMemory(&info, sizeof(info));
+    info.pcszFile = cmdline;
+    info.pcszClass = NULL;
+    info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
+
+    SHOpenWithDialog(hwnd, &info);
 }
 
 /*************************************************************************
- * OpenAs_RunDLLW          [SHELL32.@]
+ * OpenAs_RunDLLA          [SHELL32.@]
  */
-EXTERN_C void WINAPI OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
+EXTERN_C void WINAPI
+OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
 {
-    FIXME("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
+    LPWSTR pszCmdLineW = NULL;
+    TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
+
+    if (cmdline)
+        __SHCloneStrAtoW(&pszCmdLineW, cmdline);
+    OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow);
+    SHFree(pszCmdLineW);
+}
+
+/*************************************************************************/
+
+static LPCWSTR
+SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
+{
+    LPCWSTR pch;
+    size_t ich = 0;
+    if (*psz == L'"')
+    {
+        // 1st argument is quoted. the string in quotes is quoted 1st argument.
+        // [pch] --> [pszArg0+ich]
+        for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
+        {
+            if (*pch == L'"' && pch[1] == L'"')
+            {
+                // doubled double quotations found!
+                pszArg0[ich] = L'"';
+            }
+            else if (*pch == L'"')
+            {
+                // single double quotation found!
+                ++pch;
+                break;
+            }
+            else
+            {
+                // otherwise
+                pszArg0[ich] = *pch;
+            }
+        }
+    }
+    else
+    {
+        // 1st argument is unquoted. non-space sequence is 1st argument.
+        // [pch] --> [pszArg0+ich]
+        for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
+        {
+            pszArg0[ich] = *pch;
+        }
+    }
+    pszArg0[ich] = 0;
+
+    // skip space
+    while (iswspace(*pch))
+        ++pch;
+
+    return pch;
+}
+
+HRESULT WINAPI ShellExecCmdLine(
+    HWND hwnd,
+    LPCWSTR pwszCommand,
+    LPCWSTR pwszStartDir,
+    int nShow,
+    LPVOID pUnused,
+    DWORD dwSeclFlags)
+{
+    SHELLEXECUTEINFOW info;
+    DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
+    LPCWSTR pszVerb = NULL;
+    WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
+    HRESULT hr;
+    LPCWSTR pchParams;
+    LPWSTR lpCommand = NULL;
+
+    if (pwszCommand == NULL)
+        RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
+                       1, (ULONG_PTR*)pwszCommand);
+
+    __SHCloneStrW(&lpCommand, pwszCommand);
+    StrTrimW(lpCommand, L" \t");
+
+    if (dwSeclFlags & SECL_NO_UI)
+        dwFlags |= SEE_MASK_FLAG_NO_UI;
+    if (dwSeclFlags & SECL_LOG_USAGE)
+        dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
+    if (dwSeclFlags & SECL_USE_IDLIST)
+        dwFlags |= SEE_MASK_INVOKEIDLIST;
+
+    if (dwSeclFlags & SECL_RUNAS)
+    {
+        dwSize = 0;
+        hr = AssocQueryStringW(0, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
+        if (SUCCEEDED(hr) && dwSize != 0)
+        {
+            pszVerb = L"runas";
+        }
+    }
+
+    if (UrlIsFileUrlW(lpCommand))
+    {
+        StringCchCopyW(szFile, _countof(szFile), lpCommand);
+        pchParams = NULL;
+    }
+    else
+    {
+        pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
+        if (SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
+            SearchPathW(NULL, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
+            SearchPathW(NULL, szFile, wszCom, _countof(szFile2), szFile2, NULL) ||
+            SearchPathW(pwszStartDir, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
+            SearchPathW(pwszStartDir, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
+            SearchPathW(pwszStartDir, szFile, wszCom, _countof(szFile2), szFile2, NULL))
+        {
+            StringCchCopyW(szFile, _countof(szFile), szFile2);
+            pchParams = NULL;
+        }
+        else if (SearchPathW(NULL, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
+                 SearchPathW(NULL, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
+                 SearchPathW(NULL, lpCommand, wszCom, _countof(szFile2), szFile2, NULL) ||
+                 SearchPathW(pwszStartDir, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
+                 SearchPathW(pwszStartDir, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
+                 SearchPathW(pwszStartDir, lpCommand, wszCom, _countof(szFile2), szFile2, NULL))
+        {
+            StringCchCopyW(szFile, _countof(szFile), szFile2);
+            pchParams = NULL;
+        }
+
+        if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
+        {
+            if (!GetBinaryTypeW(szFile, &dwType))
+            {
+                SHFree(lpCommand);
+
+                if (!(dwSeclFlags & SECL_NO_UI))
+                {
+                    WCHAR szText[128 + MAX_PATH], szFormat[128];
+                    LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
+                    StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
+                    MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
+                }
+                return CO_E_APPNOTFOUND;
+            }
+        }
+        else
+        {
+            if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
+            {
+                SHFree(lpCommand);
+
+                if (!(dwSeclFlags & SECL_NO_UI))
+                {
+                    WCHAR szText[128 + MAX_PATH], szFormat[128];
+                    LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
+                    StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
+                    MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
+                }
+                return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+            }
+        }
+    }
+
+    ZeroMemory(&info, sizeof(info));
+    info.cbSize = sizeof(info);
+    info.fMask = dwFlags;
+    info.hwnd = hwnd;
+    info.lpVerb = pszVerb;
+    info.lpFile = szFile;
+    info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
+    info.lpDirectory = pwszStartDir;
+    info.nShow = nShow;
+    if (ShellExecuteExW(&info))
+    {
+        if (info.lpIDList)
+            CoTaskMemFree(info.lpIDList);
+
+        SHFree(lpCommand);
+
+        return S_OK;
+    }
+
+    dwError = GetLastError();
+
+    SHFree(lpCommand);
+
+    return HRESULT_FROM_WIN32(dwError);
 }