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