xref: /reactos/dll/win32/shell32/shlexec.cpp (revision 7115d7ba)
1 /*
2  *          Shell Library Functions
3  *
4  * Copyright 1998 Marcus Meissner
5  * Copyright 2002 Eric Pouech
6  * Copyright 2018-2019 Katayama Hirofumi MZ
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include "precomp.h"
24 #include <undocshell.h>
25 
26 WINE_DEFAULT_DEBUG_CHANNEL(exec);
27 
28 static const WCHAR wszOpen[] = L"open";
29 static const WCHAR wszExe[] = L".exe";
30 static const WCHAR wszCom[] = L".com";
31 
32 #define SEE_MASK_CLASSALL (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY)
33 
34 typedef UINT_PTR (*SHELL_ExecuteW32)(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
35                 const SHELLEXECUTEINFOW *sei, LPSHELLEXECUTEINFOW sei_out);
36 
37 static void ParseNoTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
38 {
39     bool firstCharQuote = false;
40     bool quotes_opened = false;
41     bool backslash_encountered = false;
42 
43     for (int curArg = 0; curArg <= argNum && *args; ++curArg)
44     {
45         firstCharQuote = false;
46         if (*args == '"')
47         {
48             quotes_opened = true;
49             firstCharQuote = true;
50             args++;
51         }
52 
53         while(*args)
54         {
55             if (*args == '\\')
56             {
57                 // if we found a backslash then flip the variable
58                 backslash_encountered = !backslash_encountered;
59             }
60             else if (*args == '"')
61             {
62                 if (quotes_opened)
63                 {
64                     if (*(args + 1) != '"')
65                     {
66                         quotes_opened = false;
67                         args++;
68                         break;
69                     }
70                     else
71                     {
72                         args++;
73                     }
74                 }
75                 else
76                 {
77                     quotes_opened = true;
78                 }
79 
80                 backslash_encountered = false;
81             }
82             else
83             {
84                 backslash_encountered = false;
85                 if (*args == ' ' && !firstCharQuote)
86                     break;
87             }
88 
89             if (curArg == argNum)
90             {
91                 used++;
92                 if (used < len)
93                     *res++ = *args;
94             }
95 
96             args++;
97         }
98 
99         while(*args == ' ')
100             ++args;
101     }
102 }
103 
104 static void ParseTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
105 {
106     bool quotes_opened = false;
107     bool backslash_encountered = false;
108 
109     for (int curArg = 0; curArg <= argNum && *args; ++curArg)
110     {
111         while(*args)
112         {
113             if (*args == '\\')
114             {
115                 // if we found a backslash then flip the variable
116                 backslash_encountered = !backslash_encountered;
117             }
118             else if (*args == '"')
119             {
120                 if (quotes_opened)
121                 {
122                     if (*(args + 1) != '"')
123                     {
124                         quotes_opened = false;
125                     }
126                     else
127                     {
128                         args++;
129                     }
130                 }
131                 else
132                 {
133                     quotes_opened = true;
134                 }
135 
136                 backslash_encountered = false;
137             }
138             else
139             {
140                 backslash_encountered = false;
141                 if (*args == ' ' && !quotes_opened && curArg != argNum)
142                     break;
143             }
144 
145             if (curArg == argNum)
146             {
147                 used++;
148                 if (used < len)
149                     *res++ = *args;
150             }
151 
152             args++;
153         }
154     }
155 }
156 
157 /***********************************************************************
158  * SHELL_ArgifyW [Internal]
159  *
160  * this function is supposed to expand the escape sequences found in the registry
161  * some diving reported that the following were used:
162  * + %1, %2...  seem to report to parameter of index N in ShellExecute pmts
163  * %1 file
164  * %2 printer
165  * %3 driver
166  * %4 port
167  * %I address of a global item ID (explorer switch /idlist)
168  * %L seems to be %1 as long filename followed by the 8+3 variation
169  * %S ???
170  * %W Working directory
171  * %V Use either %L or %W
172  * %* all following parameters (see batfile)
173  *
174  * The way we parse the command line arguments was determined through extensive
175  * testing and can be summed up by the following rules"
176  *
177  * - %2
178  *     - if first letter is " break on first non literal " and include any white spaces
179  *     - if first letter is NOT " break on first " or white space
180  *     - if " is opened any pair of consecutive " results in ONE literal "
181  *
182  * - %~2
183  *     - use rules from here http://www.autohotkey.net/~deleyd/parameters/parameters.htm
184  */
185 
186 static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len, const WCHAR* lpDir)
187 {
188     WCHAR   xlpFile[1024];
189     BOOL    done = FALSE;
190     BOOL    found_p1 = FALSE;
191     PWSTR   res = out;
192     PCWSTR  cmd;
193     DWORD   used = 0;
194     bool    tildeEffect = false;
195 
196     TRACE("Before parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
197           debugstr_w(lpFile), pidl, args);
198 
199     while (*fmt)
200     {
201         if (*fmt == '%')
202         {
203             switch (*++fmt)
204             {
205                 case '\0':
206                 case '%':
207                 {
208                     used++;
209                     if (used < len)
210                         *res++ = '%';
211                 };
212                 break;
213 
214                 case '*':
215                 {
216                     if (args)
217                     {
218                         if (*fmt == '*')
219                         {
220                             used++;
221                             while(*args)
222                             {
223                                 used++;
224                                 if (used < len)
225                                     *res++ = *args++;
226                                 else
227                                     args++;
228                             }
229                             used++;
230                             break;
231                         }
232                     }
233                 };
234                 break;
235 
236                 case '~':
237 
238                 case '2':
239                 case '3':
240                 case '4':
241                 case '5':
242                 case '6':
243                 case '7':
244                 case '8':
245                 case '9':
246                     //case '0':
247                 {
248                     if (*fmt == '~')
249                     {
250                         fmt++;
251                         tildeEffect = true;
252                     }
253 
254                     if (args)
255                     {
256                         if (tildeEffect)
257                         {
258                             ParseTildeEffect(res, args, len, used, *fmt - '2');
259                             tildeEffect = false;
260                         }
261                         else
262                         {
263                             ParseNoTildeEffect(res, args, len, used, *fmt - '2');
264                         }
265                     }
266                 };
267                 break;
268 
269                 case '1':
270                     if (!done || (*fmt == '1'))
271                     {
272                         /*FIXME Is the call to SearchPathW() really needed? We already have separated out the parameter string in args. */
273                         if (SearchPathW(lpDir, lpFile, wszExe, sizeof(xlpFile) / sizeof(WCHAR), xlpFile, NULL))
274                             cmd = xlpFile;
275                         else
276                             cmd = lpFile;
277 
278                         used += wcslen(cmd);
279                         if (used < len)
280                         {
281                             wcscpy(res, cmd);
282                             res += wcslen(cmd);
283                         }
284                     }
285                     found_p1 = TRUE;
286                     break;
287 
288                     /*
289                      * IE uses this a lot for activating things such as windows media
290                      * player. This is not verified to be fully correct but it appears
291                      * to work just fine.
292                      */
293                 case 'l':
294                 case 'L':
295                     if (lpFile)
296                     {
297                         used += wcslen(lpFile);
298                         if (used < len)
299                         {
300                             wcscpy(res, lpFile);
301                             res += wcslen(lpFile);
302                         }
303                     }
304                     found_p1 = TRUE;
305                     break;
306 
307                 case 'w':
308                 case 'W':
309                     if (lpDir)
310                     {
311                         used += wcslen(lpDir);
312                         if (used < len)
313                         {
314                             wcscpy(res, lpDir);
315                             res += wcslen(lpDir);
316                         }
317                     }
318                     break;
319 
320                 case 'v':
321                 case 'V':
322                     if (lpFile)
323                     {
324                         used += wcslen(lpFile);
325                         if (used < len)
326                         {
327                             wcscpy(res, lpFile);
328                             res += wcslen(lpFile);
329                         }
330                         found_p1 = TRUE;
331                     }
332                     else if (lpDir)
333                     {
334                         used += wcslen(lpDir);
335                         if (used < len)
336                         {
337                             wcscpy(res, lpDir);
338                             res += wcslen(lpDir);
339                         }
340                     }
341                     break;
342 
343                 case 'i':
344                 case 'I':
345                     if (pidl)
346                     {
347                         DWORD chars = 0;
348                         /* %p should not exceed 8, maybe 16 when looking forward to 64bit.
349                             * allowing a buffer of 100 should more than exceed all needs */
350                         WCHAR buf[100];
351                         LPVOID  pv;
352                         HGLOBAL hmem = SHAllocShared(pidl, ILGetSize(pidl), 0);
353                         pv = SHLockShared(hmem, 0);
354                         chars = swprintf(buf, L":%p", pv);
355 
356                         if (chars >= sizeof(buf) / sizeof(WCHAR))
357                             ERR("pidl format buffer too small!\n");
358 
359                         used += chars;
360 
361                         if (used < len)
362                         {
363                             wcscpy(res, buf);
364                             res += chars;
365                         }
366                         SHUnlockShared(pv);
367                     }
368                     found_p1 = TRUE;
369                     break;
370 
371                 default:
372                     /*
373                      * Check if this is an env-variable here...
374                      */
375 
376                     /* Make sure that we have at least one more %.*/
377                     if (strchrW(fmt, '%'))
378                     {
379                         WCHAR   tmpBuffer[1024];
380                         PWSTR   tmpB = tmpBuffer;
381                         WCHAR   tmpEnvBuff[MAX_PATH];
382                         DWORD   envRet;
383 
384                         while (*fmt != '%')
385                             *tmpB++ = *fmt++;
386                         *tmpB++ = 0;
387 
388                         TRACE("Checking %s to be an env-var\n", debugstr_w(tmpBuffer));
389 
390                         envRet = GetEnvironmentVariableW(tmpBuffer, tmpEnvBuff, MAX_PATH);
391                         if (envRet == 0 || envRet > MAX_PATH)
392                         {
393                             used += wcslen(tmpBuffer);
394                             if (used < len)
395                             {
396                                 wcscpy( res, tmpBuffer );
397                                 res += wcslen(tmpBuffer);
398                             }
399                         }
400                         else
401                         {
402                             used += wcslen(tmpEnvBuff);
403                             if (used < len)
404                             {
405                                 wcscpy( res, tmpEnvBuff );
406                                 res += wcslen(tmpEnvBuff);
407                             }
408                         }
409                     }
410                     done = TRUE;
411                     break;
412             }
413             /* Don't skip past terminator (catch a single '%' at the end) */
414             if (*fmt != '\0')
415             {
416                 fmt++;
417             }
418         }
419         else
420         {
421             used ++;
422             if (used < len)
423                 *res++ = *fmt++;
424             else
425                 fmt++;
426         }
427     }
428 
429     used++;
430     if (res - out < static_cast<int>(len))
431         *res = '\0';
432     else
433         out[len-1] = '\0';
434 
435     TRACE("used %i of %i space\n", used, len);
436     if (out_len)
437         *out_len = used;
438 
439     TRACE("After parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
440           debugstr_w(lpFile), pidl, args);
441 
442     return found_p1;
443 }
444 
445 static HRESULT SHELL_GetPathFromIDListForExecuteW(LPCITEMIDLIST pidl, LPWSTR pszPath, UINT uOutSize)
446 {
447     STRRET strret;
448     CComPtr<IShellFolder> desktop;
449 
450     HRESULT hr = SHGetDesktopFolder(&desktop);
451 
452     if (SUCCEEDED(hr))
453     {
454         hr = desktop->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &strret);
455 
456         if (SUCCEEDED(hr))
457             StrRetToStrNW(pszPath, uOutSize, &strret, pidl);
458     }
459 
460     return hr;
461 }
462 
463 /*************************************************************************
464  *    SHELL_ExecuteW [Internal]
465  *
466  */
467 static UINT_PTR SHELL_ExecuteW(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
468                                const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
469 {
470     STARTUPINFOW  startup;
471     PROCESS_INFORMATION info;
472     UINT_PTR retval = SE_ERR_NOASSOC;
473     UINT gcdret = 0;
474     WCHAR curdir[MAX_PATH];
475     DWORD dwCreationFlags;
476     const WCHAR *lpDirectory = NULL;
477 
478     TRACE("Execute %s from directory %s\n", debugstr_w(lpCmd), debugstr_w(psei->lpDirectory));
479 
480     /* make sure we don't fail the CreateProcess if the calling app passes in
481      * a bad working directory */
482     if (psei->lpDirectory && psei->lpDirectory[0])
483     {
484         DWORD attr = GetFileAttributesW(psei->lpDirectory);
485         if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY)
486             lpDirectory = psei->lpDirectory;
487     }
488 
489     /* ShellExecute specifies the command from psei->lpDirectory
490      * if present. Not from the current dir as CreateProcess does */
491     if (lpDirectory)
492         if ((gcdret = GetCurrentDirectoryW( MAX_PATH, curdir)))
493             if (!SetCurrentDirectoryW( lpDirectory))
494                 ERR("cannot set directory %s\n", debugstr_w(lpDirectory));
495 
496     ZeroMemory(&startup, sizeof(STARTUPINFOW));
497     startup.cb = sizeof(STARTUPINFOW);
498     startup.dwFlags = STARTF_USESHOWWINDOW;
499     startup.wShowWindow = psei->nShow;
500     dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
501     if (!(psei->fMask & SEE_MASK_NO_CONSOLE))
502         dwCreationFlags |= CREATE_NEW_CONSOLE;
503     startup.lpTitle = (LPWSTR)(psei->fMask & (SEE_MASK_HASLINKNAME | SEE_MASK_HASTITLE) ? psei->lpClass : NULL);
504 
505     if (psei->fMask & SEE_MASK_HASLINKNAME)
506         startup.dwFlags |= STARTF_TITLEISLINKNAME;
507 
508     if (CreateProcessW(NULL, (LPWSTR)lpCmd, NULL, NULL, FALSE, dwCreationFlags, env,
509                        lpDirectory, &startup, &info))
510     {
511         /* Give 30 seconds to the app to come up, if desired. Probably only needed
512            when starting app immediately before making a DDE connection. */
513         if (shWait)
514             if (WaitForInputIdle(info.hProcess, 30000) == WAIT_FAILED)
515                 WARN("WaitForInputIdle failed: Error %d\n", GetLastError() );
516         retval = 33;
517 
518         if (psei->fMask & SEE_MASK_NOCLOSEPROCESS)
519             psei_out->hProcess = info.hProcess;
520         else
521             CloseHandle( info.hProcess );
522         CloseHandle( info.hThread );
523     }
524     else if ((retval = GetLastError()) >= 32)
525     {
526         WARN("CreateProcess returned error %ld\n", retval);
527         retval = ERROR_BAD_FORMAT;
528     }
529 
530     TRACE("returning %lu\n", retval);
531 
532     psei_out->hInstApp = (HINSTANCE)retval;
533 
534     if (gcdret)
535         if (!SetCurrentDirectoryW(curdir))
536             ERR("cannot return to directory %s\n", debugstr_w(curdir));
537 
538     return retval;
539 }
540 
541 
542 /***********************************************************************
543  *           SHELL_BuildEnvW    [Internal]
544  *
545  * Build the environment for the new process, adding the specified
546  * path to the PATH variable. Returned pointer must be freed by caller.
547  */
548 static LPWSTR SHELL_BuildEnvW( const WCHAR *path )
549 {
550     static const WCHAR wPath[] = L"PATH=";
551     WCHAR *strings, *new_env;
552     WCHAR *p, *p2;
553     int total = wcslen(path) + 1;
554     BOOL got_path = FALSE;
555 
556     if (!(strings = GetEnvironmentStringsW())) return NULL;
557     p = strings;
558     while (*p)
559     {
560         int len = wcslen(p) + 1;
561         if (!_wcsnicmp( p, wPath, 5 )) got_path = TRUE;
562         total += len;
563         p += len;
564     }
565     if (!got_path) total += 5;  /* we need to create PATH */
566     total++;  /* terminating null */
567 
568     if (!(new_env = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, total * sizeof(WCHAR))))
569     {
570         FreeEnvironmentStringsW(strings);
571         return NULL;
572     }
573     p = strings;
574     p2 = new_env;
575     while (*p)
576     {
577         int len = wcslen(p) + 1;
578         memcpy(p2, p, len * sizeof(WCHAR));
579         if (!_wcsnicmp( p, wPath, 5 ))
580         {
581             p2[len - 1] = ';';
582             wcscpy( p2 + len, path );
583             p2 += wcslen(path) + 1;
584         }
585         p += len;
586         p2 += len;
587     }
588     if (!got_path)
589     {
590         wcscpy(p2, wPath);
591         wcscat(p2, path);
592         p2 += wcslen(p2) + 1;
593     }
594     *p2 = 0;
595     FreeEnvironmentStringsW(strings);
596     return new_env;
597 }
598 
599 
600 /***********************************************************************
601  *           SHELL_TryAppPathW    [Internal]
602  *
603  * Helper function for SHELL_FindExecutable
604  * @param lpResult - pointer to a buffer of size MAX_PATH
605  * On entry: szName is a filename (probably without path separators).
606  * On exit: if szName found in "App Path", place full path in lpResult, and return true
607  */
608 static BOOL SHELL_TryAppPathW( LPCWSTR szName, LPWSTR lpResult, WCHAR **env)
609 {
610     HKEY hkApp = 0;
611     WCHAR buffer[1024];
612     LONG len;
613     LONG res;
614     BOOL found = FALSE;
615 
616     if (env) *env = NULL;
617     wcscpy(buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
618     wcscat(buffer, szName);
619     res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
620     if (res)
621     {
622         // Add ".exe" extension, if extension does not exists
623         if (PathAddExtensionW(buffer, wszExe))
624         {
625             res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
626         }
627         if (res) goto end;
628     }
629 
630     len = MAX_PATH * sizeof(WCHAR);
631     res = RegQueryValueW(hkApp, NULL, lpResult, &len);
632     if (res) goto end;
633     found = TRUE;
634 
635     if (env)
636     {
637         DWORD count = sizeof(buffer);
638         if (!RegQueryValueExW(hkApp, L"Path", NULL, NULL, (LPBYTE)buffer, &count) && buffer[0])
639             *env = SHELL_BuildEnvW(buffer);
640     }
641 
642 end:
643     if (hkApp) RegCloseKey(hkApp);
644     return found;
645 }
646 
647 /*************************************************************************
648  *     SHELL_FindExecutableByVerb [Internal]
649  *
650  * called from SHELL_FindExecutable or SHELL_execute_class
651  * in/out:
652  *      classname a buffer, big enough, to get the key name to do actually the
653  *              command   "WordPad.Document.1\\shell\\open\\command"
654  *              passed as "WordPad.Document.1"
655  * in:
656  *      lpVerb the operation on it (open)
657  *      commandlen the size of command buffer (in bytes)
658  * out:
659  *      command a buffer, to store the command to do the
660  *              operation on the file
661  *      key a buffer, big enough, to get the key name to do actually the
662  *              command "WordPad.Document.1\\shell\\open\\command"
663  *              Can be NULL
664  */
665 static UINT SHELL_FindExecutableByVerb(LPCWSTR lpVerb, LPWSTR key, LPWSTR classname, LPWSTR command, LONG commandlen)
666 {
667     static const WCHAR wCommand[] = L"\\command";
668     HKEY hkeyClass;
669     WCHAR verb[MAX_PATH];
670 
671     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, classname, 0, 0x02000000, &hkeyClass))
672         return SE_ERR_NOASSOC;
673     if (!HCR_GetDefaultVerbW(hkeyClass, lpVerb, verb, sizeof(verb) / sizeof(verb[0])))
674         return SE_ERR_NOASSOC;
675     RegCloseKey(hkeyClass);
676 
677     /* Looking for ...buffer\shell\<verb>\command */
678     wcscat(classname, L"\\shell\\");
679     wcscat(classname, verb);
680     wcscat(classname, wCommand);
681 
682     if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, command,
683                        &commandlen) == ERROR_SUCCESS)
684     {
685         commandlen /= sizeof(WCHAR);
686         if (key) wcscpy(key, classname);
687 #if 0
688         LPWSTR tmp;
689         WCHAR param[256];
690         LONG paramlen = sizeof(param);
691         static const WCHAR wSpace[] = {' ', 0};
692 
693         /* FIXME: it seems all Windows version don't behave the same here.
694          * the doc states that this ddeexec information can be found after
695          * the exec names.
696          * on Win98, it doesn't appear, but I think it does on Win2k
697          */
698         /* Get the parameters needed by the application
699            from the associated ddeexec key */
700         tmp = strstrW(classname, wCommand);
701         tmp[0] = '\0';
702         wcscat(classname, wDdeexec);
703         if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, param,
704                            &paramlen) == ERROR_SUCCESS)
705         {
706             paramlen /= sizeof(WCHAR);
707             wcscat(command, wSpace);
708             wcscat(command, param);
709             commandlen += paramlen;
710         }
711 #endif
712 
713         command[commandlen] = '\0';
714 
715         return 33; /* FIXME see SHELL_FindExecutable() */
716     }
717 
718     return SE_ERR_NOASSOC;
719 }
720 
721 /*************************************************************************
722  *    SHELL_FindExecutable [Internal]
723  *
724  * Utility for code sharing between FindExecutable and ShellExecute
725  * in:
726  *      lpFile the name of a file
727  *      lpVerb the operation on it (open)
728  * out:
729  *      lpResult a buffer, big enough :-(, to store the command to do the
730  *              operation on the file
731  *      key a buffer, big enough, to get the key name to do actually the
732  *              command (it'll be used afterwards for more information
733  *              on the operation)
734  */
735 static UINT SHELL_FindExecutable(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpVerb,
736                                  LPWSTR lpResult, DWORD resultLen, LPWSTR key, WCHAR **env, LPITEMIDLIST pidl, LPCWSTR args)
737 {
738     WCHAR *extension = NULL; /* pointer to file extension */
739     WCHAR classname[256];     /* registry name for this file type */
740     LONG  classnamelen = sizeof(classname); /* length of above */
741     WCHAR command[1024];     /* command from registry */
742     WCHAR wBuffer[256];      /* Used to GetProfileString */
743     UINT  retval = SE_ERR_NOASSOC;
744     WCHAR *tok;              /* token pointer */
745     WCHAR xlpFile[256];      /* result of SearchPath */
746     DWORD attribs;           /* file attributes */
747 
748     TRACE("%s\n", debugstr_w(lpFile));
749 
750     if (!lpResult)
751         return ERROR_INVALID_PARAMETER;
752 
753     xlpFile[0] = '\0';
754     lpResult[0] = '\0'; /* Start off with an empty return string */
755     if (key) *key = '\0';
756 
757     /* trap NULL parameters on entry */
758     if (!lpFile)
759     {
760         WARN("(lpFile=%s,lpResult=%s): NULL parameter\n",
761              debugstr_w(lpFile), debugstr_w(lpResult));
762         return ERROR_FILE_NOT_FOUND; /* File not found. Close enough, I guess. */
763     }
764 
765     if (SHELL_TryAppPathW( lpFile, lpResult, env ))
766     {
767         TRACE("found %s via App Paths\n", debugstr_w(lpResult));
768         return 33;
769     }
770 
771     if (SearchPathW(lpPath, lpFile, wszExe, sizeof(xlpFile) / sizeof(WCHAR), xlpFile, NULL))
772     {
773         TRACE("SearchPathW returned non-zero\n");
774         lpFile = xlpFile;
775         /* The file was found in the application-supplied default directory (or the system search path) */
776     }
777     else if (lpPath && SearchPathW(NULL, lpFile, wszExe, sizeof(xlpFile)/sizeof(WCHAR), xlpFile, NULL))
778     {
779         TRACE("SearchPathW returned non-zero\n");
780         lpFile = xlpFile;
781         /* The file was found in one of the directories in the system-wide search path */
782     }
783 
784     attribs = GetFileAttributesW(lpFile);
785     if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY))
786     {
787         wcscpy(classname, L"Folder");
788     }
789     else
790     {
791         /* Did we get something? Anything? */
792         if (xlpFile[0] == 0)
793         {
794             TRACE("Returning SE_ERR_FNF\n");
795             return SE_ERR_FNF;
796         }
797         /* First thing we need is the file's extension */
798         extension = wcsrchr(xlpFile, '.'); /* Assume last "." is the one; */
799         /* File->Run in progman uses */
800         /* .\FILE.EXE :( */
801         TRACE("xlpFile=%s,extension=%s\n", debugstr_w(xlpFile), debugstr_w(extension));
802 
803         if (extension == NULL || extension[1] == 0)
804         {
805             WARN("Returning SE_ERR_NOASSOC\n");
806             return SE_ERR_NOASSOC;
807         }
808 
809         /* Three places to check: */
810         /* 1. win.ini, [windows], programs (NB no leading '.') */
811         /* 2. Registry, HKEY_CLASS_ROOT\<classname>\shell\open\command */
812         /* 3. win.ini, [extensions], extension (NB no leading '.' */
813         /* All I know of the order is that registry is checked before */
814         /* extensions; however, it'd make sense to check the programs */
815         /* section first, so that's what happens here. */
816 
817         /* See if it's a program - if GetProfileString fails, we skip this
818          * section. Actually, if GetProfileString fails, we've probably
819          * got a lot more to worry about than running a program... */
820         if (GetProfileStringW(L"windows", L"programs", L"exe pif bat cmd com", wBuffer, sizeof(wBuffer) / sizeof(WCHAR)) > 0)
821         {
822             CharLowerW(wBuffer);
823             tok = wBuffer;
824             while (*tok)
825             {
826                 WCHAR *p = tok;
827                 while (*p && *p != ' ' && *p != '\t') p++;
828                 if (*p)
829                 {
830                     *p++ = 0;
831                     while (*p == ' ' || *p == '\t') p++;
832                 }
833 
834                 if (wcsicmp(tok, &extension[1]) == 0) /* have to skip the leading "." */
835                 {
836                     wcscpy(lpResult, xlpFile);
837                     /* Need to perhaps check that the file has a path
838                      * attached */
839                     TRACE("found %s\n", debugstr_w(lpResult));
840                     return 33;
841                     /* Greater than 32 to indicate success */
842                 }
843                 tok = p;
844             }
845         }
846 
847         /* Check registry */
848         if (RegQueryValueW(HKEY_CLASSES_ROOT, extension, classname,
849                            &classnamelen) == ERROR_SUCCESS)
850         {
851             classnamelen /= sizeof(WCHAR);
852             if (classnamelen == sizeof(classname) / sizeof(WCHAR))
853                 classnamelen--;
854 
855             classname[classnamelen] = '\0';
856             TRACE("File type: %s\n", debugstr_w(classname));
857         }
858         else
859         {
860             *classname = '\0';
861         }
862     }
863 
864     if (*classname)
865     {
866         /* pass the verb string to SHELL_FindExecutableByVerb() */
867         retval = SHELL_FindExecutableByVerb(lpVerb, key, classname, command, sizeof(command));
868 
869         if (retval > 32)
870         {
871             DWORD finishedLen;
872             SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen, lpPath);
873             if (finishedLen > resultLen)
874                 ERR("Argify buffer not large enough.. truncated\n");
875             /* Remove double quotation marks and command line arguments */
876             if (*lpResult == '"')
877             {
878                 WCHAR *p = lpResult;
879                 while (*(p + 1) != '"')
880                 {
881                     *p = *(p + 1);
882                     p++;
883                 }
884                 *p = '\0';
885             }
886             else
887             {
888                 /* Truncate on first space */
889                 WCHAR *p = lpResult;
890                 while (*p != ' ' && *p != '\0')
891                     p++;
892                 *p = '\0';
893             }
894         }
895     }
896     else /* Check win.ini */
897     {
898         /* Toss the leading dot */
899         extension++;
900         if (GetProfileStringW(L"extensions", extension, L"", command, sizeof(command) / sizeof(WCHAR)) > 0)
901         {
902             if (wcslen(command) != 0)
903             {
904                 wcscpy(lpResult, command);
905                 tok = wcschr(lpResult, '^'); /* should be ^.extension? */
906                 if (tok != NULL)
907                 {
908                     tok[0] = '\0';
909                     wcscat(lpResult, xlpFile); /* what if no dir in xlpFile? */
910                     tok = wcschr(command, '^'); /* see above */
911                     if ((tok != NULL) && (wcslen(tok) > 5))
912                     {
913                         wcscat(lpResult, &tok[5]);
914                     }
915                 }
916                 retval = 33; /* FIXME - see above */
917             }
918         }
919     }
920 
921     TRACE("returning %s\n", debugstr_w(lpResult));
922     return retval;
923 }
924 
925 /******************************************************************
926  *        dde_cb
927  *
928  * callback for the DDE connection. not really useful
929  */
930 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
931                                 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
932                                 ULONG_PTR dwData1, ULONG_PTR dwData2)
933 {
934     TRACE("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
935           uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
936     return NULL;
937 }
938 
939 /******************************************************************
940  *        dde_connect
941  *
942  * ShellExecute helper. Used to do an operation with a DDE connection
943  *
944  * Handles both the direct connection (try #1), and if it fails,
945  * launching an application and trying (#2) to connect to it
946  *
947  */
948 static unsigned dde_connect(const WCHAR* key, const WCHAR* start, WCHAR* ddeexec,
949                             const WCHAR* lpFile, WCHAR *env,
950                             LPCWSTR szCommandline, LPITEMIDLIST pidl, SHELL_ExecuteW32 execfunc,
951                             const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
952 {
953     WCHAR       regkey[256];
954     WCHAR *     endkey = regkey + wcslen(key);
955     WCHAR       app[256], topic[256], ifexec[256], static_res[256];
956     WCHAR *     dynamic_res=NULL;
957     WCHAR *     res;
958     LONG        applen, topiclen, ifexeclen;
959     WCHAR *     exec;
960     DWORD       ddeInst = 0;
961     DWORD       tid;
962     DWORD       resultLen, endkeyLen;
963     HSZ         hszApp, hszTopic;
964     HCONV       hConv;
965     HDDEDATA    hDdeData;
966     unsigned    ret = SE_ERR_NOASSOC;
967     BOOL unicode = !(GetVersion() & 0x80000000);
968 
969     if (strlenW(key) + 1 > sizeof(regkey) / sizeof(regkey[0]))
970     {
971         FIXME("input parameter %s larger than buffer\n", debugstr_w(key));
972         return 2;
973     }
974     wcscpy(regkey, key);
975     static const WCHAR wApplication[] = L"\\application";
976     endkeyLen = sizeof(regkey) / sizeof(regkey[0]) - (endkey - regkey);
977     if (strlenW(wApplication) + 1 > endkeyLen)
978     {
979         FIXME("endkey %s overruns buffer\n", debugstr_w(wApplication));
980         return 2;
981     }
982     wcscpy(endkey, wApplication);
983     applen = sizeof(app);
984     if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, app, &applen) != ERROR_SUCCESS)
985     {
986         WCHAR command[1024], fullpath[MAX_PATH];
987         static const WCHAR wSo[] = L".so";
988         DWORD sizeSo = sizeof(wSo) / sizeof(WCHAR);
989         LPWSTR ptr = NULL;
990         DWORD ret = 0;
991 
992         /* Get application command from start string and find filename of application */
993         if (*start == '"')
994         {
995             if (strlenW(start + 1) + 1 > sizeof(command) / sizeof(command[0]))
996             {
997                 FIXME("size of input parameter %s larger than buffer\n",
998                       debugstr_w(start + 1));
999                 return 2;
1000             }
1001             wcscpy(command, start + 1);
1002             if ((ptr = wcschr(command, '"')))
1003                 * ptr = 0;
1004             ret = SearchPathW(NULL, command, wszExe, sizeof(fullpath) / sizeof(WCHAR), fullpath, &ptr);
1005         }
1006         else
1007         {
1008             LPCWSTR p;
1009             LPWSTR space;
1010             for (p = start; (space = const_cast<LPWSTR>(strchrW(p, ' '))); p = space + 1)
1011             {
1012                 int idx = space - start;
1013                 memcpy(command, start, idx * sizeof(WCHAR));
1014                 command[idx] = '\0';
1015                 if ((ret = SearchPathW(NULL, command, wszExe, sizeof(fullpath) / sizeof(WCHAR), fullpath, &ptr)))
1016                     break;
1017             }
1018             if (!ret)
1019                 ret = SearchPathW(NULL, start, wszExe, sizeof(fullpath) / sizeof(WCHAR), fullpath, &ptr);
1020         }
1021 
1022         if (!ret)
1023         {
1024             ERR("Unable to find application path for command %s\n", debugstr_w(start));
1025             return ERROR_ACCESS_DENIED;
1026         }
1027         if (strlenW(ptr) + 1 > sizeof(app) / sizeof(app[0]))
1028         {
1029             FIXME("size of found path %s larger than buffer\n", debugstr_w(ptr));
1030             return 2;
1031         }
1032         wcscpy(app, ptr);
1033 
1034         /* Remove extensions (including .so) */
1035         ptr = app + wcslen(app) - (sizeSo - 1);
1036         if (wcslen(app) >= sizeSo &&
1037                 !wcscmp(ptr, wSo))
1038             *ptr = 0;
1039 
1040         ptr = const_cast<LPWSTR>(strrchrW(app, '.'));
1041         assert(ptr);
1042         *ptr = 0;
1043     }
1044 
1045     static const WCHAR wTopic[] = L"\\topic";
1046     if (strlenW(wTopic) + 1 > endkeyLen)
1047     {
1048         FIXME("endkey %s overruns buffer\n", debugstr_w(wTopic));
1049         return 2;
1050     }
1051     wcscpy(endkey, wTopic);
1052     topiclen = sizeof(topic);
1053     if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, topic, &topiclen) != ERROR_SUCCESS)
1054     {
1055         wcscpy(topic, L"System");
1056     }
1057 
1058     if (unicode)
1059     {
1060         if (DdeInitializeW(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1061             return 2;
1062     }
1063     else
1064     {
1065         if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1066             return 2;
1067     }
1068 
1069     hszApp = DdeCreateStringHandleW(ddeInst, app, CP_WINUNICODE);
1070     hszTopic = DdeCreateStringHandleW(ddeInst, topic, CP_WINUNICODE);
1071 
1072     hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1073     exec = ddeexec;
1074     if (!hConv)
1075     {
1076         TRACE("Launching %s\n", debugstr_w(start));
1077         ret = execfunc(start, env, TRUE, psei, psei_out);
1078         if (ret <= 32)
1079         {
1080             TRACE("Couldn't launch\n");
1081             goto error;
1082         }
1083         /* if ddeexec is NULL, then we just need to exit here */
1084         if (ddeexec == NULL)
1085         {
1086             TRACE("Exiting because ddeexec is NULL. ret=42.\n");
1087             /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1088             /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1089             return 42;
1090         }
1091         /* if ddeexec is 'empty string', then we just need to exit here */
1092         if (wcscmp(ddeexec, L"") == 0)
1093         {
1094             TRACE("Exiting because ddeexec is 'empty string'. ret=42.\n");
1095             /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1096             /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1097             return 42;
1098         }
1099         hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1100         if (!hConv)
1101         {
1102             TRACE("Couldn't connect. ret=%d\n", ret);
1103             DdeUninitialize(ddeInst);
1104             SetLastError(ERROR_DDE_FAIL);
1105             return 30; /* whatever */
1106         }
1107         static const WCHAR wIfexec[] = L"\\ifexec";
1108         if (strlenW(wIfexec) + 1 > endkeyLen)
1109         {
1110             FIXME("endkey %s overruns buffer\n", debugstr_w(wIfexec));
1111             return 2;
1112         }
1113         strcpyW(endkey, wIfexec);
1114         ifexeclen = sizeof(ifexec);
1115         if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, ifexec, &ifexeclen) == ERROR_SUCCESS)
1116         {
1117             exec = ifexec;
1118         }
1119     }
1120 
1121     SHELL_ArgifyW(static_res, sizeof(static_res)/sizeof(WCHAR), exec, lpFile, pidl, szCommandline, &resultLen, NULL);
1122     if (resultLen > sizeof(static_res)/sizeof(WCHAR))
1123     {
1124         res = dynamic_res = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, resultLen * sizeof(WCHAR)));
1125         SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL, NULL);
1126     }
1127     else
1128         res = static_res;
1129     TRACE("%s %s => %s\n", debugstr_w(exec), debugstr_w(lpFile), debugstr_w(res));
1130 
1131     /* It's documented in the KB 330337 that IE has a bug and returns
1132      * error DMLERR_NOTPROCESSED on XTYP_EXECUTE request.
1133      */
1134     if (unicode)
1135         hDdeData = DdeClientTransaction((LPBYTE)res, (strlenW(res) + 1) * sizeof(WCHAR), hConv, 0L, 0, XTYP_EXECUTE, 30000, &tid);
1136     else
1137     {
1138         DWORD lenA = WideCharToMultiByte(CP_ACP, 0, res, -1, NULL, 0, NULL, NULL);
1139         char *resA = (LPSTR)HeapAlloc(GetProcessHeap(), 0, lenA);
1140         WideCharToMultiByte(CP_ACP, 0, res, -1, resA, lenA, NULL, NULL);
1141         hDdeData = DdeClientTransaction( (LPBYTE)resA, lenA, hConv, 0L, 0,
1142                                          XTYP_EXECUTE, 10000, &tid );
1143         HeapFree(GetProcessHeap(), 0, resA);
1144     }
1145     if (hDdeData)
1146         DdeFreeDataHandle(hDdeData);
1147     else
1148         WARN("DdeClientTransaction failed with error %04x\n", DdeGetLastError(ddeInst));
1149     ret = 33;
1150 
1151     HeapFree(GetProcessHeap(), 0, dynamic_res);
1152 
1153     DdeDisconnect(hConv);
1154 
1155 error:
1156     DdeUninitialize(ddeInst);
1157 
1158     return ret;
1159 }
1160 
1161 /*************************************************************************
1162  *    execute_from_key [Internal]
1163  */
1164 static UINT_PTR execute_from_key(LPCWSTR key, LPCWSTR lpFile, WCHAR *env,
1165                                  LPCWSTR szCommandline, LPCWSTR executable_name,
1166                                  SHELL_ExecuteW32 execfunc,
1167                                  LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out)
1168 {
1169     WCHAR cmd[256], param[1024], ddeexec[256];
1170     DWORD cmdlen = sizeof(cmd), ddeexeclen = sizeof(ddeexec);
1171     UINT_PTR retval = SE_ERR_NOASSOC;
1172     DWORD resultLen;
1173     LPWSTR tmp;
1174 
1175     TRACE("%s %s %s %s %s\n", debugstr_w(key), debugstr_w(lpFile), debugstr_w(env),
1176           debugstr_w(szCommandline), debugstr_w(executable_name));
1177 
1178     cmd[0] = '\0';
1179     param[0] = '\0';
1180 
1181     /* Get the application from the registry */
1182     if (RegQueryValueW(HKEY_CLASSES_ROOT, key, cmd, (LONG *)&cmdlen) == ERROR_SUCCESS)
1183     {
1184         TRACE("got cmd: %s\n", debugstr_w(cmd));
1185 
1186         /* Is there a replace() function anywhere? */
1187         cmdlen /= sizeof(WCHAR);
1188         if (cmdlen >= sizeof(cmd) / sizeof(WCHAR))
1189             cmdlen = sizeof(cmd) / sizeof(WCHAR) - 1;
1190         cmd[cmdlen] = '\0';
1191         SHELL_ArgifyW(param, sizeof(param) / sizeof(WCHAR), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen,
1192                       (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1193         if (resultLen > sizeof(param) / sizeof(WCHAR))
1194             ERR("Argify buffer not large enough, truncating\n");
1195     }
1196 
1197     /* Get the parameters needed by the application
1198        from the associated ddeexec key */
1199     tmp = const_cast<LPWSTR>(strstrW(key, L"command"));
1200     assert(tmp);
1201     wcscpy(tmp, L"ddeexec");
1202 
1203     if (RegQueryValueW(HKEY_CLASSES_ROOT, key, ddeexec, (LONG *)&ddeexeclen) == ERROR_SUCCESS)
1204     {
1205         TRACE("Got ddeexec %s => %s\n", debugstr_w(key), debugstr_w(ddeexec));
1206         if (!param[0]) strcpyW(param, executable_name);
1207         retval = dde_connect(key, param, ddeexec, lpFile, env, szCommandline, (LPITEMIDLIST)psei->lpIDList, execfunc, psei, psei_out);
1208     }
1209     else if (param[0])
1210     {
1211         TRACE("executing: %s\n", debugstr_w(param));
1212         retval = execfunc(param, env, FALSE, psei, psei_out);
1213     }
1214     else
1215         WARN("Nothing appropriate found for %s\n", debugstr_w(key));
1216 
1217     return retval;
1218 }
1219 
1220 /*************************************************************************
1221  * FindExecutableA            [SHELL32.@]
1222  */
1223 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
1224 {
1225     HINSTANCE retval;
1226     WCHAR *wFile = NULL, *wDirectory = NULL;
1227     WCHAR wResult[MAX_PATH];
1228 
1229     if (lpFile) __SHCloneStrAtoW(&wFile, lpFile);
1230     if (lpDirectory) __SHCloneStrAtoW(&wDirectory, lpDirectory);
1231 
1232     retval = FindExecutableW(wFile, wDirectory, wResult);
1233     WideCharToMultiByte(CP_ACP, 0, wResult, -1, lpResult, MAX_PATH, NULL, NULL);
1234     SHFree(wFile);
1235     SHFree(wDirectory);
1236 
1237     TRACE("returning %s\n", lpResult);
1238     return retval;
1239 }
1240 
1241 /*************************************************************************
1242  * FindExecutableW            [SHELL32.@]
1243  *
1244  * This function returns the executable associated with the specified file
1245  * for the default verb.
1246  *
1247  * PARAMS
1248  *  lpFile   [I] The file to find the association for. This must refer to
1249  *               an existing file otherwise FindExecutable fails and returns
1250  *               SE_ERR_FNF.
1251  *  lpResult [O] Points to a buffer into which the executable path is
1252  *               copied. This parameter must not be NULL otherwise
1253  *               FindExecutable() segfaults. The buffer must be of size at
1254  *               least MAX_PATH characters.
1255  *
1256  * RETURNS
1257  *  A value greater than 32 on success, less than or equal to 32 otherwise.
1258  *  See the SE_ERR_* constants.
1259  *
1260  * NOTES
1261  *  On Windows XP and 2003, FindExecutable() seems to first convert the
1262  *  filename into 8.3 format, thus taking into account only the first three
1263  *  characters of the extension, and expects to find an association for those.
1264  *  However other Windows versions behave sanely.
1265  */
1266 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
1267 {
1268     UINT_PTR retval = SE_ERR_NOASSOC;
1269     WCHAR old_dir[1024];
1270     WCHAR res[MAX_PATH];
1271 
1272     TRACE("File %s, Dir %s\n", debugstr_w(lpFile), debugstr_w(lpDirectory));
1273 
1274     lpResult[0] = '\0'; /* Start off with an empty return string */
1275     if (lpFile == NULL)
1276         return (HINSTANCE)SE_ERR_FNF;
1277 
1278     if (lpDirectory)
1279     {
1280         GetCurrentDirectoryW(sizeof(old_dir) / sizeof(WCHAR), old_dir);
1281         SetCurrentDirectoryW(lpDirectory);
1282     }
1283 
1284     retval = SHELL_FindExecutable(lpDirectory, lpFile, wszOpen, res, MAX_PATH, NULL, NULL, NULL, NULL);
1285     if (retval > 32)
1286         strcpyW(lpResult, res);
1287 
1288     TRACE("returning %s\n", debugstr_w(lpResult));
1289     if (lpDirectory)
1290         SetCurrentDirectoryW(old_dir);
1291     return (HINSTANCE)retval;
1292 }
1293 
1294 /* FIXME: is this already implemented somewhere else? */
1295 static HKEY ShellExecute_GetClassKey(const SHELLEXECUTEINFOW *sei)
1296 {
1297     LPCWSTR ext = NULL, lpClass = NULL;
1298     LPWSTR cls = NULL;
1299     DWORD type = 0, sz = 0;
1300     HKEY hkey = 0;
1301     LONG r;
1302 
1303     if (sei->fMask & SEE_MASK_CLASSALL)
1304         return sei->hkeyClass;
1305 
1306     if (sei->fMask & SEE_MASK_CLASSNAME)
1307         lpClass = sei->lpClass;
1308     else
1309     {
1310         ext = PathFindExtensionW(sei->lpFile);
1311         TRACE("ext = %s\n", debugstr_w(ext));
1312         if (!ext)
1313             return hkey;
1314 
1315         r = RegOpenKeyW(HKEY_CLASSES_ROOT, ext, &hkey);
1316         if (r != ERROR_SUCCESS)
1317             return hkey;
1318 
1319         r = RegQueryValueExW(hkey, NULL, 0, &type, NULL, &sz);
1320         if (r == ERROR_SUCCESS && type == REG_SZ)
1321         {
1322             sz += sizeof (WCHAR);
1323             cls = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sz);
1324             cls[0] = 0;
1325             RegQueryValueExW(hkey, NULL, 0, &type, (LPBYTE) cls, &sz);
1326         }
1327 
1328         RegCloseKey( hkey );
1329         lpClass = cls;
1330     }
1331 
1332     TRACE("class = %s\n", debugstr_w(lpClass));
1333 
1334     hkey = 0;
1335     if (lpClass)
1336         RegOpenKeyW( HKEY_CLASSES_ROOT, lpClass, &hkey);
1337 
1338     HeapFree(GetProcessHeap(), 0, cls);
1339 
1340     return hkey;
1341 }
1342 
1343 static IDataObject *shellex_get_dataobj( LPSHELLEXECUTEINFOW sei )
1344 {
1345     LPCITEMIDLIST pidllast = NULL;
1346     CComPtr<IDataObject> dataobj;
1347     CComPtr<IShellFolder> shf;
1348     LPITEMIDLIST pidl = NULL;
1349     HRESULT r;
1350 
1351     if (sei->fMask & SEE_MASK_CLASSALL)
1352         pidl = (LPITEMIDLIST)sei->lpIDList;
1353     else
1354     {
1355         WCHAR fullpath[MAX_PATH];
1356         BOOL ret;
1357 
1358         fullpath[0] = 0;
1359         ret = GetFullPathNameW(sei->lpFile, MAX_PATH, fullpath, NULL);
1360         if (!ret)
1361             goto end;
1362 
1363         pidl = ILCreateFromPathW(fullpath);
1364     }
1365 
1366     r = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shf), &pidllast);
1367     if (FAILED(r))
1368         goto end;
1369 
1370     shf->GetUIObjectOf(NULL, 1, &pidllast, IID_NULL_PPV_ARG(IDataObject, &dataobj));
1371 
1372 end:
1373     if (pidl != sei->lpIDList)
1374         ILFree(pidl);
1375     return dataobj.Detach();
1376 }
1377 
1378 static HRESULT shellex_run_context_menu_default(IShellExtInit *obj,
1379         LPSHELLEXECUTEINFOW sei)
1380 {
1381     CComPtr<IContextMenu> cm = NULL;
1382     CMINVOKECOMMANDINFOEX ici;
1383     MENUITEMINFOW info;
1384     WCHAR string[0x80];
1385     INT i, n, def = -1;
1386     HMENU hmenu = 0;
1387     HRESULT r;
1388 
1389     TRACE("%p %p\n", obj, sei);
1390 
1391     r = obj->QueryInterface(IID_PPV_ARG(IContextMenu, &cm));
1392     if (FAILED(r))
1393         return r;
1394 
1395     hmenu = CreateMenu();
1396     if (!hmenu)
1397         goto end;
1398 
1399     /* the number of the last menu added is returned in r */
1400     r = cm->QueryContextMenu(hmenu, 0, 0x20, 0x7fff, CMF_DEFAULTONLY);
1401     if (FAILED(r))
1402         goto end;
1403 
1404     n = GetMenuItemCount(hmenu);
1405     for (i = 0; i < n; i++)
1406     {
1407         memset(&info, 0, sizeof(info));
1408         info.cbSize = sizeof info;
1409         info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_STATE | MIIM_DATA | MIIM_ID;
1410         info.dwTypeData = string;
1411         info.cch = sizeof string;
1412         string[0] = 0;
1413         GetMenuItemInfoW(hmenu, i, TRUE, &info);
1414 
1415         TRACE("menu %d %s %08x %08lx %08x %08x\n", i, debugstr_w(string),
1416               info.fState, info.dwItemData, info.fType, info.wID);
1417         if ((!sei->lpVerb && (info.fState & MFS_DEFAULT)) ||
1418             (sei->lpVerb && !lstrcmpiW(sei->lpVerb, string)))
1419         {
1420             def = i;
1421             break;
1422         }
1423     }
1424 
1425     r = E_FAIL;
1426     if (def == -1)
1427         goto end;
1428 
1429     memset(&ici, 0, sizeof ici);
1430     ici.cbSize = sizeof ici;
1431     ici.fMask = CMIC_MASK_UNICODE | (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1432     ici.nShow = sei->nShow;
1433     ici.lpVerb = MAKEINTRESOURCEA(def);
1434     ici.hwnd = sei->hwnd;
1435     ici.lpParametersW = sei->lpParameters;
1436 
1437     r = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1438 
1439     TRACE("invoke command returned %08x\n", r);
1440 
1441 end:
1442     if (hmenu)
1443         DestroyMenu( hmenu );
1444     return r;
1445 }
1446 
1447 static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei)
1448 {
1449     // Can not use CComPtr here because of CoUninitialize at the end, before the destructors would run.
1450     IDataObject *dataobj = NULL;
1451     IObjectWithSite *ows = NULL;
1452     IShellExtInit *obj = NULL;
1453     HRESULT r;
1454 
1455     TRACE("%p %s %p\n", hkey, debugstr_guid(guid), sei);
1456 
1457     r = CoInitialize(NULL);
1458     if (FAILED(r))
1459         goto end;
1460 
1461     r = CoCreateInstance(*guid, NULL, CLSCTX_INPROC_SERVER,
1462                          IID_PPV_ARG(IShellExtInit, &obj));
1463     if (FAILED(r))
1464     {
1465         ERR("failed %08x\n", r);
1466         goto end;
1467     }
1468 
1469     dataobj = shellex_get_dataobj(sei);
1470     if (!dataobj)
1471     {
1472         ERR("failed to get data object\n");
1473         r = E_FAIL;
1474         goto end;
1475     }
1476 
1477     r = obj->Initialize(NULL, dataobj, hkey);
1478     if (FAILED(r))
1479         goto end;
1480 
1481     r = obj->QueryInterface(IID_PPV_ARG(IObjectWithSite, &ows));
1482     if (FAILED(r))
1483         goto end;
1484 
1485     ows->SetSite(NULL);
1486 
1487     r = shellex_run_context_menu_default(obj, sei);
1488 
1489 end:
1490     if (ows)
1491         ows->Release();
1492     if (dataobj)
1493         dataobj->Release();
1494     if (obj)
1495         obj->Release();
1496     CoUninitialize();
1497     return r;
1498 }
1499 
1500 
1501 /*************************************************************************
1502  *    ShellExecute_FromContextMenu [Internal]
1503  */
1504 static LONG ShellExecute_FromContextMenu( LPSHELLEXECUTEINFOW sei )
1505 {
1506     HKEY hkey, hkeycm = 0;
1507     WCHAR szguid[39];
1508     HRESULT hr;
1509     GUID guid;
1510     DWORD i;
1511     LONG r;
1512 
1513     TRACE("%s\n", debugstr_w(sei->lpFile));
1514 
1515     hkey = ShellExecute_GetClassKey(sei);
1516     if (!hkey)
1517         return ERROR_FUNCTION_FAILED;
1518 
1519     r = RegOpenKeyW(hkey, L"shellex\\ContextMenuHandlers", &hkeycm);
1520     if (r == ERROR_SUCCESS)
1521     {
1522         i = 0;
1523         while (1)
1524         {
1525             r = RegEnumKeyW(hkeycm, i++, szguid, sizeof(szguid) / sizeof(szguid[0]));
1526             if (r != ERROR_SUCCESS)
1527                 break;
1528 
1529             hr = CLSIDFromString(szguid, &guid);
1530             if (SUCCEEDED(hr))
1531             {
1532                 /* stop at the first one that succeeds in running */
1533                 hr = shellex_load_object_and_run(hkey, &guid, sei);
1534                 if (SUCCEEDED(hr))
1535                     break;
1536             }
1537         }
1538         RegCloseKey(hkeycm);
1539     }
1540 
1541     if (hkey != sei->hkeyClass)
1542         RegCloseKey(hkey);
1543     return r;
1544 }
1545 
1546 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR lpstrProtocol, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc);
1547 
1548 static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1549 {
1550     WCHAR execCmd[1024], classname[1024];
1551     /* launch a document by fileclass like 'WordPad.Document.1' */
1552     /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
1553     /* FIXME: wcmd should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */
1554     ULONG cmask = (psei->fMask & SEE_MASK_CLASSALL);
1555     DWORD resultLen;
1556     BOOL done;
1557     UINT_PTR rslt;
1558 
1559     /* FIXME: remove following block when SHELL_quote_and_execute supports hkeyClass parameter */
1560     if (cmask != SEE_MASK_CLASSNAME)
1561     {
1562         WCHAR wcmd[1024];
1563         HCR_GetExecuteCommandW((cmask == SEE_MASK_CLASSKEY) ? psei->hkeyClass : NULL,
1564                                (cmask == SEE_MASK_CLASSNAME) ? psei->lpClass : NULL,
1565                                psei->lpVerb,
1566                                execCmd, sizeof(execCmd));
1567 
1568         /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
1569         TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName));
1570 
1571         wcmd[0] = '\0';
1572         done = SHELL_ArgifyW(wcmd, sizeof(wcmd) / sizeof(WCHAR), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, NULL, &resultLen,
1573                              (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1574         if (!done && wszApplicationName[0])
1575         {
1576             strcatW(wcmd, L" ");
1577             if (*wszApplicationName != '"')
1578             {
1579                 strcatW(wcmd, L"\"");
1580                 strcatW(wcmd, wszApplicationName);
1581                 strcatW(wcmd, L"\"");
1582             }
1583             else
1584                 strcatW(wcmd, wszApplicationName);
1585         }
1586         if (resultLen > sizeof(wcmd) / sizeof(WCHAR))
1587             ERR("Argify buffer not large enough... truncating\n");
1588         return execfunc(wcmd, NULL, FALSE, psei, psei_out);
1589     }
1590 
1591     strcpyW(classname, psei->lpClass);
1592     rslt = SHELL_FindExecutableByVerb(psei->lpVerb, NULL, classname, execCmd, sizeof(execCmd));
1593 
1594     TRACE("SHELL_FindExecutableByVerb returned %u (%s, %s)\n", (unsigned int)rslt, debugstr_w(classname), debugstr_w(execCmd));
1595     if (33 > rslt)
1596         return rslt;
1597     rslt = SHELL_quote_and_execute( execCmd, L"", classname,
1598                                       wszApplicationName, NULL, psei,
1599                                       psei_out, execfunc );
1600     return rslt;
1601 
1602 }
1603 
1604 static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters, DWORD parametersLen, LPWSTR wszApplicationName, DWORD dwApplicationNameLen)
1605 {
1606     static const WCHAR wExplorer[] = L"explorer.exe";
1607     WCHAR buffer[MAX_PATH];
1608     BOOL appKnownSingular = FALSE;
1609 
1610     /* last chance to translate IDList: now also allow CLSID paths */
1611     if (SUCCEEDED(SHELL_GetPathFromIDListForExecuteW((LPCITEMIDLIST)sei->lpIDList, buffer, sizeof(buffer)/sizeof(WCHAR)))) {
1612         if (buffer[0] == ':' && buffer[1] == ':') {
1613             /* open shell folder for the specified class GUID */
1614             if (strlenW(buffer) + 1 > parametersLen)
1615                 ERR("parameters len exceeds buffer size (%i > %i), truncating\n",
1616                     lstrlenW(buffer) + 1, parametersLen);
1617             lstrcpynW(wszParameters, buffer, parametersLen);
1618             if (strlenW(wExplorer) > dwApplicationNameLen)
1619                 ERR("application len exceeds buffer size (%i > %i), truncating\n",
1620                     lstrlenW(wExplorer) + 1, dwApplicationNameLen);
1621             lstrcpynW(wszApplicationName, wExplorer, dwApplicationNameLen);
1622             appKnownSingular = TRUE;
1623 
1624             sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1625         } else {
1626             WCHAR target[MAX_PATH];
1627             DWORD attribs;
1628             DWORD resultLen;
1629             /* Check if we're executing a directory and if so use the
1630                handler for the Folder class */
1631             strcpyW(target, buffer);
1632             attribs = GetFileAttributesW(buffer);
1633             if (attribs != INVALID_FILE_ATTRIBUTES &&
1634                     (attribs & FILE_ATTRIBUTE_DIRECTORY) &&
1635                     HCR_GetExecuteCommandW(0, L"Folder",
1636                                            sei->lpVerb,
1637                                            buffer, sizeof(buffer))) {
1638                 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen,
1639                               buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen,
1640                               (sei->lpDirectory && *sei->lpDirectory) ? sei->lpDirectory : NULL);
1641                 if (resultLen > dwApplicationNameLen)
1642                     ERR("Argify buffer not large enough... truncating\n");
1643                 appKnownSingular = FALSE;
1644             }
1645             sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1646         }
1647     }
1648     return appKnownSingular;
1649 }
1650 
1651 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR wszKeyname, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1652 {
1653     UINT_PTR retval;
1654     DWORD len;
1655     WCHAR *wszQuotedCmd;
1656 
1657     /* Length of quotes plus length of command plus NULL terminator */
1658     len = 2 + lstrlenW(wcmd) + 1;
1659     if (wszParameters[0])
1660     {
1661         /* Length of space plus length of parameters */
1662         len += 1 + lstrlenW(wszParameters);
1663     }
1664     wszQuotedCmd = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1665     /* Must quote to handle case where cmd contains spaces,
1666      * else security hole if malicious user creates executable file "C:\\Program"
1667      */
1668     strcpyW(wszQuotedCmd, L"\"");
1669     strcatW(wszQuotedCmd, wcmd);
1670     strcatW(wszQuotedCmd, L"\"");
1671     if (wszParameters[0])
1672     {
1673         strcatW(wszQuotedCmd, L" ");
1674         strcatW(wszQuotedCmd, wszParameters);
1675     }
1676 
1677     TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(psei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(wszKeyname));
1678 
1679     if (*wszKeyname)
1680         retval = execute_from_key(wszKeyname, wszApplicationName, env, psei->lpParameters, wcmd, execfunc, psei, psei_out);
1681     else
1682         retval = execfunc(wszQuotedCmd, env, FALSE, psei, psei_out);
1683     HeapFree(GetProcessHeap(), 0, wszQuotedCmd);
1684     return retval;
1685 }
1686 
1687 static UINT_PTR SHELL_execute_url(LPCWSTR lpFile, LPCWSTR wcmd, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1688 {
1689     static const WCHAR wShell[] = L"\\shell\\";
1690     static const WCHAR wCommand[] = L"\\command";
1691     UINT_PTR retval;
1692     WCHAR *lpstrProtocol;
1693     LPCWSTR lpstrRes;
1694     INT iSize;
1695     DWORD len;
1696 
1697     lpstrRes = strchrW(lpFile, ':');
1698     if (lpstrRes)
1699         iSize = lpstrRes - lpFile;
1700     else
1701         iSize = strlenW(lpFile);
1702 
1703     TRACE("Got URL: %s\n", debugstr_w(lpFile));
1704     /* Looking for ...<protocol>\shell\<lpVerb>\command */
1705     len = iSize + lstrlenW(wShell) + lstrlenW(wCommand) + 1;
1706     if (psei->lpVerb && *psei->lpVerb)
1707         len += lstrlenW(psei->lpVerb);
1708     else
1709         len += lstrlenW(wszOpen);
1710     lpstrProtocol = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1711     memcpy(lpstrProtocol, lpFile, iSize * sizeof(WCHAR));
1712     lpstrProtocol[iSize] = '\0';
1713     strcatW(lpstrProtocol, wShell);
1714     strcatW(lpstrProtocol, psei->lpVerb && *psei->lpVerb ? psei->lpVerb : wszOpen);
1715     strcatW(lpstrProtocol, wCommand);
1716 
1717     retval = execute_from_key(lpstrProtocol, lpFile, NULL, psei->lpParameters,
1718                               wcmd, execfunc, psei, psei_out);
1719     HeapFree(GetProcessHeap(), 0, lpstrProtocol);
1720     return retval;
1721 }
1722 
1723 static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
1724 {
1725     WCHAR msg[2048];
1726     DWORD_PTR msgArguments[3]  = { (DWORD_PTR)filename, 0, 0 };
1727     DWORD error_code;
1728 
1729     error_code = GetLastError();
1730     if (retval == SE_ERR_NOASSOC)
1731         LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, sizeof(msg) / sizeof(WCHAR));
1732     else
1733         FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1734                        NULL,
1735                        error_code,
1736                        LANG_USER_DEFAULT,
1737                        msg,
1738                        sizeof(msg) / sizeof(WCHAR),
1739                        (va_list*)msgArguments);
1740 
1741     MessageBoxW(hwnd, msg, NULL, MB_ICONERROR);
1742 }
1743 
1744 static WCHAR *expand_environment( const WCHAR *str )
1745 {
1746     WCHAR *buf;
1747     DWORD len;
1748 
1749     len = ExpandEnvironmentStringsW(str, NULL, 0);
1750     if (!len) return NULL;
1751 
1752     buf = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1753     if (!buf) return NULL;
1754 
1755     len = ExpandEnvironmentStringsW(str, buf, len);
1756     if (!len)
1757     {
1758         HeapFree(GetProcessHeap(), 0, buf);
1759         return NULL;
1760     }
1761     return buf;
1762 }
1763 
1764 /*************************************************************************
1765  *    SHELL_execute [Internal]
1766  */
1767 static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
1768 {
1769     static const DWORD unsupportedFlags =
1770         SEE_MASK_INVOKEIDLIST  | SEE_MASK_ICON         | SEE_MASK_HOTKEY |
1771         SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
1772         SEE_MASK_UNICODE       | SEE_MASK_ASYNCOK      | SEE_MASK_HMONITOR;
1773 
1774     WCHAR parametersBuffer[1024], dirBuffer[MAX_PATH], wcmdBuffer[1024];
1775     WCHAR *wszApplicationName, *wszParameters, *wszDir, *wcmd;
1776     DWORD dwApplicationNameLen = MAX_PATH + 2;
1777     DWORD parametersLen = sizeof(parametersBuffer) / sizeof(WCHAR);
1778     DWORD dirLen = sizeof(dirBuffer) / sizeof(WCHAR);
1779     DWORD wcmdLen = sizeof(wcmdBuffer) / sizeof(WCHAR);
1780     DWORD len;
1781     SHELLEXECUTEINFOW sei_tmp;    /* modifiable copy of SHELLEXECUTEINFO struct */
1782     WCHAR wfileName[MAX_PATH];
1783     WCHAR *env;
1784     WCHAR wszKeyname[256];
1785     LPCWSTR lpFile;
1786     UINT_PTR retval = SE_ERR_NOASSOC;
1787     BOOL appKnownSingular = FALSE;
1788 
1789     /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */
1790     sei_tmp = *sei;
1791 
1792     TRACE("mask=0x%08x hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
1793           sei_tmp.fMask, sei_tmp.hwnd, debugstr_w(sei_tmp.lpVerb),
1794           debugstr_w(sei_tmp.lpFile), debugstr_w(sei_tmp.lpParameters),
1795           debugstr_w(sei_tmp.lpDirectory), sei_tmp.nShow,
1796           ((sei_tmp.fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME) ?
1797           debugstr_w(sei_tmp.lpClass) : "not used");
1798 
1799     sei->hProcess = NULL;
1800 
1801     /* make copies of all path/command strings */
1802     if (!sei_tmp.lpFile)
1803     {
1804         wszApplicationName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwApplicationNameLen * sizeof(WCHAR));
1805         *wszApplicationName = '\0';
1806     }
1807     else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"')
1808     {
1809         if(len-1 >= dwApplicationNameLen)
1810             dwApplicationNameLen = len;
1811 
1812         wszApplicationName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwApplicationNameLen * sizeof(WCHAR));
1813         memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR));
1814 
1815         if(len > 2)
1816             wszApplicationName[len-2] = '\0';
1817         appKnownSingular = TRUE;
1818 
1819         TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName));
1820     }
1821     else
1822     {
1823         DWORD l = strlenW(sei_tmp.lpFile) + 1;
1824         if(l > dwApplicationNameLen) dwApplicationNameLen = l + 1;
1825         wszApplicationName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwApplicationNameLen * sizeof(WCHAR));
1826         memcpy(wszApplicationName, sei_tmp.lpFile, l * sizeof(WCHAR));
1827 
1828         if (wszApplicationName[2] == 0 && wszApplicationName[1] == L':' &&
1829             ((L'A' <= wszApplicationName[0] && wszApplicationName[0] <= L'Z') ||
1830              (L'a' <= wszApplicationName[0] && wszApplicationName[0] <= L'z')))
1831         {
1832             // 'C:' --> 'C:\'
1833             PathAddBackslashW(wszApplicationName);
1834         }
1835     }
1836 
1837     wszParameters = parametersBuffer;
1838     if (sei_tmp.lpParameters)
1839     {
1840         len = lstrlenW(sei_tmp.lpParameters) + 1;
1841         if (len > parametersLen)
1842         {
1843             wszParameters = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1844             parametersLen = len;
1845         }
1846         strcpyW(wszParameters, sei_tmp.lpParameters);
1847     }
1848     else
1849         *wszParameters = L'\0';
1850 
1851     wszDir = dirBuffer;
1852     if (sei_tmp.lpDirectory)
1853     {
1854         len = lstrlenW(sei_tmp.lpDirectory) + 1;
1855         if (len > dirLen)
1856         {
1857             wszDir = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1858             dirLen = len;
1859         }
1860         strcpyW(wszDir, sei_tmp.lpDirectory);
1861     }
1862     else
1863         *wszDir = L'\0';
1864 
1865     /* adjust string pointers to point to the new buffers */
1866     sei_tmp.lpFile = wszApplicationName;
1867     sei_tmp.lpParameters = wszParameters;
1868     sei_tmp.lpDirectory = wszDir;
1869 
1870     if (sei_tmp.fMask & unsupportedFlags)
1871     {
1872         // SEE_MASK_IDLIST is not in unsupportedFlags, but the check above passes because SEE_MASK_INVOKEIDLIST is in it
1873         if ((sei_tmp.fMask & unsupportedFlags) != SEE_MASK_IDLIST)
1874         {
1875             FIXME("flags ignored: 0x%08x\n", sei_tmp.fMask & unsupportedFlags);
1876         }
1877     }
1878 
1879     /* process the IDList */
1880     if (sei_tmp.fMask & SEE_MASK_IDLIST)
1881     {
1882         CComPtr<IShellExecuteHookW> pSEH;
1883 
1884         HRESULT hr = SHBindToParent((LPCITEMIDLIST)sei_tmp.lpIDList, IID_PPV_ARG(IShellExecuteHookW, &pSEH), NULL);
1885 
1886         if (SUCCEEDED(hr))
1887         {
1888             hr = pSEH->Execute(&sei_tmp);
1889 
1890             if (hr == S_OK)
1891             {
1892                 HeapFree(GetProcessHeap(), 0, wszApplicationName);
1893                 if (wszParameters != parametersBuffer)
1894                     HeapFree(GetProcessHeap(), 0, wszParameters);
1895                 if (wszDir != dirBuffer)
1896                     HeapFree(GetProcessHeap(), 0, wszDir);
1897                 return TRUE;
1898             }
1899         }
1900 
1901         SHGetPathFromIDListW((LPCITEMIDLIST)sei_tmp.lpIDList, wszApplicationName);
1902         appKnownSingular = TRUE;
1903         TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
1904     }
1905 
1906     if (sei_tmp.fMask & SEE_MASK_DOENVSUBST)
1907     {
1908         WCHAR *tmp;
1909 
1910         tmp = expand_environment(sei_tmp.lpFile);
1911         if (!tmp)
1912         {
1913             return FALSE;
1914         }
1915         HeapFree(GetProcessHeap(), 0, wszApplicationName);
1916         sei_tmp.lpFile = wszApplicationName = tmp;
1917 
1918         tmp = expand_environment(sei_tmp.lpDirectory);
1919         if (!tmp)
1920         {
1921             return FALSE;
1922         }
1923         if (wszDir != dirBuffer) HeapFree(GetProcessHeap(), 0, wszDir);
1924         sei_tmp.lpDirectory = wszDir = tmp;
1925     }
1926 
1927     if (ERROR_SUCCESS == ShellExecute_FromContextMenu(&sei_tmp))
1928     {
1929         sei->hInstApp = (HINSTANCE) 33;
1930         HeapFree(GetProcessHeap(), 0, wszApplicationName);
1931         if (wszParameters != parametersBuffer)
1932             HeapFree(GetProcessHeap(), 0, wszParameters);
1933         if (wszDir != dirBuffer)
1934             HeapFree(GetProcessHeap(), 0, wszDir);
1935         return TRUE;
1936     }
1937 
1938     if (sei_tmp.fMask & SEE_MASK_CLASSALL)
1939     {
1940         retval = SHELL_execute_class(wszApplicationName, &sei_tmp, sei, execfunc);
1941         if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
1942         {
1943             OPENASINFO Info;
1944 
1945             //FIXME
1946             // need full path
1947 
1948             Info.pcszFile = wszApplicationName;
1949             Info.pcszClass = NULL;
1950             Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
1951 
1952             //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
1953             DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
1954             do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
1955         }
1956         HeapFree(GetProcessHeap(), 0, wszApplicationName);
1957         if (wszParameters != parametersBuffer)
1958             HeapFree(GetProcessHeap(), 0, wszParameters);
1959         if (wszDir != dirBuffer)
1960             HeapFree(GetProcessHeap(), 0, wszDir);
1961         return retval > 32;
1962     }
1963 
1964     /* Has the IDList not yet been translated? */
1965     if (sei_tmp.fMask & SEE_MASK_IDLIST)
1966     {
1967         appKnownSingular = SHELL_translate_idlist( &sei_tmp, wszParameters,
1968                            parametersLen,
1969                            wszApplicationName,
1970                            dwApplicationNameLen );
1971     }
1972 
1973     /* convert file URLs */
1974     if (UrlIsFileUrlW(sei_tmp.lpFile))
1975     {
1976         LPWSTR buf;
1977         DWORD size;
1978 
1979         size = MAX_PATH;
1980         buf = static_cast<LPWSTR>(HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)));
1981         if (!buf || FAILED(PathCreateFromUrlW(sei_tmp.lpFile, buf, &size, 0)))
1982         {
1983             HeapFree(GetProcessHeap(), 0, buf);
1984             return SE_ERR_OOM;
1985         }
1986 
1987         HeapFree(GetProcessHeap(), 0, wszApplicationName);
1988         wszApplicationName = buf;
1989         sei_tmp.lpFile = wszApplicationName;
1990     }
1991     else /* or expand environment strings (not both!) */
1992     {
1993         len = ExpandEnvironmentStringsW(sei_tmp.lpFile, NULL, 0);
1994         if (len > 0)
1995         {
1996             LPWSTR buf;
1997             buf = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
1998 
1999             ExpandEnvironmentStringsW(sei_tmp.lpFile, buf, len + 1);
2000             HeapFree(GetProcessHeap(), 0, wszApplicationName);
2001             wszApplicationName = buf;
2002             /* appKnownSingular unmodified */
2003 
2004             sei_tmp.lpFile = wszApplicationName;
2005         }
2006     }
2007 
2008     if (*sei_tmp.lpDirectory)
2009     {
2010         len = ExpandEnvironmentStringsW(sei_tmp.lpDirectory, NULL, 0);
2011         if (len > 0)
2012         {
2013             LPWSTR buf;
2014             len++;
2015             buf = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2016             ExpandEnvironmentStringsW(sei_tmp.lpDirectory, buf, len);
2017             if (wszDir != dirBuffer)
2018                 HeapFree(GetProcessHeap(), 0, wszDir);
2019             wszDir = buf;
2020             sei_tmp.lpDirectory = wszDir;
2021         }
2022     }
2023 
2024     /* Else, try to execute the filename */
2025     TRACE("execute: %s,%s,%s\n", debugstr_w(wszApplicationName), debugstr_w(wszParameters), debugstr_w(wszDir));
2026 
2027     /* separate out command line arguments from executable file name */
2028     if (!*sei_tmp.lpParameters && !appKnownSingular)
2029     {
2030         /* If the executable path is quoted, handle the rest of the command line as parameters. */
2031         if (sei_tmp.lpFile[0] == L'"')
2032         {
2033             LPWSTR src = wszApplicationName/*sei_tmp.lpFile*/ + 1;
2034             LPWSTR dst = wfileName;
2035             LPWSTR end;
2036 
2037             /* copy the unquoted executable path to 'wfileName' */
2038             while(*src && *src != L'"')
2039                 *dst++ = *src++;
2040 
2041             *dst = L'\0';
2042 
2043             if (*src == L'"')
2044             {
2045                 end = ++src;
2046 
2047                 while(isspaceW(*src))
2048                     ++src;
2049             }
2050             else
2051                 end = src;
2052 
2053             /* copy the parameter string to 'wszParameters' */
2054             strcpyW(wszParameters, src);
2055 
2056             /* terminate previous command string after the quote character */
2057             *end = L'\0';
2058             lpFile = wfileName;
2059         }
2060         else
2061         {
2062             lpFile = sei_tmp.lpFile;
2063         }
2064     }
2065     else
2066         lpFile = sei_tmp.lpFile;
2067 
2068     wcmd = wcmdBuffer;
2069     len = lstrlenW(wszApplicationName) + 3;
2070     if (sei_tmp.lpParameters[0])
2071         len += 1 + lstrlenW(wszParameters);
2072     if (len > wcmdLen)
2073     {
2074         wcmd = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2075         wcmdLen = len;
2076     }
2077     swprintf(wcmd, L"\"%s\"", wszApplicationName);
2078     if (sei_tmp.lpParameters[0])
2079     {
2080         strcatW(wcmd, L" ");
2081         strcatW(wcmd, wszParameters);
2082     }
2083 
2084     retval = execfunc(wcmd, NULL, FALSE, &sei_tmp, sei);
2085     if (retval > 32)
2086     {
2087         HeapFree(GetProcessHeap(), 0, wszApplicationName);
2088         if (wszParameters != parametersBuffer)
2089             HeapFree(GetProcessHeap(), 0, wszParameters);
2090         if (wszDir != dirBuffer)
2091             HeapFree(GetProcessHeap(), 0, wszDir);
2092         if (wcmd != wcmdBuffer)
2093             HeapFree(GetProcessHeap(), 0, wcmd);
2094         return TRUE;
2095     }
2096 
2097     /* Else, try to find the executable */
2098     wcmd[0] = L'\0';
2099     retval = SHELL_FindExecutable(sei_tmp.lpDirectory, lpFile, sei_tmp.lpVerb, wcmd, wcmdLen, wszKeyname, &env, (LPITEMIDLIST)sei_tmp.lpIDList, sei_tmp.lpParameters);
2100     if (retval > 32)  /* Found */
2101     {
2102         retval = SHELL_quote_and_execute(wcmd, wszParameters, wszKeyname,
2103                                          wszApplicationName, env, &sei_tmp,
2104                                          sei, execfunc);
2105         HeapFree(GetProcessHeap(), 0, env);
2106     }
2107     else if (PathIsDirectoryW(lpFile))
2108     {
2109         WCHAR wExec[MAX_PATH];
2110         WCHAR * lpQuotedFile = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (strlenW(lpFile) + 3));
2111 
2112         if (lpQuotedFile)
2113         {
2114             retval = SHELL_FindExecutable(sei_tmp.lpDirectory, L"explorer",
2115                                           wszOpen, wExec, MAX_PATH,
2116                                           NULL, &env, NULL, NULL);
2117             if (retval > 32)
2118             {
2119                 swprintf(lpQuotedFile, L"\"%s\"", lpFile);
2120                 retval = SHELL_quote_and_execute(wExec, lpQuotedFile,
2121                                                  wszKeyname,
2122                                                  wszApplicationName, env,
2123                                                  &sei_tmp, sei, execfunc);
2124                 HeapFree(GetProcessHeap(), 0, env);
2125             }
2126             HeapFree(GetProcessHeap(), 0, lpQuotedFile);
2127         }
2128         else
2129             retval = 0; /* Out of memory */
2130     }
2131     else if (PathIsURLW(lpFile))    /* File not found, check for URL */
2132     {
2133         retval = SHELL_execute_url(lpFile, wcmd, &sei_tmp, sei, execfunc );
2134     }
2135     /* Check if file specified is in the form www.??????.*** */
2136     else if (!strncmpiW(lpFile, L"www", 3))
2137     {
2138         /* if so, prefix lpFile with http:// and call ShellExecute */
2139         WCHAR lpstrTmpFile[256];
2140         strcpyW(lpstrTmpFile, L"http://");
2141         strcatW(lpstrTmpFile, lpFile);
2142         retval = (UINT_PTR)ShellExecuteW(sei_tmp.hwnd, sei_tmp.lpVerb, lpstrTmpFile, NULL, NULL, 0);
2143     }
2144 
2145     TRACE("retval %lu\n", retval);
2146 
2147     if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2148     {
2149         OPENASINFO Info;
2150 
2151         //FIXME
2152         // need full path
2153 
2154         Info.pcszFile = wszApplicationName;
2155         Info.pcszClass = NULL;
2156         Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2157 
2158         //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2159         DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2160         do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2161     }
2162 
2163     HeapFree(GetProcessHeap(), 0, wszApplicationName);
2164     if (wszParameters != parametersBuffer)
2165         HeapFree(GetProcessHeap(), 0, wszParameters);
2166     if (wszDir != dirBuffer)
2167         HeapFree(GetProcessHeap(), 0, wszDir);
2168     if (wcmd != wcmdBuffer)
2169         HeapFree(GetProcessHeap(), 0, wcmd);
2170 
2171     sei->hInstApp = (HINSTANCE)(retval > 32 ? 33 : retval);
2172 
2173     return retval > 32;
2174 }
2175 
2176 /*************************************************************************
2177  * ShellExecuteA            [SHELL32.290]
2178  */
2179 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2180                                LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd)
2181 {
2182     SHELLEXECUTEINFOA sei;
2183 
2184     TRACE("%p,%s,%s,%s,%s,%d\n",
2185           hWnd, debugstr_a(lpVerb), debugstr_a(lpFile),
2186           debugstr_a(lpParameters), debugstr_a(lpDirectory), iShowCmd);
2187 
2188     sei.cbSize = sizeof(sei);
2189     sei.fMask = SEE_MASK_FLAG_NO_UI;
2190     sei.hwnd = hWnd;
2191     sei.lpVerb = lpVerb;
2192     sei.lpFile = lpFile;
2193     sei.lpParameters = lpParameters;
2194     sei.lpDirectory = lpDirectory;
2195     sei.nShow = iShowCmd;
2196     sei.lpIDList = 0;
2197     sei.lpClass = 0;
2198     sei.hkeyClass = 0;
2199     sei.dwHotKey = 0;
2200     sei.hProcess = 0;
2201 
2202     ShellExecuteExA(&sei);
2203     return sei.hInstApp;
2204 }
2205 
2206 /*************************************************************************
2207  * ShellExecuteExA                [SHELL32.292]
2208  *
2209  */
2210 BOOL
2211 WINAPI
2212 DECLSPEC_HOTPATCH
2213 ShellExecuteExA(LPSHELLEXECUTEINFOA sei)
2214 {
2215     SHELLEXECUTEINFOW seiW;
2216     BOOL ret;
2217     WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL;
2218 
2219     TRACE("%p\n", sei);
2220 
2221     memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW));
2222 
2223     if (sei->lpVerb)
2224         seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb);
2225 
2226     if (sei->lpFile)
2227         seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile);
2228 
2229     if (sei->lpParameters)
2230         seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters);
2231 
2232     if (sei->lpDirectory)
2233         seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory);
2234 
2235     if ((sei->fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME && sei->lpClass)
2236         seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass);
2237     else
2238         seiW.lpClass = NULL;
2239 
2240     ret = SHELL_execute(&seiW, SHELL_ExecuteW);
2241 
2242     sei->hInstApp = seiW.hInstApp;
2243 
2244     if (sei->fMask & SEE_MASK_NOCLOSEPROCESS)
2245         sei->hProcess = seiW.hProcess;
2246 
2247     SHFree(wVerb);
2248     SHFree(wFile);
2249     SHFree(wParameters);
2250     SHFree(wDirectory);
2251     SHFree(wClass);
2252 
2253     return ret;
2254 }
2255 
2256 /*************************************************************************
2257  * ShellExecuteExW                [SHELL32.293]
2258  *
2259  */
2260 BOOL
2261 WINAPI
2262 DECLSPEC_HOTPATCH
2263 ShellExecuteExW(LPSHELLEXECUTEINFOW sei)
2264 {
2265     return SHELL_execute(sei, SHELL_ExecuteW);
2266 }
2267 
2268 /*************************************************************************
2269  * ShellExecuteW            [SHELL32.294]
2270  * from shellapi.h
2271  * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpVerb,
2272  * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
2273  */
2274 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, LPCWSTR lpFile,
2275                                LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
2276 {
2277     SHELLEXECUTEINFOW sei;
2278 
2279     TRACE("\n");
2280     sei.cbSize = sizeof(sei);
2281     sei.fMask = SEE_MASK_FLAG_NO_UI;
2282     sei.hwnd = hwnd;
2283     sei.lpVerb = lpVerb;
2284     sei.lpFile = lpFile;
2285     sei.lpParameters = lpParameters;
2286     sei.lpDirectory = lpDirectory;
2287     sei.nShow = nShowCmd;
2288     sei.lpIDList = 0;
2289     sei.lpClass = 0;
2290     sei.hkeyClass = 0;
2291     sei.dwHotKey = 0;
2292     sei.hProcess = 0;
2293 
2294     SHELL_execute(&sei, SHELL_ExecuteW);
2295     return sei.hInstApp;
2296 }
2297 
2298 /*************************************************************************
2299  * WOWShellExecute            [SHELL32.@]
2300  *
2301  * FIXME: the callback function most likely doesn't work the same way on Windows.
2302  */
2303 EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2304         LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd, void *callback)
2305 {
2306     SHELLEXECUTEINFOW seiW;
2307     WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL;
2308     HANDLE hProcess = 0;
2309 
2310     seiW.lpVerb = lpVerb ? __SHCloneStrAtoW(&wVerb, lpVerb) : NULL;
2311     seiW.lpFile = lpFile ? __SHCloneStrAtoW(&wFile, lpFile) : NULL;
2312     seiW.lpParameters = lpParameters ? __SHCloneStrAtoW(&wParameters, lpParameters) : NULL;
2313     seiW.lpDirectory = lpDirectory ? __SHCloneStrAtoW(&wDirectory, lpDirectory) : NULL;
2314 
2315     seiW.cbSize = sizeof(seiW);
2316     seiW.fMask = 0;
2317     seiW.hwnd = hWnd;
2318     seiW.nShow = iShowCmd;
2319     seiW.lpIDList = 0;
2320     seiW.lpClass = 0;
2321     seiW.hkeyClass = 0;
2322     seiW.dwHotKey = 0;
2323     seiW.hProcess = hProcess;
2324 
2325     SHELL_execute(&seiW, (SHELL_ExecuteW32)callback);
2326 
2327     SHFree(wVerb);
2328     SHFree(wFile);
2329     SHFree(wParameters);
2330     SHFree(wDirectory);
2331     return seiW.hInstApp;
2332 }
2333 
2334 /*************************************************************************
2335  * OpenAs_RunDLLW          [SHELL32.@]
2336  */
2337 EXTERN_C void WINAPI
2338 OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
2339 {
2340     OPENASINFO info;
2341     TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
2342 
2343     ZeroMemory(&info, sizeof(info));
2344     info.pcszFile = cmdline;
2345     info.pcszClass = NULL;
2346     info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
2347 
2348     SHOpenWithDialog(hwnd, &info);
2349 }
2350 
2351 /*************************************************************************
2352  * OpenAs_RunDLLA          [SHELL32.@]
2353  */
2354 EXTERN_C void WINAPI
2355 OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
2356 {
2357     LPWSTR pszCmdLineW = NULL;
2358     TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
2359 
2360     if (cmdline)
2361         __SHCloneStrAtoW(&pszCmdLineW, cmdline);
2362     OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow);
2363     SHFree(pszCmdLineW);
2364 }
2365 
2366 /*************************************************************************/
2367 
2368 static LPCWSTR
2369 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
2370 {
2371     LPCWSTR pch;
2372     size_t ich = 0;
2373     if (*psz == L'"')
2374     {
2375         // 1st argument is quoted. the string in quotes is quoted 1st argument.
2376         // [pch] --> [pszArg0+ich]
2377         for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
2378         {
2379             if (*pch == L'"' && pch[1] == L'"')
2380             {
2381                 // doubled double quotations found!
2382                 pszArg0[ich] = L'"';
2383             }
2384             else if (*pch == L'"')
2385             {
2386                 // single double quotation found!
2387                 ++pch;
2388                 break;
2389             }
2390             else
2391             {
2392                 // otherwise
2393                 pszArg0[ich] = *pch;
2394             }
2395         }
2396     }
2397     else
2398     {
2399         // 1st argument is unquoted. non-space sequence is 1st argument.
2400         // [pch] --> [pszArg0+ich]
2401         for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
2402         {
2403             pszArg0[ich] = *pch;
2404         }
2405     }
2406     pszArg0[ich] = 0;
2407 
2408     // skip space
2409     while (iswspace(*pch))
2410         ++pch;
2411 
2412     return pch;
2413 }
2414 
2415 HRESULT WINAPI ShellExecCmdLine(
2416     HWND hwnd,
2417     LPCWSTR pwszCommand,
2418     LPCWSTR pwszStartDir,
2419     int nShow,
2420     LPVOID pUnused,
2421     DWORD dwSeclFlags)
2422 {
2423     SHELLEXECUTEINFOW info;
2424     DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
2425     LPCWSTR pszVerb = NULL;
2426     WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
2427     HRESULT hr;
2428     LPCWSTR pchParams;
2429     LPWSTR lpCommand = NULL;
2430 
2431     if (pwszCommand == NULL)
2432         RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
2433                        1, (ULONG_PTR*)pwszCommand);
2434 
2435     __SHCloneStrW(&lpCommand, pwszCommand);
2436     StrTrimW(lpCommand, L" \t");
2437 
2438     if (dwSeclFlags & SECL_NO_UI)
2439         dwFlags |= SEE_MASK_FLAG_NO_UI;
2440     if (dwSeclFlags & SECL_LOG_USAGE)
2441         dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
2442     if (dwSeclFlags & SECL_USE_IDLIST)
2443         dwFlags |= SEE_MASK_INVOKEIDLIST;
2444 
2445     if (dwSeclFlags & SECL_RUNAS)
2446     {
2447         dwSize = 0;
2448         hr = AssocQueryStringW(0, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
2449         if (SUCCEEDED(hr) && dwSize != 0)
2450         {
2451             pszVerb = L"runas";
2452         }
2453     }
2454 
2455     if (PathIsURLW(lpCommand) || UrlIsW(lpCommand, URLIS_APPLIABLE))
2456     {
2457         StringCchCopyW(szFile, _countof(szFile), lpCommand);
2458         pchParams = NULL;
2459     }
2460     else
2461     {
2462         pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
2463         if (szFile[0] != UNICODE_NULL && szFile[1] == L':' &&
2464             szFile[2] == UNICODE_NULL)
2465         {
2466             PathAddBackslashW(szFile);
2467         }
2468 
2469         WCHAR szCurDir[MAX_PATH];
2470         GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
2471         if (pwszStartDir)
2472         {
2473             SetCurrentDirectoryW(pwszStartDir);
2474         }
2475 
2476         if (PathIsRelativeW(szFile) &&
2477             GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) &&
2478             PathFileExistsW(szFile2))
2479         {
2480             StringCchCopyW(szFile, _countof(szFile), szFile2);
2481         }
2482         else if (SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
2483                  SearchPathW(NULL, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
2484                  SearchPathW(NULL, szFile, wszCom, _countof(szFile2), szFile2, NULL) ||
2485                  SearchPathW(pwszStartDir, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
2486                  SearchPathW(pwszStartDir, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
2487                  SearchPathW(pwszStartDir, szFile, wszCom, _countof(szFile2), szFile2, NULL))
2488         {
2489             StringCchCopyW(szFile, _countof(szFile), szFile2);
2490         }
2491         else if (SearchPathW(NULL, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
2492                  SearchPathW(NULL, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
2493                  SearchPathW(NULL, lpCommand, wszCom, _countof(szFile2), szFile2, NULL) ||
2494                  SearchPathW(pwszStartDir, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
2495                  SearchPathW(pwszStartDir, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
2496                  SearchPathW(pwszStartDir, lpCommand, wszCom, _countof(szFile2), szFile2, NULL))
2497         {
2498             StringCchCopyW(szFile, _countof(szFile), szFile2);
2499             pchParams = NULL;
2500         }
2501 
2502         if (pwszStartDir)
2503         {
2504             SetCurrentDirectoryW(szCurDir);
2505         }
2506 
2507         if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
2508         {
2509             if (!GetBinaryTypeW(szFile, &dwType))
2510             {
2511                 SHFree(lpCommand);
2512 
2513                 if (!(dwSeclFlags & SECL_NO_UI))
2514                 {
2515                     WCHAR szText[128 + MAX_PATH], szFormat[128];
2516                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2517                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2518                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2519                 }
2520                 return CO_E_APPNOTFOUND;
2521             }
2522         }
2523         else
2524         {
2525             if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
2526             {
2527                 SHFree(lpCommand);
2528 
2529                 if (!(dwSeclFlags & SECL_NO_UI))
2530                 {
2531                     WCHAR szText[128 + MAX_PATH], szFormat[128];
2532                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2533                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2534                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2535                 }
2536                 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
2537             }
2538         }
2539     }
2540 
2541     ZeroMemory(&info, sizeof(info));
2542     info.cbSize = sizeof(info);
2543     info.fMask = dwFlags;
2544     info.hwnd = hwnd;
2545     info.lpVerb = pszVerb;
2546     info.lpFile = szFile;
2547     info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
2548     info.lpDirectory = pwszStartDir;
2549     info.nShow = nShow;
2550     if (ShellExecuteExW(&info))
2551     {
2552         if (info.lpIDList)
2553             CoTaskMemFree(info.lpIDList);
2554 
2555         SHFree(lpCommand);
2556 
2557         return S_OK;
2558     }
2559 
2560     dwError = GetLastError();
2561 
2562     SHFree(lpCommand);
2563 
2564     return HRESULT_FROM_WIN32(dwError);
2565 }
2566