xref: /reactos/dll/win32/shell32/utils.cpp (revision 0c2cdcae)
1 /*
2  * PROJECT:     shell32
3  * LICENSE:     LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4  * PURPOSE:     Utility functions
5  * COPYRIGHT:   Copyright 2023-2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 
10 WINE_DEFAULT_DEBUG_CHANNEL(shell);
11 
12 HRESULT
13 SHILClone(
14     _In_opt_ LPCITEMIDLIST pidl,
15     _Outptr_ LPITEMIDLIST *ppidl)
16 {
17     if (!pidl)
18     {
19         *ppidl = NULL;
20         return S_OK;
21     }
22     *ppidl = ILClone(pidl);
23     return (*ppidl ? S_OK : E_OUTOFMEMORY);
24 }
25 
26 BOOL PathIsDotOrDotDotW(_In_ LPCWSTR pszPath)
27 {
28     if (pszPath[0] != L'.')
29         return FALSE;
30     return !pszPath[1] || (pszPath[1] == L'.' && !pszPath[2]);
31 }
32 
33 #define PATH_VALID_ELEMENT ( \
34     PATH_CHAR_CLASS_DOT | PATH_CHAR_CLASS_SEMICOLON | PATH_CHAR_CLASS_COMMA | \
35     PATH_CHAR_CLASS_SPACE | PATH_CHAR_CLASS_OTHER_VALID \
36 )
37 
38 BOOL PathIsValidElement(_In_ LPCWSTR pszPath)
39 {
40     if (!*pszPath || PathIsDotOrDotDotW(pszPath))
41         return FALSE;
42 
43     for (LPCWSTR pch = pszPath; *pch; ++pch)
44     {
45         if (!PathIsValidCharW(*pch, PATH_VALID_ELEMENT))
46             return FALSE;
47     }
48 
49     return TRUE;
50 }
51 
52 BOOL PathIsDosDevice(_In_ LPCWSTR pszName)
53 {
54     WCHAR szPath[MAX_PATH];
55     StringCchCopyW(szPath, _countof(szPath), pszName);
56     PathRemoveExtensionW(szPath);
57 
58     if (lstrcmpiW(szPath, L"NUL") == 0 || lstrcmpiW(szPath, L"PRN") == 0 ||
59         lstrcmpiW(szPath, L"CON") == 0 || lstrcmpiW(szPath, L"AUX") == 0)
60     {
61         return TRUE;
62     }
63 
64     if (_wcsnicmp(szPath, L"LPT", 3) == 0 || _wcsnicmp(szPath, L"COM", 3) == 0)
65     {
66         if ((L'0' <= szPath[3] && szPath[3] <= L'9') && szPath[4] == UNICODE_NULL)
67             return TRUE;
68     }
69 
70     return FALSE;
71 }
72 
73 HRESULT SHILAppend(_Inout_ LPITEMIDLIST pidl, _Inout_ LPITEMIDLIST *ppidl)
74 {
75     LPITEMIDLIST pidlOld = *ppidl;
76     if (!pidlOld)
77     {
78         *ppidl = pidl;
79         return S_OK;
80     }
81 
82     HRESULT hr = SHILCombine(*ppidl, pidl, ppidl);
83     ILFree(pidlOld);
84     ILFree(pidl);
85     return hr;
86 }
87 
88 static BOOL
89 OpenEffectiveToken(
90     _In_ DWORD DesiredAccess,
91     _Out_ HANDLE *phToken)
92 {
93     BOOL ret;
94 
95     if (phToken == NULL)
96     {
97         SetLastError(ERROR_INVALID_PARAMETER);
98         return FALSE;
99     }
100 
101     *phToken = NULL;
102 
103     ret = OpenThreadToken(GetCurrentThread(), DesiredAccess, FALSE, phToken);
104     if (!ret && GetLastError() == ERROR_NO_TOKEN)
105         ret = OpenProcessToken(GetCurrentProcess(), DesiredAccess, phToken);
106 
107     return ret;
108 }
109 
110 HRESULT
111 Shell_TranslateIDListAlias(
112     _In_ LPCITEMIDLIST pidl,
113     _In_ HANDLE hToken,
114     _Out_ LPITEMIDLIST *ppidlAlias,
115     _In_ DWORD dwFlags)
116 {
117     return E_FAIL; //FIXME
118 }
119 
120 BOOL BindCtx_ContainsObject(_In_ IBindCtx *pBindCtx, _In_ LPCWSTR pszName)
121 {
122     CComPtr<IUnknown> punk;
123     if (!pBindCtx || FAILED(pBindCtx->GetObjectParam(const_cast<LPWSTR>(pszName), &punk)))
124         return FALSE;
125     return TRUE;
126 }
127 
128 DWORD BindCtx_GetMode(_In_ IBindCtx *pbc, _In_ DWORD dwDefault)
129 {
130     if (!pbc)
131         return dwDefault;
132 
133     BIND_OPTS BindOpts = { sizeof(BindOpts) };
134     HRESULT hr = pbc->GetBindOptions(&BindOpts);
135     if (FAILED(hr))
136         return dwDefault;
137 
138     return BindOpts.grfMode;
139 }
140 
141 BOOL SHSkipJunctionBinding(_In_ IBindCtx *pbc, _In_ CLSID *pclsid)
142 {
143     if (!pbc)
144         return FALSE;
145 
146     BIND_OPTS BindOps = { sizeof(BindOps) };
147     if (SUCCEEDED(pbc->GetBindOptions(&BindOps)) && BindOps.grfFlags == OLECONTF_LINKS)
148         return TRUE;
149 
150     return pclsid && SHSkipJunction(pbc, pclsid);
151 }
152 
153 HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW *pFindData)
154 {
155     CComPtr<IUnknown> punk;
156     CComPtr<IFileSystemBindData> pBindData;
157 
158     if (!pBindCtx || FAILED(pBindCtx->GetObjectParam((LPWSTR)STR_FILE_SYS_BIND_DATA, &punk)))
159         return S_FALSE;
160 
161     if (FAILED(punk->QueryInterface(IID_PPV_ARG(IFileSystemBindData, &pBindData))))
162         return S_FALSE;
163 
164     if (pFindData)
165         pBindData->GetFindData(pFindData);
166 
167     return S_OK;
168 }
169 
170 BOOL Shell_FailForceReturn(_In_ HRESULT hr)
171 {
172     DWORD code = HRESULT_CODE(hr);
173 
174     switch (code)
175     {
176         case ERROR_BAD_NETPATH:
177         case ERROR_BAD_NET_NAME:
178         case ERROR_CANCELLED:
179             return TRUE;
180 
181         default:
182             return (ERROR_FILE_NOT_FOUND <= code && code <= ERROR_PATH_NOT_FOUND);
183     }
184 }
185 
186 HRESULT
187 SHBindToObjectEx(
188     _In_opt_ IShellFolder *pShellFolder,
189     _In_ LPCITEMIDLIST pidl,
190     _In_opt_ IBindCtx *pBindCtx,
191     _In_ REFIID riid,
192     _Out_ void **ppvObj)
193 {
194     CComPtr<IShellFolder> psfDesktop;
195 
196     *ppvObj = NULL;
197 
198     if (!pShellFolder)
199     {
200         SHGetDesktopFolder(&psfDesktop);
201         if (!psfDesktop)
202             return E_FAIL;
203 
204         pShellFolder = psfDesktop;
205     }
206 
207     HRESULT hr;
208     if (_ILIsDesktop(pidl))
209         hr = pShellFolder->QueryInterface(riid, ppvObj);
210     else
211         hr = pShellFolder->BindToObject(pidl, pBindCtx, riid, ppvObj);
212 
213     if (SUCCEEDED(hr) && !*ppvObj)
214         hr = E_FAIL;
215 
216     return hr;
217 }
218 
219 EXTERN_C
220 HRESULT SHBindToObject(
221     _In_opt_ IShellFolder *psf,
222     _In_ LPCITEMIDLIST pidl,
223     _In_ REFIID riid,
224     _Out_ void **ppvObj)
225 {
226     return SHBindToObjectEx(psf, pidl, NULL, riid, ppvObj);
227 }
228 
229 HRESULT
230 Shell_DisplayNameOf(
231     _In_ IShellFolder *psf,
232     _In_ LPCITEMIDLIST pidl,
233     _In_ DWORD dwFlags,
234     _Out_ LPWSTR pszBuf,
235     _In_ UINT cchBuf)
236 {
237     *pszBuf = UNICODE_NULL;
238     STRRET sr;
239     HRESULT hr = psf->GetDisplayNameOf(pidl, dwFlags, &sr);
240     if (FAILED(hr))
241         return hr;
242     return StrRetToBufW(&sr, pidl, pszBuf, cchBuf);
243 }
244 
245 DWORD
246 SHGetAttributes(_In_ IShellFolder *psf, _In_ LPCITEMIDLIST pidl, _In_ DWORD dwAttributes)
247 {
248     LPCITEMIDLIST pidlLast = pidl;
249     IShellFolder *release = NULL;
250 
251     if (!psf)
252     {
253         SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
254         if (!psf)
255             return 0;
256         release = psf;
257     }
258 
259     DWORD oldAttrs = dwAttributes;
260     if (FAILED(psf->GetAttributesOf(1, &pidlLast, &dwAttributes)))
261         dwAttributes = 0;
262     else
263         dwAttributes &= oldAttrs;
264 
265     if ((dwAttributes & SFGAO_FOLDER) &&
266         (dwAttributes & SFGAO_STREAM) &&
267         !(dwAttributes & SFGAO_STORAGEANCESTOR) &&
268         (oldAttrs & SFGAO_STORAGEANCESTOR) &&
269         (SHGetObjectCompatFlags(psf, NULL) & 0x200))
270     {
271         dwAttributes &= ~(SFGAO_STREAM | SFGAO_STORAGEANCESTOR);
272         dwAttributes |= SFGAO_STORAGEANCESTOR;
273     }
274 
275     if (release)
276         release->Release();
277     return dwAttributes;
278 }
279 
280 HRESULT SHCoInitializeAnyApartment(VOID)
281 {
282     HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
283     if (FAILED(hr))
284         hr = CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE);
285     return hr;
286 }
287 
288 HRESULT
289 SHGetNameAndFlagsW(
290     _In_ LPCITEMIDLIST pidl,
291     _In_ DWORD dwFlags,
292     _Out_opt_ LPWSTR pszText,
293     _In_ UINT cchBuf,
294     _Inout_opt_ DWORD *pdwAttributes)
295 {
296     if (pszText)
297         *pszText = UNICODE_NULL;
298 
299     HRESULT hrCoInit = SHCoInitializeAnyApartment();
300 
301     CComPtr<IShellFolder> psfFolder;
302     LPCITEMIDLIST ppidlLast;
303     HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &ppidlLast);
304     if (SUCCEEDED(hr))
305     {
306         if (pszText)
307             hr = Shell_DisplayNameOf(psfFolder, ppidlLast, dwFlags, pszText, cchBuf);
308 
309         if (SUCCEEDED(hr))
310         {
311             if (pdwAttributes)
312                 *pdwAttributes = SHGetAttributes(psfFolder, ppidlLast, *pdwAttributes);
313         }
314     }
315 
316     if (SUCCEEDED(hrCoInit))
317         CoUninitialize();
318 
319     return hr;
320 }
321 
322 EXTERN_C HWND
323 BindCtx_GetUIWindow(_In_ IBindCtx *pBindCtx)
324 {
325     HWND hWnd = NULL;
326 
327     CComPtr<IUnknown> punk;
328     if (pBindCtx && SUCCEEDED(pBindCtx->GetObjectParam((LPWSTR)L"UI During Binding", &punk)))
329         IUnknown_GetWindow(punk, &hWnd);
330 
331     return hWnd;
332 }
333 
334 class CDummyOleWindow : public IOleWindow
335 {
336 protected:
337     LONG m_cRefs;
338     HWND m_hWnd;
339 
340 public:
341     CDummyOleWindow() : m_cRefs(1), m_hWnd(NULL) { }
342     virtual ~CDummyOleWindow() { }
343 
344     // IUnknown methods
345     STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObj) override
346     {
347         static const QITAB c_tab[] =
348         {
349             QITABENT(CDummyOleWindow, IOleWindow),
350             { NULL }
351         };
352         return ::QISearch(this, c_tab, riid, ppvObj);
353     }
354     STDMETHODIMP_(ULONG) AddRef() override
355     {
356         return ++m_cRefs;
357     }
358     STDMETHODIMP_(ULONG) Release() override
359     {
360         if (--m_cRefs == 0)
361         {
362             delete this;
363             return 0;
364         }
365         return m_cRefs;
366     }
367 
368     // IOleWindow methods
369     STDMETHODIMP GetWindow(HWND *phWnd) override
370     {
371         *phWnd = m_hWnd;
372         if (!m_hWnd)
373             return E_NOTIMPL;
374         return S_OK;
375     }
376     STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode) override
377     {
378         return E_NOTIMPL;
379     }
380 };
381 
382 EXTERN_C HRESULT
383 BindCtx_RegisterObjectParam(
384     _In_ IBindCtx *pBindCtx,
385     _In_ LPOLESTR pszKey,
386     _In_opt_ IUnknown *punk,
387     _Out_ LPBC *ppbc)
388 {
389     HRESULT hr = S_OK;
390     CDummyOleWindow *pUnknown = NULL;
391 
392     *ppbc = pBindCtx;
393 
394     if (pBindCtx)
395     {
396         pBindCtx->AddRef();
397     }
398     else
399     {
400         hr = CreateBindCtx(0, ppbc);
401         if (FAILED(hr))
402             return hr;
403     }
404 
405     if (!punk)
406         punk = pUnknown = new CDummyOleWindow();
407 
408     hr = (*ppbc)->RegisterObjectParam(pszKey, punk);
409 
410     if (pUnknown)
411         pUnknown->Release();
412 
413     if (FAILED(hr))
414     {
415         (*ppbc)->Release();
416         *ppbc = NULL;
417     }
418 
419     return hr;
420 }
421 
422 /*************************************************************************
423  *                SHSetFolderPathA (SHELL32.231)
424  *
425  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shsetfolderpatha
426  */
427 EXTERN_C
428 HRESULT WINAPI
429 SHSetFolderPathA(
430     _In_ INT csidl,
431     _In_ HANDLE hToken,
432     _In_ DWORD dwFlags,
433     _In_ LPCSTR pszPath)
434 {
435     TRACE("(%d, %p, 0x%X, %s)\n", csidl, hToken, dwFlags, debugstr_a(pszPath));
436     CStringW strPathW(pszPath);
437     return SHSetFolderPathW(csidl, hToken, dwFlags, strPathW);
438 }
439 
440 /*************************************************************************
441  *                PathIsSlowA (SHELL32.240)
442  *
443  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-pathisslowa
444  */
445 EXTERN_C
446 BOOL WINAPI
447 PathIsSlowA(
448     _In_ LPCSTR pszFile,
449     _In_ DWORD dwAttr)
450 {
451     TRACE("(%s, 0x%X)\n", debugstr_a(pszFile), dwAttr);
452     CStringW strFileW(pszFile);
453     return PathIsSlowW(strFileW, dwAttr);
454 }
455 
456 /*************************************************************************
457  *                ExtractIconResInfoA (SHELL32.221)
458  */
459 EXTERN_C
460 WORD WINAPI
461 ExtractIconResInfoA(
462     _In_ HANDLE hHandle,
463     _In_ LPCSTR lpFileName,
464     _In_ WORD wIndex,
465     _Out_ LPWORD lpSize,
466     _Out_ LPHANDLE lpIcon)
467 {
468     TRACE("(%p, %s, %u, %p, %p)\n", hHandle, debugstr_a(lpFileName), wIndex, lpSize, lpIcon);
469 
470     if (!lpFileName)
471         return 0;
472 
473     CStringW strFileNameW(lpFileName);
474     return ExtractIconResInfoW(hHandle, strFileNameW, wIndex, lpSize, lpIcon);
475 }
476 
477 /*************************************************************************
478  *                ShortSizeFormatW (SHELL32.204)
479  */
480 EXTERN_C
481 LPWSTR WINAPI
482 ShortSizeFormatW(
483     _In_ DWORD dwNumber,
484     _Out_writes_(0x8FFF) LPWSTR pszBuffer)
485 {
486     TRACE("(%lu, %p)\n", dwNumber, pszBuffer);
487     return StrFormatByteSizeW(dwNumber, pszBuffer, 0x8FFF);
488 }
489 
490 /*************************************************************************
491  *                SHOpenEffectiveToken (SHELL32.235)
492  */
493 EXTERN_C BOOL WINAPI SHOpenEffectiveToken(_Out_ LPHANDLE phToken)
494 {
495     TRACE("%p\n", phToken);
496     return OpenEffectiveToken(TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, phToken);
497 }
498 
499 /*************************************************************************
500  *                SHGetUserSessionId (SHELL32.248)
501  */
502 EXTERN_C DWORD WINAPI SHGetUserSessionId(_In_opt_ HANDLE hToken)
503 {
504     DWORD dwSessionId, dwLength;
505     BOOL bOpenToken = FALSE;
506 
507     TRACE("%p\n", hToken);
508 
509     if (!hToken)
510         bOpenToken = SHOpenEffectiveToken(&hToken);
511 
512     if (!hToken ||
513         !GetTokenInformation(hToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId), &dwLength))
514     {
515         dwSessionId = 0;
516     }
517 
518     if (bOpenToken)
519         CloseHandle(hToken);
520 
521     return dwSessionId;
522 }
523 
524 /*************************************************************************
525  *                SHInvokePrivilegedFunctionW (SHELL32.246)
526  */
527 EXTERN_C
528 HRESULT WINAPI
529 SHInvokePrivilegedFunctionW(
530     _In_ LPCWSTR pszName,
531     _In_ PRIVILEGED_FUNCTION fn,
532     _In_opt_ LPARAM lParam)
533 {
534     TRACE("(%s %p %p)\n", debugstr_w(pszName), fn, lParam);
535 
536     if (!pszName || !fn)
537         return E_INVALIDARG;
538 
539     HANDLE hToken = NULL;
540     TOKEN_PRIVILEGES NewPriv, PrevPriv;
541     BOOL bAdjusted = FALSE;
542 
543     if (SHOpenEffectiveToken(&hToken) &&
544         ::LookupPrivilegeValueW(NULL, pszName, &NewPriv.Privileges[0].Luid))
545     {
546         NewPriv.PrivilegeCount = 1;
547         NewPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
548 
549         DWORD dwReturnSize;
550         bAdjusted = ::AdjustTokenPrivileges(hToken, FALSE, &NewPriv,
551                                             sizeof(PrevPriv), &PrevPriv, &dwReturnSize);
552     }
553 
554     HRESULT hr = fn(lParam);
555 
556     if (bAdjusted)
557         ::AdjustTokenPrivileges(hToken, FALSE, &PrevPriv, 0, NULL, NULL);
558 
559     if (hToken)
560         ::CloseHandle(hToken);
561 
562     return hr;
563 }
564 
565 /*************************************************************************
566  *                SHTestTokenPrivilegeW (SHELL32.236)
567  *
568  * @see http://undoc.airesoft.co.uk/shell32.dll/SHTestTokenPrivilegeW.php
569  */
570 EXTERN_C
571 BOOL WINAPI
572 SHTestTokenPrivilegeW(
573     _In_opt_ HANDLE hToken,
574     _In_ LPCWSTR lpName)
575 {
576     LUID Luid;
577     DWORD dwLength;
578     PTOKEN_PRIVILEGES pTokenPriv;
579     HANDLE hNewToken = NULL;
580     BOOL ret = FALSE;
581 
582     TRACE("(%p, %s)\n", hToken, debugstr_w(lpName));
583 
584     if (!lpName)
585         return FALSE;
586 
587     if (!hToken)
588     {
589         if (!SHOpenEffectiveToken(&hNewToken))
590             goto Quit;
591 
592         if (!hNewToken)
593             return FALSE;
594 
595         hToken = hNewToken;
596     }
597 
598     if (!LookupPrivilegeValueW(NULL, lpName, &Luid))
599         return FALSE;
600 
601     dwLength = 0;
602     if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwLength))
603         goto Quit;
604 
605     pTokenPriv = (PTOKEN_PRIVILEGES)LocalAlloc(LPTR, dwLength);
606     if (!pTokenPriv)
607         goto Quit;
608 
609     if (GetTokenInformation(hToken, TokenPrivileges, pTokenPriv, dwLength, &dwLength))
610     {
611         UINT iPriv, cPrivs;
612         cPrivs = pTokenPriv->PrivilegeCount;
613         for (iPriv = 0; !ret && iPriv < cPrivs; ++iPriv)
614         {
615             ret = RtlEqualLuid(&Luid, &pTokenPriv->Privileges[iPriv].Luid);
616         }
617     }
618 
619     LocalFree(pTokenPriv);
620 
621 Quit:
622     if (hToken == hNewToken)
623         CloseHandle(hNewToken);
624 
625     return ret;
626 }
627 
628 BOOL IsShutdownAllowed(VOID)
629 {
630     return SHTestTokenPrivilegeW(NULL, SE_SHUTDOWN_NAME);
631 }
632 
633 /*************************************************************************
634  *                IsSuspendAllowed (SHELL32.53)
635  */
636 BOOL WINAPI IsSuspendAllowed(VOID)
637 {
638     TRACE("()\n");
639     return IsShutdownAllowed() && IsPwrSuspendAllowed();
640 }
641 
642 /*************************************************************************
643  *                SHGetShellStyleHInstance (SHELL32.749)
644  */
645 EXTERN_C HINSTANCE
646 WINAPI
647 SHGetShellStyleHInstance(VOID)
648 {
649     HINSTANCE hInst = NULL;
650     WCHAR szPath[MAX_PATH], szColorName[100];
651     HRESULT hr;
652     CStringW strShellStyle;
653 
654     TRACE("SHGetShellStyleHInstance called\n");
655 
656     /* First, attempt to load the shellstyle dll from the current active theme */
657     hr = GetCurrentThemeName(szPath, _countof(szPath), szColorName, _countof(szColorName), NULL, 0);
658     if (FAILED(hr))
659         goto DoDefault;
660 
661     /* Strip the theme filename */
662     PathRemoveFileSpecW(szPath);
663 
664     strShellStyle = szPath;
665     strShellStyle += L"\\Shell\\";
666     strShellStyle += szColorName;
667     strShellStyle += L"\\ShellStyle.dll";
668 
669     hInst = LoadLibraryExW(strShellStyle, NULL, LOAD_LIBRARY_AS_DATAFILE);
670     if (hInst)
671         return hInst;
672 
673     /* Otherwise, use the version stored in the System32 directory */
674 DoDefault:
675     if (!ExpandEnvironmentStringsW(L"%SystemRoot%\\System32\\ShellStyle.dll",
676                                    szPath, _countof(szPath)))
677     {
678         ERR("Expand failed\n");
679         return NULL;
680     }
681     return LoadLibraryExW(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
682 }
683 
684 /*************************************************************************
685  *                SHCreatePropertyBag (SHELL32.715)
686  */
687 EXTERN_C HRESULT
688 WINAPI
689 SHCreatePropertyBag(_In_ REFIID riid, _Out_ void **ppvObj)
690 {
691     return SHCreatePropertyBagOnMemory(STGM_READWRITE, riid, ppvObj);
692 }
693 
694 /*************************************************************************
695  *                SheRemoveQuotesA (SHELL32.@)
696  */
697 EXTERN_C LPSTR
698 WINAPI
699 SheRemoveQuotesA(LPSTR psz)
700 {
701     PCHAR pch;
702 
703     if (*psz == '"')
704     {
705         for (pch = psz + 1; *pch && *pch != '"'; ++pch)
706         {
707             *(pch - 1) = *pch;
708         }
709 
710         if (*pch == '"')
711             *(pch - 1) = ANSI_NULL;
712     }
713 
714     return psz;
715 }
716 
717 /*************************************************************************
718  *                SheRemoveQuotesW (SHELL32.@)
719  *
720  * ExtractAssociatedIconExW uses this function.
721  */
722 EXTERN_C LPWSTR
723 WINAPI
724 SheRemoveQuotesW(LPWSTR psz)
725 {
726     PWCHAR pch;
727 
728     if (*psz == L'"')
729     {
730         for (pch = psz + 1; *pch && *pch != L'"'; ++pch)
731         {
732             *(pch - 1) = *pch;
733         }
734 
735         if (*pch == L'"')
736             *(pch - 1) = UNICODE_NULL;
737     }
738 
739     return psz;
740 }
741 
742 /*************************************************************************
743  *  SHFindComputer [SHELL32.91]
744  *
745  * Invokes the shell search in My Computer. Used in SHFindFiles.
746  * Two parameters are ignored.
747  */
748 EXTERN_C BOOL
749 WINAPI
750 SHFindComputer(LPCITEMIDLIST pidlRoot, LPCITEMIDLIST pidlSavedSearch)
751 {
752     UNREFERENCED_PARAMETER(pidlRoot);
753     UNREFERENCED_PARAMETER(pidlSavedSearch);
754 
755     TRACE("%p %p\n", pidlRoot, pidlSavedSearch);
756 
757     IContextMenu *pCM;
758     HRESULT hr = CoCreateInstance(CLSID_ShellSearchExt, NULL, CLSCTX_INPROC_SERVER,
759                                   IID_IContextMenu, (void **)&pCM);
760     if (FAILED(hr))
761     {
762         ERR("0x%08X\n", hr);
763         return hr;
764     }
765 
766     CMINVOKECOMMANDINFO InvokeInfo = { sizeof(InvokeInfo) };
767     InvokeInfo.lpParameters = "{996E1EB1-B524-11D1-9120-00A0C98BA67D}";
768     InvokeInfo.nShow = SW_SHOWNORMAL;
769     hr = pCM->InvokeCommand(&InvokeInfo);
770     pCM->Release();
771 
772     return SUCCEEDED(hr);
773 }
774 
775 static HRESULT
776 Int64ToStr(
777     _In_ LONGLONG llValue,
778     _Out_writes_(cchValue) LPWSTR pszValue,
779     _In_ UINT cchValue)
780 {
781     WCHAR szBuff[40];
782     UINT ich = 0, ichValue;
783 #if (WINVER >= _WIN32_WINNT_VISTA)
784     BOOL bMinus = (llValue < 0);
785 
786     if (bMinus)
787         llValue = -llValue;
788 #endif
789 
790     if (cchValue <= 0)
791         return E_FAIL;
792 
793     do
794     {
795         szBuff[ich++] = (WCHAR)(L'0' + (llValue % 10));
796         llValue /= 10;
797     } while (llValue != 0 && ich < _countof(szBuff) - 1);
798 
799 #if (WINVER >= _WIN32_WINNT_VISTA)
800     if (bMinus && ich < _countof(szBuff))
801         szBuff[ich++] = '-';
802 #endif
803 
804     for (ichValue = 0; ich > 0 && ichValue < cchValue; ++ichValue)
805     {
806         --ich;
807         pszValue[ichValue] = szBuff[ich];
808     }
809 
810     if (ichValue >= cchValue)
811     {
812         pszValue[cchValue - 1] = UNICODE_NULL;
813         return E_FAIL;
814     }
815 
816     pszValue[ichValue] = UNICODE_NULL;
817     return S_OK;
818 }
819 
820 static VOID
821 Int64GetNumFormat(
822     _Out_ NUMBERFMTW *pDest,
823     _In_opt_ const NUMBERFMTW *pSrc,
824     _In_ DWORD dwNumberFlags,
825     _Out_writes_(cchDecimal) LPWSTR pszDecimal,
826     _In_ INT cchDecimal,
827     _Out_writes_(cchThousand) LPWSTR pszThousand,
828     _In_ INT cchThousand)
829 {
830     WCHAR szBuff[20];
831 
832     if (pSrc)
833         *pDest = *pSrc;
834     else
835         dwNumberFlags = 0;
836 
837     if (!(dwNumberFlags & FMT_USE_NUMDIGITS))
838     {
839         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IDIGITS, szBuff, _countof(szBuff));
840         pDest->NumDigits = StrToIntW(szBuff);
841     }
842 
843     if (!(dwNumberFlags & FMT_USE_LEADZERO))
844     {
845         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_ILZERO, szBuff, _countof(szBuff));
846         pDest->LeadingZero = StrToIntW(szBuff);
847     }
848 
849     if (!(dwNumberFlags & FMT_USE_GROUPING))
850     {
851         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szBuff, _countof(szBuff));
852         pDest->Grouping = StrToIntW(szBuff);
853     }
854 
855     if (!(dwNumberFlags & FMT_USE_DECIMAL))
856     {
857         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, pszDecimal, cchDecimal);
858         pDest->lpDecimalSep = pszDecimal;
859     }
860 
861     if (!(dwNumberFlags & FMT_USE_THOUSAND))
862     {
863         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, pszThousand, cchThousand);
864         pDest->lpThousandSep = pszThousand;
865     }
866 
867     if (!(dwNumberFlags & FMT_USE_NEGNUMBER))
868     {
869         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, szBuff, _countof(szBuff));
870         pDest->NegativeOrder = StrToIntW(szBuff);
871     }
872 }
873 
874 /*************************************************************************
875  *  Int64ToString [SHELL32.209]
876  *
877  * @see http://undoc.airesoft.co.uk/shell32.dll/Int64ToString.php
878  */
879 EXTERN_C
880 INT WINAPI
881 Int64ToString(
882     _In_ LONGLONG llValue,
883     _Out_writes_(cchOut) LPWSTR pszOut,
884     _In_ UINT cchOut,
885     _In_ BOOL bUseFormat,
886     _In_opt_ const NUMBERFMTW *pNumberFormat,
887     _In_ DWORD dwNumberFlags)
888 {
889     INT ret;
890     NUMBERFMTW NumFormat;
891     WCHAR szValue[80], szDecimalSep[6], szThousandSep[6];
892 
893     Int64ToStr(llValue, szValue, _countof(szValue));
894 
895     if (bUseFormat)
896     {
897         Int64GetNumFormat(&NumFormat, pNumberFormat, dwNumberFlags,
898                           szDecimalSep, _countof(szDecimalSep),
899                           szThousandSep, _countof(szThousandSep));
900         ret = GetNumberFormatW(LOCALE_USER_DEFAULT, 0, szValue, &NumFormat, pszOut, cchOut);
901         if (ret)
902             --ret;
903         return ret;
904     }
905 
906     if (FAILED(StringCchCopyW(pszOut, cchOut, szValue)))
907         return 0;
908 
909     return lstrlenW(pszOut);
910 }
911 
912 /*************************************************************************
913  *  LargeIntegerToString [SHELL32.210]
914  *
915  * @see http://undoc.airesoft.co.uk/shell32.dll/LargeIntegerToString.php
916  */
917 EXTERN_C
918 INT WINAPI
919 LargeIntegerToString(
920     _In_ const LARGE_INTEGER *pLargeInt,
921     _Out_writes_(cchOut) LPWSTR pszOut,
922     _In_ UINT cchOut,
923     _In_ BOOL bUseFormat,
924     _In_opt_ const NUMBERFMTW *pNumberFormat,
925     _In_ DWORD dwNumberFlags)
926 {
927     return Int64ToString(pLargeInt->QuadPart, pszOut, cchOut, bUseFormat,
928                          pNumberFormat, dwNumberFlags);
929 }
930 
931 /*************************************************************************
932  *  CopyStreamUI [SHELL32.726]
933  *
934  * Copy a stream to another stream with optional progress display.
935  */
936 EXTERN_C
937 HRESULT WINAPI
938 CopyStreamUI(
939     _In_ IStream *pSrc,
940     _Out_ IStream *pDst,
941     _Inout_opt_ IProgressDialog *pProgress,
942     _In_opt_ DWORDLONG dwlSize)
943 {
944     HRESULT hr = E_FAIL;
945     DWORD cbBuff, cbRead, dwSizeToWrite;
946     DWORDLONG cbDone;
947     LPVOID pBuff;
948     CComHeapPtr<BYTE> pHeapPtr;
949     STATSTG Stat;
950     BYTE abBuff[1024];
951 
952     TRACE("(%p, %p, %p, %I64u)\n", pSrc, pDst, pProgress, dwlSize);
953 
954     if (dwlSize == 0) // Invalid size?
955     {
956         // Get the stream size
957         ZeroMemory(&Stat, sizeof(Stat));
958         if (FAILED(pSrc->Stat(&Stat, STATFLAG_NONAME)))
959             pProgress = NULL; // No size info. Disable progress
960         else
961             dwlSize = Stat.cbSize.QuadPart;
962     }
963 
964     if (!pProgress) // Progress is disabled?
965     {
966         ULARGE_INTEGER uliSize;
967 
968         if (dwlSize > 0)
969             uliSize.QuadPart = dwlSize;
970         else
971             uliSize.HighPart = uliSize.LowPart = INVALID_FILE_SIZE;
972 
973         return pSrc->CopyTo(pDst, uliSize, NULL, NULL); // One punch
974     }
975 
976     // Allocate the buffer if necessary
977     if (dwlSize > 0 && dwlSize <= sizeof(abBuff))
978     {
979         cbBuff = sizeof(abBuff);
980         pBuff = abBuff;
981     }
982     else
983     {
984 #define COPY_STREAM_DEFAULT_BUFFER_SIZE 0x4000
985         cbBuff = COPY_STREAM_DEFAULT_BUFFER_SIZE;
986         if (pHeapPtr.AllocateBytes(cbBuff))
987         {
988             pBuff = pHeapPtr;
989         }
990         else // Low memory?
991         {
992             cbBuff = sizeof(abBuff);
993             pBuff = abBuff;
994         }
995 #undef COPY_STREAM_DEFAULT_BUFFER_SIZE
996     }
997 
998     // Start reading
999     LARGE_INTEGER zero;
1000     zero.QuadPart = 0;
1001     pSrc->Seek(zero, 0, NULL);
1002     pDst->Seek(zero, 0, NULL);
1003     cbDone = 0;
1004     pProgress->SetProgress64(cbDone, dwlSize);
1005 
1006     // Repeat reading and writing until goal
1007     for (;;)
1008     {
1009         hr = pSrc->Read(pBuff, cbBuff, &cbRead);
1010         if (FAILED(hr))
1011             break;
1012 
1013         // Calculate the size to write
1014         if (dwlSize > 0)
1015             dwSizeToWrite = (DWORD)min((DWORDLONG)(dwlSize - cbDone), (DWORDLONG)cbRead);
1016         else
1017             dwSizeToWrite = cbRead;
1018 
1019         if (dwSizeToWrite == 0) // No need to write?
1020         {
1021             hr = S_OK;
1022             break;
1023         }
1024 
1025         hr = pDst->Write(pBuff, dwSizeToWrite, NULL);
1026         if (hr != S_OK)
1027             break;
1028 
1029         cbDone += dwSizeToWrite;
1030 
1031         if (pProgress->HasUserCancelled()) // Cancelled?
1032         {
1033             hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
1034             break;
1035         }
1036         pProgress->SetProgress64(cbDone, dwlSize);
1037 
1038         if (dwlSize > 0 && cbDone >= dwlSize) // Reached the goal?
1039         {
1040             hr = S_OK;
1041             break;
1042         }
1043     }
1044 
1045     return hr;
1046 }
1047 
1048 /*************************************************************************
1049  *  SHOpenPropSheetA [SHELL32.707]
1050  *
1051  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-shopenpropsheeta
1052  */
1053 EXTERN_C
1054 BOOL WINAPI
1055 SHOpenPropSheetA(
1056     _In_opt_ LPCSTR pszCaption,
1057     _In_opt_ HKEY *ahKeys,
1058     _In_ UINT cKeys,
1059     _In_ const CLSID *pclsidDefault,
1060     _In_ IDataObject *pDataObject,
1061     _In_opt_ IShellBrowser *pShellBrowser,
1062     _In_opt_ LPCSTR pszStartPage)
1063 {
1064     CStringW strStartPageW, strCaptionW;
1065     LPCWSTR pszCaptionW = NULL, pszStartPageW = NULL;
1066 
1067     TRACE("(%s, %p, %u, %p, %p, %p, %s)", debugstr_a(pszCaption), ahKeys, cKeys, pclsidDefault,
1068           pDataObject, pShellBrowser, debugstr_a(pszStartPage));
1069 
1070     if (pszCaption)
1071     {
1072         strStartPageW = pszCaption;
1073         pszCaptionW = strCaptionW;
1074     }
1075 
1076     if (pszStartPage)
1077     {
1078         strStartPageW = pszStartPage;
1079         pszStartPageW = strStartPageW;
1080     }
1081 
1082     return SHOpenPropSheetW(pszCaptionW, ahKeys, cKeys, pclsidDefault,
1083                             pDataObject, pShellBrowser, pszStartPageW);
1084 }
1085 
1086 /*************************************************************************
1087  *  Activate_RunDLL [SHELL32.105]
1088  *
1089  * Unlocks the foreground window and allows the shell window to become the
1090  * foreground window. Every parameter is unused.
1091  */
1092 EXTERN_C
1093 BOOL WINAPI
1094 Activate_RunDLL(
1095     _In_ HWND hwnd,
1096     _In_ HINSTANCE hinst,
1097     _In_ LPCWSTR cmdline,
1098     _In_ INT cmdshow)
1099 {
1100     DWORD dwProcessID;
1101 
1102     UNREFERENCED_PARAMETER(hwnd);
1103     UNREFERENCED_PARAMETER(hinst);
1104     UNREFERENCED_PARAMETER(cmdline);
1105     UNREFERENCED_PARAMETER(cmdshow);
1106 
1107     TRACE("(%p, %p, %s, %d)\n", hwnd, hinst, debugstr_w(cmdline), cmdline);
1108 
1109     GetWindowThreadProcessId(GetShellWindow(), &dwProcessID);
1110     return AllowSetForegroundWindow(dwProcessID);
1111 }
1112 
1113 /*************************************************************************
1114  *                SHStartNetConnectionDialogA (SHELL32.12)
1115  *
1116  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shstartnetconnectiondialoga
1117  */
1118 EXTERN_C
1119 HRESULT WINAPI
1120 SHStartNetConnectionDialogA(
1121     _In_ HWND hwnd,
1122     _In_ LPCSTR pszRemoteName,
1123     _In_ DWORD dwType)
1124 {
1125     LPCWSTR pszRemoteNameW = NULL;
1126     CStringW strRemoteNameW;
1127 
1128     TRACE("(%p, %s, %lu)\n", hwnd, debugstr_a(pszRemoteName), dwType);
1129 
1130     if (pszRemoteName)
1131     {
1132         strRemoteNameW = pszRemoteName;
1133         pszRemoteNameW = strRemoteNameW;
1134     }
1135 
1136     return SHStartNetConnectionDialogW(hwnd, pszRemoteNameW, dwType);
1137 }
1138 
1139 /*************************************************************************
1140  * Helper functions for PathIsEqualOrSubFolder
1141  */
1142 
1143 static INT
1144 DynamicPathCommonPrefixW(
1145     _In_ LPCWSTR lpszPath1,
1146     _In_ LPCWSTR lpszPath2,
1147     _Out_ CStringW& strPath)
1148 {
1149     SIZE_T cchPath1 = wcslen(lpszPath1);
1150     SIZE_T cchPath2 = wcslen(lpszPath2);
1151     LPWSTR lpszPath = strPath.GetBuffer((INT)max(cchPath1, cchPath2) + 16);
1152     INT ret = PathCommonPrefixW(lpszPath1, lpszPath2, lpszPath);
1153     strPath.ReleaseBuffer();
1154     return ret;
1155 }
1156 
1157 EXTERN_C HRESULT WINAPI
1158 SHGetPathCchFromIDListW(LPCITEMIDLIST pidl, LPWSTR pszPath, SIZE_T cchPathMax);
1159 
1160 static HRESULT
1161 DynamicSHGetPathFromIDListW(
1162     _In_ LPCITEMIDLIST pidl,
1163     _Out_ CStringW& strPath)
1164 {
1165     HRESULT hr;
1166 
1167     for (UINT cchPath = MAX_PATH;; cchPath *= 2)
1168     {
1169         LPWSTR lpszPath = strPath.GetBuffer(cchPath);
1170         if (!lpszPath)
1171             return E_OUTOFMEMORY;
1172 
1173         hr = SHGetPathCchFromIDListW(pidl, lpszPath, cchPath);
1174         strPath.ReleaseBuffer();
1175 
1176         if (hr != E_NOT_SUFFICIENT_BUFFER)
1177             break;
1178 
1179         if (cchPath >= MAXUINT / 2)
1180         {
1181             hr = E_FAIL;
1182             break;
1183         }
1184     }
1185 
1186     if (FAILED(hr))
1187         strPath.Empty();
1188 
1189     return hr;
1190 }
1191 
1192 static HRESULT
1193 DynamicSHGetSpecialFolderPathW(
1194     _In_ HWND hwndOwner,
1195     _Out_ CStringW& strPath,
1196     _In_ INT nCSIDL,
1197     _In_ BOOL bCreate)
1198 {
1199     LPITEMIDLIST pidl;
1200     HRESULT hr = SHGetSpecialFolderLocation(hwndOwner, nCSIDL, &pidl);
1201     if (SUCCEEDED(hr))
1202     {
1203         hr = DynamicSHGetPathFromIDListW(pidl, strPath);
1204         CoTaskMemFree(pidl);
1205     }
1206 
1207     if (FAILED(hr))
1208         strPath.Empty();
1209     else if (bCreate)
1210         CreateDirectoryW(strPath, NULL);
1211 
1212     return hr;
1213 }
1214 
1215 static VOID
1216 DynamicPathRemoveBackslashW(
1217     _Out_ CStringW& strPath)
1218 {
1219     INT nLength = strPath.GetLength();
1220     if (nLength > 0 && strPath[nLength - 1] == L'\\')
1221         strPath = strPath.Left(nLength - 1);
1222 }
1223 
1224 /*************************************************************************
1225  *                PathIsEqualOrSubFolder (SHELL32.755)
1226  */
1227 EXTERN_C
1228 BOOL WINAPI
1229 PathIsEqualOrSubFolder(
1230     _In_ LPCWSTR pszPath1OrCSIDL,
1231     _In_ LPCWSTR pszPath2)
1232 {
1233     CStringW strCommon, strPath1;
1234 
1235     TRACE("(%s %s)\n", debugstr_w(pszPath1OrCSIDL), debugstr_w(pszPath2));
1236 
1237     if (IS_INTRESOURCE(pszPath1OrCSIDL))
1238     {
1239         DynamicSHGetSpecialFolderPathW(
1240             NULL, strPath1, LOWORD(pszPath1OrCSIDL) | CSIDL_FLAG_DONT_VERIFY, FALSE);
1241     }
1242     else
1243     {
1244         strPath1 = pszPath1OrCSIDL;
1245     }
1246 
1247     DynamicPathRemoveBackslashW(strPath1);
1248 
1249     if (!DynamicPathCommonPrefixW(strPath1, pszPath2, strCommon))
1250         return FALSE;
1251 
1252     return strPath1.CompareNoCase(strCommon) == 0;
1253 }
1254 
1255 /*************************************************************************
1256  *  SHGetRealIDL [SHELL32.98]
1257  */
1258 EXTERN_C
1259 HRESULT WINAPI
1260 SHGetRealIDL(
1261     _In_ IShellFolder *psf,
1262     _In_ PCUITEMID_CHILD pidlSimple,
1263     _Outptr_ PITEMID_CHILD *ppidlReal)
1264 {
1265     HRESULT hr;
1266     STRRET strret;
1267     WCHAR szPath[MAX_PATH];
1268     SFGAOF attrs;
1269 
1270     *ppidlReal = NULL;
1271 
1272     hr = IShellFolder_GetDisplayNameOf(psf, pidlSimple, SHGDN_INFOLDER | SHGDN_FORPARSING,
1273                                        &strret, 0);
1274     if (FAILED_UNEXPECTEDLY(hr))
1275         return hr;
1276 
1277     hr = StrRetToBufW(&strret, pidlSimple, szPath, _countof(szPath));
1278     if (FAILED_UNEXPECTEDLY(hr))
1279         return hr;
1280 
1281     attrs = SFGAO_FILESYSTEM;
1282     hr = psf->GetAttributesOf(1, &pidlSimple, &attrs);
1283     if (SUCCEEDED(hr) && !(attrs & SFGAO_FILESYSTEM))
1284         return SHILClone(pidlSimple, ppidlReal);
1285 
1286     hr = IShellFolder_ParseDisplayName(psf, NULL, NULL, szPath, NULL, ppidlReal, NULL);
1287     if (hr == E_INVALIDARG || hr == E_NOTIMPL)
1288         return SHILClone(pidlSimple, ppidlReal);
1289 
1290     return hr;
1291 }
1292 
1293 EXTERN_C HRESULT
1294 IUnknown_InitializeCommand(
1295     _In_ IUnknown *pUnk,
1296     _In_ PCWSTR pszCommandName,
1297     _In_opt_ IPropertyBag *pPB)
1298 {
1299     HRESULT hr;
1300     CComPtr<IInitializeCommand> pIC;
1301     if (SUCCEEDED(hr = pUnk->QueryInterface(IID_PPV_ARG(IInitializeCommand, &pIC))))
1302         hr = pIC->Initialize(pszCommandName, pPB);
1303     return hr;
1304 }
1305 
1306 EXTERN_C HRESULT
1307 InvokeIExecuteCommand(
1308     _In_ IExecuteCommand *pEC,
1309     _In_ PCWSTR pszCommandName,
1310     _In_opt_ IPropertyBag *pPB,
1311     _In_opt_ IShellItemArray *pSIA,
1312     _In_opt_ LPCMINVOKECOMMANDINFOEX pICI,
1313     _In_opt_ IUnknown *pSite)
1314 {
1315     if (!pEC)
1316         return E_INVALIDARG;
1317 
1318     if (pSite)
1319         IUnknown_SetSite(pEC, pSite);
1320     IUnknown_InitializeCommand(pEC, pszCommandName, pPB);
1321 
1322     CComPtr<IObjectWithSelection> pOWS;
1323     if (pSIA && SUCCEEDED(pEC->QueryInterface(IID_PPV_ARG(IObjectWithSelection, &pOWS))))
1324         pOWS->SetSelection(pSIA);
1325 
1326     DWORD dwKeyState = 0, fMask = pICI ? pICI->fMask : 0;
1327     pEC->SetNoShowUI((fMask & CMIC_MASK_FLAG_NO_UI) != 0);
1328     pEC->SetShowWindow(pICI ? pICI->nShow : SW_SHOW);
1329     if (fMask & CMIC_MASK_SHIFT_DOWN)
1330         dwKeyState |= MK_SHIFT;
1331     if (fMask & CMIC_MASK_CONTROL_DOWN)
1332         dwKeyState |= MK_CONTROL;
1333     pEC->SetKeyState(dwKeyState);
1334     if ((fMask & CMIC_MASK_UNICODE) && pICI->lpDirectoryW)
1335         pEC->SetDirectory(pICI->lpDirectoryW);
1336     if ((fMask & CMIC_MASK_UNICODE) && pICI->lpParametersW)
1337         pEC->SetParameters(pICI->lpParametersW);
1338     if (fMask & CMIC_MASK_PTINVOKE)
1339         pEC->SetPosition(pICI->ptInvoke);
1340 
1341     HRESULT hr = pEC->Execute();
1342     if (pSite)
1343         IUnknown_SetSite(pEC, NULL);
1344     return hr;
1345 }
1346 
1347 EXTERN_C HRESULT
1348 InvokeIExecuteCommandWithDataObject(
1349     _In_ IExecuteCommand *pEC,
1350     _In_ PCWSTR pszCommandName,
1351     _In_opt_ IPropertyBag *pPB,
1352     _In_ IDataObject *pDO,
1353     _In_opt_ LPCMINVOKECOMMANDINFOEX pICI,
1354     _In_opt_ IUnknown *pSite)
1355 {
1356     CComPtr<IShellItemArray> pSIA;
1357     HRESULT hr = SHCreateShellItemArrayFromDataObject(pDO, IID_PPV_ARG(IShellItemArray, &pSIA));
1358     return SUCCEEDED(hr) ? InvokeIExecuteCommand(pEC, pszCommandName, pPB, pSIA, pICI, pSite) : hr;
1359 }
1360 
1361 static HRESULT
1362 GetCommandStringA(_In_ IContextMenu *pCM, _In_ UINT_PTR Id, _In_ UINT GCS, _Out_writes_(cchMax) LPSTR Buf, _In_ UINT cchMax)
1363 {
1364     HRESULT hr = pCM->GetCommandString(Id, GCS & ~GCS_UNICODE, NULL, Buf, cchMax);
1365     if (FAILED(hr))
1366     {
1367         WCHAR buf[MAX_PATH];
1368         hr = pCM->GetCommandString(Id, GCS | GCS_UNICODE, NULL, (LPSTR)buf, _countof(buf));
1369         if (SUCCEEDED(hr))
1370             hr = SHUnicodeToAnsi(buf, Buf, cchMax) > 0 ? S_OK : E_FAIL;
1371     }
1372     return hr;
1373 }
1374 
1375 UINT
1376 GetDfmCmd(_In_ IContextMenu *pCM, _In_ LPCSTR verba)
1377 {
1378     CHAR buf[MAX_PATH];
1379     if (IS_INTRESOURCE(verba))
1380     {
1381         if (FAILED(GetCommandStringA(pCM, LOWORD(verba), GCS_VERB, buf, _countof(buf))))
1382             return 0;
1383         verba = buf;
1384     }
1385     return MapVerbToDfmCmd(verba); // Returns DFM_CMD_* or 0
1386 }
1387