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