Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4014 lines
134 KiB

  1. #include "shellprv.h"
  2. #include "shlexec.h"
  3. #include <newexe.h>
  4. #include <appmgmt.h>
  5. #include "ids.h"
  6. #include <shstr.h>
  7. #include "pidl.h"
  8. #include "apithk.h" // for TermsrvAppInstallMode()
  9. #include "fstreex.h"
  10. #include "uemapp.h"
  11. #include "views.h" // for SHRunControlPanelEx
  12. #include "control.h" // for MakeCPLCommandLine, etc
  13. #include <wincrui.h> // for CredUIInitControls
  14. #include <winsafer.h> // for ComputeAccessTokenFromCodeAuthzLevel, etc
  15. #include <winsaferp.h> // for Saferi APIs
  16. #include <softpub.h> // for WinVerifyTrust constants
  17. #include <lmcons.h> // for UNLEN (max username length), GNLEN (max groupname length), PWLEN (max password length)
  18. #define DM_MISC 0 // miscellany
  19. #define SZWNDCLASS TEXT("WndClass")
  20. #define SZTERMEVENT TEXT("TermEvent")
  21. typedef PSHCREATEPROCESSINFOW PSHCREATEPROCESSINFO;
  22. // stolen from sdk\inc\winbase.h
  23. #define LOGON_WITH_PROFILE 0x00000001
  24. #define IntToHinst(i) IntToPtr_(HINSTANCE, i)
  25. // the timer id for the kill this DDE window...
  26. #define DDE_DEATH_TIMER_ID 0x00005555
  27. // the timeout value (180 seconds) for killing a dead dde window...
  28. #define DDE_DEATH_TIMEOUT (1000 * 180)
  29. // timeout for conversation window terminating with us...
  30. #define DDE_TERMINATETIMEOUT (10 * 1000)
  31. #define SZCONV TEXT("ddeconv")
  32. #define SZDDEEVENT TEXT("ddeevent")
  33. #define PEMAGIC ((WORD)'P'+((WORD)'E'<<8))
  34. void *g_pfnWowShellExecCB = NULL;
  35. class CEnvironmentBlock
  36. {
  37. public:
  38. ~CEnvironmentBlock() { if (_pszBlock) LocalFree(_pszBlock); }
  39. void SetToken(HANDLE hToken) { _hToken = hToken; }
  40. void *GetCustomBlock() { return _pszBlock; }
  41. HRESULT SetVar(LPCWSTR pszVar, LPCWSTR pszValue);
  42. HRESULT AppendVar(LPCWSTR pszVar, WCHAR chDelimiter, LPCWSTR pszValue);
  43. private: // methods
  44. HRESULT _InitBlock(DWORD cchNeeded);
  45. DWORD _BlockLen(LPCWSTR pszEnv);
  46. DWORD _BlockLenCached();
  47. BOOL _FindVar(LPCWSTR pszVar, DWORD cchVar, LPWSTR *ppszBlockVar);
  48. private: // members
  49. HANDLE _hToken;
  50. LPWSTR _pszBlock;
  51. DWORD _cchBlockSize;
  52. DWORD _cchBlockLen;
  53. };
  54. typedef enum
  55. {
  56. CPT_FAILED = -1,
  57. CPT_NORMAL = 0,
  58. CPT_ASUSER,
  59. CPT_SANDBOX,
  60. CPT_INSTALLTS,
  61. CPT_WITHLOGON,
  62. CPT_WITHLOGONADMIN,
  63. CPT_WITHLOGONCANCELLED,
  64. } CPTYPE;
  65. typedef enum {
  66. TRY_RETRYASYNC = -1, // stop execution (complete on async thread)
  67. TRY_STOP = 0, // stop execution (completed or failed)
  68. TRY_CONTINUE, // continue exec (did something useful)
  69. TRY_CONTINUE_UNHANDLED, // continue exec (did nothing)
  70. } TRYRESULT;
  71. #define KEEPTRYING(tr) (tr >= TRY_CONTINUE ? TRUE : FALSE)
  72. #define STOPTRYING(tr) (tr <= TRY_STOP ? TRUE : FALSE)
  73. class CShellExecute
  74. {
  75. public:
  76. CShellExecute();
  77. STDMETHODIMP_(ULONG) AddRef()
  78. {
  79. return InterlockedIncrement(&_cRef);
  80. }
  81. STDMETHODIMP_(ULONG) Release()
  82. {
  83. ASSERT( 0 != _cRef );
  84. ULONG cRef = InterlockedDecrement(&_cRef);
  85. if ( 0 == cRef )
  86. {
  87. delete this;
  88. }
  89. return cRef;
  90. }
  91. void ExecuteNormal(LPSHELLEXECUTEINFO pei);
  92. DWORD Finalize(LPSHELLEXECUTEINFO pei);
  93. BOOL Init(PSHCREATEPROCESSINFO pscpi);
  94. void ExecuteProcess(void);
  95. DWORD Finalize(PSHCREATEPROCESSINFO pscpi);
  96. protected:
  97. ~CShellExecute();
  98. // default inits
  99. HRESULT _Init(LPSHELLEXECUTEINFO pei);
  100. // member init methods
  101. TRYRESULT _InitAssociations(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl);
  102. HRESULT _InitClassAssociations(LPCTSTR pszClass, HKEY hkClass, DWORD mask);
  103. HRESULT _InitShellAssociations(LPCTSTR pszFile, LPCITEMIDLIST pidl);
  104. void _SetMask(ULONG fMask);
  105. void _SetWorkingDir(LPCTSTR pszIn);
  106. void _SetFile(LPCTSTR pszIn, BOOL fFileAndUrl);
  107. void _SetFileAndUrl();
  108. BOOL _SetDDEInfo(void);
  109. TRYRESULT _MaybeInstallApp(BOOL fSync);
  110. TRYRESULT _ShouldRetryWithNewClassKey(BOOL fSync);
  111. TRYRESULT _SetDarwinCmdTemplate(BOOL fSync);
  112. BOOL _SetAppRunAsCmdTemplate(void);
  113. TRYRESULT _SetCmdTemplate(BOOL fSync);
  114. BOOL _FileIsApp();
  115. BOOL _SetCommand(void);
  116. void _SetStartup(LPSHELLEXECUTEINFO pei);
  117. IBindCtx *_PerfBindCtx();
  118. TRYRESULT _PerfPidl(LPCITEMIDLIST *ppidl);
  119. // utility methods
  120. HRESULT _QueryString(ASSOCF flags, ASSOCSTR str, LPTSTR psz, DWORD cch);
  121. BOOL _CheckForRegisteredProgram();
  122. BOOL _ExecMayCreateProcess(LPCTSTR *ppszNewEnvString);
  123. HRESULT _BuildEnvironmentForNewProcess(LPCTSTR pszNewEnvString);
  124. void _FixActivationStealingApps(HWND hwndOldActive, int nShow);
  125. DWORD _GetCreateFlags(ULONG fMask);
  126. BOOL _Resolve(LPCITEMIDLIST *ppidl);
  127. // DDE stuff
  128. HWND _GetConversationWindow(HWND hwndDDE);
  129. HWND _CreateHiddenDDEWindow(HWND hwndParent);
  130. HGLOBAL _CreateDDECommand(int nShow, BOOL fLFNAware, BOOL fNative);
  131. void _DestroyHiddenDDEWindow(HWND hwnd);
  132. BOOL _TryDDEShortCircuit(HWND hwnd, HGLOBAL hMem, int nShow);
  133. BOOL _PostDDEExecute(HWND hwndOurs, HWND hwndTheirs, HGLOBAL hDDECommand, HANDLE hWait);
  134. BOOL _DDEExecute(BOOL fWillRetry,
  135. HWND hwndParent,
  136. int nShowCmd,
  137. BOOL fWaitForDDE);
  138. // exec methods
  139. TRYRESULT _TryHooks(LPSHELLEXECUTEINFO pei);
  140. TRYRESULT _TryValidateUNC(LPTSTR pszFile, LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl);
  141. void _DoExecCommand(void);
  142. void _NotifyShortcutInvoke();
  143. TRYRESULT _TryExecDDE(void);
  144. TRYRESULT _ZoneCheckFile(PCWSTR pszFile);
  145. TRYRESULT _VerifyZoneTrust(PCWSTR pszFile);
  146. TRYRESULT _VerifySaferTrust(PCWSTR pszFile);
  147. TRYRESULT _VerifyExecTrust(LPSHELLEXECUTEINFO pei);
  148. TRYRESULT _TryExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl);
  149. TRYRESULT _DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl);
  150. BOOL _ShellExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidlExec);
  151. TRYRESULT _TryInvokeApplication(BOOL fSync);
  152. // uninit/error handling methods
  153. DWORD _FinalMapError(HINSTANCE UNALIGNED64 *phinst);
  154. BOOL _ReportWin32(DWORD err);
  155. BOOL _ReportHinst(HINSTANCE hinst);
  156. DWORD _MapHINSTToWin32Err(HINSTANCE se_err);
  157. HINSTANCE _MapWin32ErrToHINST(UINT errWin32);
  158. TRYRESULT _TryWowShellExec(void);
  159. TRYRESULT _RetryAsync();
  160. DWORD _InvokeAppThreadProc();
  161. static DWORD WINAPI s_InvokeAppThreadProc(void *pv);
  162. //
  163. // PRIVATE MEMBERS
  164. //
  165. LONG _cRef;
  166. TCHAR _szFile[INTERNET_MAX_URL_LENGTH];
  167. TCHAR _szWorkingDir[MAX_PATH];
  168. TCHAR _szCommand[INTERNET_MAX_URL_LENGTH];
  169. TCHAR _szCmdTemplate[INTERNET_MAX_URL_LENGTH];
  170. TCHAR _szDDECmd[MAX_PATH];
  171. TCHAR _szApplication[MAX_PATH];
  172. TCHAR _szPolicyApp[MAX_PATH];
  173. TCHAR _szAppFriendly[MAX_PATH];
  174. TCHAR _szUrl[INTERNET_MAX_URL_LENGTH];
  175. DWORD _dwCreateFlags;
  176. STARTUPINFO _startup;
  177. int _nShow;
  178. UINT _uConnect;
  179. PROCESS_INFORMATION _pi;
  180. // used only within restricted scope
  181. // to avoid stack usage;
  182. WCHAR _wszTemp[INTERNET_MAX_URL_LENGTH];
  183. TCHAR _szTemp[MAX_PATH];
  184. // we always pass a UNICODE verb to the _pqa
  185. WCHAR _wszVerb[MAX_PATH];
  186. LPCWSTR _pszQueryVerb;
  187. LPCTSTR _lpParameters;
  188. LPTSTR _pszAllocParams;
  189. LPCTSTR _lpClass;
  190. LPCTSTR _lpTitle;
  191. LPTSTR _pszAllocTitle;
  192. LPCITEMIDLIST _lpID;
  193. SFGAOF _sfgaoID;
  194. LPITEMIDLIST _pidlFree;
  195. ATOM _aApplication;
  196. ATOM _aTopic;
  197. LPITEMIDLIST _pidlGlobal;
  198. IQueryAssociations *_pqa;
  199. HWND _hwndParent;
  200. LPSECURITY_ATTRIBUTES _pProcAttrs;
  201. LPSECURITY_ATTRIBUTES _pThreadAttrs;
  202. HANDLE _hUserToken;
  203. HANDLE _hCloseToken;
  204. CEnvironmentBlock _envblock;
  205. CPTYPE _cpt;
  206. // error state
  207. HINSTANCE _hInstance; // hinstance value should only be set with ReportHinst
  208. DWORD _err; // win32 error value should only be set with ReportWin32
  209. // FLAGS
  210. BOOL _fNoUI; // dont show any UI
  211. BOOL _fUEM; // fire UEM events
  212. BOOL _fDoEnvSubst; // do environment substitution on paths
  213. BOOL _fUseClass;
  214. BOOL _fNoQueryClassStore; // blocks calling darwins class store
  215. BOOL _fClassStoreOnly;
  216. BOOL _fIsUrl; //_szFile is actually an URL
  217. BOOL _fActivateHandler;
  218. BOOL _fDDEInfoSet;
  219. BOOL _fDDEWait;
  220. BOOL _fNoExecPidl;
  221. BOOL _fNoResolve; // unnecessary to resolve this path
  222. BOOL _fAlreadyQueriedClassStore; // have we already queried the NT5 class store?
  223. BOOL _fInheritHandles;
  224. BOOL _fIsNamespaceObject; // is namespace object like ::{GUID}, must pidlexec
  225. BOOL _fWaitForInputIdle;
  226. BOOL _fUseNullCWD; // should we pass NULL as the lpCurrentDirectory param to _SHCreateProcess?
  227. BOOL _fInvokeIdList;
  228. BOOL _fAsync; // shellexec() switched
  229. };
  230. CShellExecute::CShellExecute() : _cRef(1)
  231. {
  232. TraceMsg(TF_SHELLEXEC, "SHEX::SHEX Created [%X]", this);
  233. }
  234. CShellExecute::~CShellExecute()
  235. {
  236. if (_hCloseToken)
  237. CloseHandle(_hCloseToken);
  238. // Clean this up if the exec failed
  239. if (_err != ERROR_SUCCESS && _pidlGlobal)
  240. SHFreeShared((HANDLE)_pidlGlobal,GetCurrentProcessId());
  241. if (_aTopic)
  242. GlobalDeleteAtom(_aTopic);
  243. if (_aApplication)
  244. GlobalDeleteAtom(_aApplication);
  245. if (_pqa)
  246. _pqa->Release();
  247. if (_pi.hProcess)
  248. CloseHandle(_pi.hProcess);
  249. if (_pi.hThread)
  250. CloseHandle(_pi.hThread);
  251. if (_pszAllocParams)
  252. LocalFree(_pszAllocParams);
  253. if (_pszAllocTitle)
  254. LocalFree(_pszAllocTitle);
  255. ILFree(_pidlFree);
  256. TraceMsg(TF_SHELLEXEC, "SHEX::SHEX deleted [%X]", this);
  257. }
  258. void CShellExecute::_SetMask(ULONG fMask)
  259. {
  260. _fDoEnvSubst = (fMask & SEE_MASK_DOENVSUBST);
  261. _fNoUI = (fMask & SEE_MASK_FLAG_NO_UI);
  262. _fUEM = (fMask & SEE_MASK_FLAG_LOG_USAGE);
  263. _fNoQueryClassStore = (fMask & SEE_MASK_NOQUERYCLASSSTORE) || !IsOS(OS_DOMAINMEMBER);
  264. _fDDEWait = fMask & SEE_MASK_FLAG_DDEWAIT;
  265. _fWaitForInputIdle = fMask & SEE_MASK_WAITFORINPUTIDLE;
  266. _fUseClass = _UseClassName(fMask) || _UseClassKey(fMask);
  267. _fInvokeIdList = _InvokeIDList(fMask);
  268. _dwCreateFlags = _GetCreateFlags(fMask);
  269. _uConnect = fMask & SEE_MASK_CONNECTNETDRV ? VALIDATEUNC_CONNECT : 0;
  270. if (_fNoUI)
  271. _uConnect |= VALIDATEUNC_NOUI;
  272. // must be off for this condition to pass.
  273. // PARTIAL ANSWER (reinerf): the SEE_MASK_FILEANDURL has to be off
  274. // so we can wait until we find out what the associated App is and query
  275. // to find out whether they want the the cache filename or the URL name passed
  276. // on the command line.
  277. #define NOEXECPIDLMASK (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FORCENOIDLIST | SEE_MASK_FILEANDURL)
  278. _fNoExecPidl = BOOLIFY(fMask & NOEXECPIDLMASK);
  279. }
  280. HRESULT CShellExecute::_Init(LPSHELLEXECUTEINFO pei)
  281. {
  282. TraceMsg(TF_SHELLEXEC, "SHEX::_Init()");
  283. _SetMask(pei->fMask);
  284. _lpParameters = pei->lpParameters;
  285. _lpID = (LPITEMIDLIST)((pei->fMask) & SEE_MASK_PIDL ? pei->lpIDList : NULL);
  286. _lpTitle = _UseTitleName(pei->fMask) ? pei->lpClass : NULL;
  287. // default to TRUE;
  288. _fActivateHandler = TRUE;
  289. if (pei->lpVerb && *(pei->lpVerb))
  290. {
  291. SHTCharToUnicode(pei->lpVerb, _wszVerb, SIZECHARS(_wszVerb));
  292. _pszQueryVerb = _wszVerb;
  293. if (0 == lstrcmpi(pei->lpVerb, TEXT("runas")))
  294. _cpt = CPT_WITHLOGON;
  295. }
  296. _hwndParent = pei->hwnd;
  297. pei->hProcess = 0;
  298. _nShow = pei->nShow;
  299. // initialize the startup struct
  300. _SetStartup(pei);
  301. return S_OK;
  302. }
  303. void CShellExecute::_SetWorkingDir(LPCTSTR pszIn)
  304. {
  305. // if we were given a directory, we attempt to use it
  306. if (pszIn && *pszIn)
  307. {
  308. StrCpyN(_szWorkingDir, pszIn, SIZECHARS(_szWorkingDir));
  309. if (_fDoEnvSubst)
  310. DoEnvironmentSubst(_szWorkingDir, SIZECHARS(_szWorkingDir));
  311. //
  312. // if the passed directory is not valid (after env subst) dont
  313. // fail, act just like Win31 and use whatever the current dir is.
  314. //
  315. // Win31 is stranger than I could imagine, if you pass ShellExecute
  316. // an invalid directory, it will change the current drive.
  317. //
  318. if (!PathIsDirectory(_szWorkingDir))
  319. {
  320. if (PathGetDriveNumber(_szWorkingDir) >= 0)
  321. {
  322. TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() bad directory %s, using %c:", _szWorkingDir, _szWorkingDir[0]);
  323. PathStripToRoot(_szWorkingDir);
  324. }
  325. else
  326. {
  327. TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() bad directory %s, using current dir", _szWorkingDir);
  328. GetCurrentDirectory(SIZECHARS(_szWorkingDir), _szWorkingDir);
  329. }
  330. }
  331. else
  332. {
  333. goto Done;
  334. }
  335. }
  336. else
  337. {
  338. // if we are doing a SHCreateProcessAsUser or a normal shellexecute w/ the "runas" verb, and
  339. // the caller passed NULL for lpCurrentDirectory then we we do NOT want to fall back and use
  340. // the CWD because the newly logged on user might not have permissions in the current users CWD.
  341. // We will have better luck just passing NULL and letting the OS figure it out.
  342. if (_cpt != CPT_NORMAL)
  343. {
  344. _fUseNullCWD = TRUE;
  345. goto Done;
  346. }
  347. else
  348. {
  349. GetCurrentDirectory(SIZECHARS(_szWorkingDir), _szWorkingDir);
  350. }
  351. }
  352. // there are some cases where even CD is bad.
  353. // and CreateProcess() will then fail.
  354. if (!PathIsDirectory(_szWorkingDir))
  355. {
  356. GetWindowsDirectory(_szWorkingDir, SIZECHARS(_szWorkingDir));
  357. }
  358. Done:
  359. TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() pszIn = %s, NewDir = %s", pszIn, _szWorkingDir);
  360. }
  361. inline BOOL _IsNamespaceObject(LPCTSTR psz)
  362. {
  363. return (psz[0] == L':' && psz[1] == L':' && psz[2] == L'{');
  364. }
  365. void CShellExecute::_SetFile(LPCTSTR pszIn, BOOL fFileAndUrl)
  366. {
  367. if (pszIn && pszIn[0])
  368. {
  369. TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileName() Entered pszIn = %s", pszIn);
  370. _fIsUrl = UrlIs(pszIn, URLIS_URL);
  371. StrCpyN(_szFile, pszIn, SIZECHARS(_szFile));
  372. _fIsNamespaceObject = (!_fInvokeIdList && !_fUseClass && _IsNamespaceObject(_szFile));
  373. if (_fDoEnvSubst)
  374. DoEnvironmentSubst(_szFile, SIZECHARS(_szFile));
  375. if (fFileAndUrl)
  376. {
  377. ASSERT(!_fIsUrl);
  378. // our lpFile points to a string that contains both an Internet Cache
  379. // File location and the URL name that is associated with that cache file
  380. // (they are seperated by a single NULL). The application that we are
  381. // about to execute wants the URL name instead of the cache file, so
  382. // use it instead.
  383. int iLength = lstrlen(pszIn);
  384. LPCTSTR pszUrlPart = &pszIn[iLength + 1];
  385. if (IsBadStringPtr(pszUrlPart, INTERNET_MAX_URL_LENGTH) || !PathIsURL(pszUrlPart))
  386. {
  387. ASSERT(FALSE);
  388. }
  389. else
  390. {
  391. // we have a vaild URL, so use it
  392. StrCpyN(_szUrl, pszUrlPart, ARRAYSIZE(_szUrl));
  393. }
  394. }
  395. }
  396. else
  397. {
  398. // LEGACY - to support shellexec() of directories.
  399. if (!_lpID)
  400. StrCpyN(_szFile, _szWorkingDir, SIZECHARS(_szFile));
  401. }
  402. PathUnquoteSpaces(_szFile);
  403. TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileName() exit: szFile = %s", _szFile);
  404. }
  405. void CShellExecute::_SetFileAndUrl()
  406. {
  407. TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileAndUrl() enter: pszIn = %s", _szUrl);
  408. if (*_szUrl && SUCCEEDED(_QueryString(0, ASSOCSTR_EXECUTABLE, _szTemp, SIZECHARS(_szTemp)))
  409. && DoesAppWantUrl(_szTemp))
  410. {
  411. // we have a vaild URL, so use it
  412. StrCpyN(_szFile, _szUrl, ARRAYSIZE(_szFile));
  413. }
  414. TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileAndUrl() exit: szFile = %s",_szFile);
  415. }
  416. //
  417. // _TryValidateUNC() has queer return values
  418. //
  419. TRYRESULT CShellExecute::_TryValidateUNC(LPTSTR pszFile, LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl)
  420. {
  421. HRESULT hr = S_FALSE;
  422. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  423. if (PathIsUNC(pszFile))
  424. {
  425. TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC Is UNC: %s", pszFile);
  426. // Notes:
  427. // SHValidateUNC() returns FALSE if it failed. In such a case,
  428. // GetLastError will gives us the right error code.
  429. //
  430. if (!(_sfgaoID & SFGAO_FILESYSTEM) && !SHValidateUNC(_hwndParent, pszFile, _uConnect))
  431. {
  432. tr = TRY_STOP;
  433. // Note that SHValidateUNC calls SetLastError() and we need
  434. // to preserve that so that the caller makes the right decision
  435. DWORD err = GetLastError();
  436. if (ERROR_CANCELLED == err)
  437. {
  438. // Not a print share, use the error returned from the first call
  439. // _ReportWin32(ERROR_CANCELLED);
  440. // we dont need to report this error, it is the callers responsibility
  441. // the caller should GetLastError() on E_FAIL and do a _ReportWin32()
  442. TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC FAILED with ERROR_CANCELLED");
  443. }
  444. else if (pei && ERROR_NOT_SUPPORTED == err && PathIsUNC(pszFile))
  445. {
  446. //
  447. // Now check to see if it's a print share, if it is, we need to exec as pidl
  448. //
  449. // we only check for print shares when ERROR_NOT_SUPPORTED is returned
  450. // from the first call to SHValidateUNC(). this error means that
  451. // the RESOURCETYPE did not match the requested.
  452. //
  453. // Note: This call should not display "connect ui" because SHValidateUNC()
  454. // will already have shown UI if necessary/possible.
  455. // CONNECT_CURRENT_MEDIA is not used by any provider (per JSchwart)
  456. //
  457. if (SHValidateUNC(_hwndParent, pszFile, VALIDATEUNC_NOUI | VALIDATEUNC_PRINT))
  458. {
  459. tr = TRY_CONTINUE;
  460. TraceMsg(TF_SHELLEXEC, "SHEX::TVUNC found print share");
  461. }
  462. else
  463. // need to reset the orginal error ,cuz SHValidateUNC() has set it again
  464. SetLastError(err);
  465. }
  466. }
  467. else
  468. {
  469. TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC UNC is accessible");
  470. }
  471. }
  472. TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC exit: hr = %X", hr);
  473. switch (tr)
  474. {
  475. // TRY_CONTINUE_UNHANDLED pszFile is not a UNC or is a valid UNC according to the flags
  476. // TRY_CONTINUE pszFile is a valid UNC to a print share
  477. // TRY_STOP pszFile is a UNC but cannot be validated use GetLastError() to get the real error
  478. case TRY_CONTINUE:
  479. // we got a good UNC
  480. ASSERT(pei);
  481. tr = _DoExecPidl(pei, pidl);
  482. // if we dont get a pidl we just try something else.
  483. break;
  484. case TRY_STOP:
  485. if (pei)
  486. _ReportWin32(GetLastError());
  487. tr = TRY_STOP;
  488. break;
  489. default:
  490. break;
  491. }
  492. return tr;
  493. }
  494. HRESULT _InvokeInProcExec(IContextMenu *pcm, LPSHELLEXECUTEINFO pei)
  495. {
  496. HRESULT hr = E_OUTOFMEMORY;
  497. HMENU hmenu = CreatePopupMenu();
  498. if (hmenu)
  499. {
  500. CMINVOKECOMMANDINFOEX ici;
  501. void * pvFree;
  502. if (SUCCEEDED(SEI2ICIX(pei, &ici, &pvFree)))
  503. {
  504. BOOL fDefVerb (ici.lpVerb == NULL || *ici.lpVerb == 0);
  505. // This optimization eliminate creating handlers that
  506. // will not change the default verb
  507. UINT uFlags = fDefVerb ? CMF_DEFAULTONLY : 0;
  508. ici.fMask |= CMIC_MASK_FLAG_NO_UI;
  509. hr = pcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, uFlags);
  510. if (SUCCEEDED(hr))
  511. {
  512. if (fDefVerb)
  513. {
  514. UINT idCmd = GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0);
  515. if (-1 == idCmd)
  516. {
  517. // i dont think we should ever get here...
  518. ici.lpVerb = (LPSTR)MAKEINTRESOURCE(0); // best guess
  519. }
  520. else
  521. ici.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - CONTEXTMENU_IDCMD_FIRST);
  522. }
  523. hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
  524. }
  525. if (pvFree)
  526. LocalFree(pvFree);
  527. }
  528. DestroyMenu(hmenu);
  529. }
  530. return hr;
  531. }
  532. BOOL CShellExecute::_ShellExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidlExec)
  533. {
  534. IContextMenu *pcm;
  535. HRESULT hr = SHGetUIObjectFromFullPIDL(pidlExec, pei->hwnd, IID_PPV_ARG(IContextMenu, &pcm));
  536. if (SUCCEEDED(hr))
  537. {
  538. hr = _InvokeInProcExec(pcm, pei);
  539. pcm->Release();
  540. }
  541. if (FAILED(hr))
  542. {
  543. DWORD errWin32 = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : GetLastError();
  544. if (!errWin32)
  545. errWin32 = ERROR_ACCESS_DENIED;
  546. if (errWin32 != ERROR_CANCELLED)
  547. _ReportWin32(errWin32);
  548. }
  549. TraceMsg(TF_SHELLEXEC, "SHEX::_ShellExecPidl() exiting hr = %X", hr);
  550. return(SUCCEEDED(hr));
  551. }
  552. //
  553. // BOOL CShellExecute::_DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl)
  554. //
  555. // returns TRUE if a pidl was created, FALSE otherwise
  556. //
  557. TRYRESULT CShellExecute::_DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl)
  558. {
  559. TraceMsg(TF_SHELLEXEC, "SHEX::_DoExecPidl enter: szFile = %s", _szFile);
  560. LPITEMIDLIST pidlFree = NULL;
  561. if (!pidl)
  562. pidl = pidlFree = ILCreateFromPath(_szFile);
  563. if (pidl)
  564. {
  565. //
  566. // if _ShellExecPidl() FAILS, it does
  567. // Report() for us
  568. //
  569. _ShellExecPidl(pei, pidl);
  570. if (pidlFree)
  571. ILFree(pidlFree);
  572. return TRY_STOP;
  573. }
  574. else
  575. {
  576. TraceMsg(TF_SHELLEXEC, "SHEX::_DoExecPidl() unhandled cuz ILCreateFromPath() failed");
  577. return TRY_CONTINUE;
  578. }
  579. }
  580. /*----------------------------------------------------------
  581. Purpose: This function looks up the given file in "HKLM\Software\
  582. Microsoft\Windows\CurrentVersion\App Paths" to
  583. see if it has an absolute path registered.
  584. Returns: TRUE if the file has a registered path
  585. FALSE if it does not or if the provided filename has
  586. a relative path already
  587. Cond: !! Side effect: the szFile field may be changed by
  588. !! this function.
  589. */
  590. BOOL CShellExecute::_CheckForRegisteredProgram()
  591. {
  592. TCHAR szTemp[MAX_PATH];
  593. TraceMsg(TF_SHELLEXEC, "SHEX::CFRP entered");
  594. // Only supported for files with no paths specified
  595. if (PathIsFileSpec(_szFile)
  596. && PathToAppPath(_szFile, szTemp)
  597. && PathResolve(szTemp, NULL, PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS))
  598. {
  599. TraceMsg(TF_SHELLEXEC, "SHEX::CFRP Set szFile = %s", szTemp);
  600. StrCpyN(_szFile, szTemp, ARRAYSIZE(_szFile));
  601. return TRUE;
  602. }
  603. return FALSE;
  604. }
  605. BOOL CShellExecute::_Resolve(LPCITEMIDLIST *ppidl)
  606. {
  607. // No; get the fully qualified path and add .exe extension
  608. // if needed
  609. LPCTSTR rgszDirs[2] = { _szWorkingDir, NULL };
  610. const UINT uFlags = PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF;
  611. // if the Path is not an URL
  612. // and the path cant be resolved
  613. //
  614. // PathResolve() now does SetLastError() when we pass VERIFYEXISTS
  615. // this means that we can be assure if all these tests fail
  616. // that LastError is set.
  617. //
  618. // _CheckForRegisteredProgram() changes _szFile if
  619. // there is a registered program in the registry
  620. // so we recheck to see if it exists.
  621. if (!_fNoResolve && !_fIsUrl && !_fIsNamespaceObject &&
  622. !_CheckForRegisteredProgram())
  623. {
  624. if (!PathResolve(_szFile, rgszDirs, uFlags))
  625. {
  626. DWORD cchFile = ARRAYSIZE(_szFile);
  627. if (S_OK != UrlApplyScheme(_szFile, _szFile, &cchFile, URL_APPLY_GUESSSCHEME))
  628. {
  629. // No; file not found, bail out
  630. //
  631. // WARNING LEGACY - we must return ERROR_FILE_NOT_FOUND - ZekeL - 14-APR-99
  632. // some apps, specifically Netscape Navigator 4.5, rely on this
  633. // failing with ERROR_FILE_NOT_FOUND. so even though PathResolve() does
  634. // a SetLastError() to the correct error we cannot propagate that up
  635. //
  636. _ReportWin32(ERROR_FILE_NOT_FOUND);
  637. ASSERT(_err);
  638. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl FAILED %d", _err);
  639. return FALSE;
  640. }
  641. else
  642. _fIsUrl = TRUE;
  643. }
  644. }
  645. // _PerfPidl(ppidl);
  646. return TRUE;
  647. }
  648. // this is the SAFER exe detection API
  649. // only use if this is really a file system file
  650. // and we are planning on using CreateProcess()
  651. TRYRESULT CShellExecute::_VerifySaferTrust(PCWSTR pszFile)
  652. {
  653. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  654. DWORD dwPolicy, cbPolicy;
  655. if (_cpt == CPT_NORMAL
  656. && SaferGetPolicyInformation(
  657. SAFER_SCOPEID_MACHINE,
  658. SaferPolicyEnableTransparentEnforcement,
  659. sizeof(dwPolicy), &dwPolicy, &cbPolicy, NULL)
  660. && dwPolicy != 0
  661. && SaferiIsExecutableFileType(pszFile, TRUE)
  662. && (_pszQueryVerb && !StrCmpIW(_pszQueryVerb, L"open")))
  663. {
  664. SAFER_LEVEL_HANDLE hAuthzLevel;
  665. SAFER_CODE_PROPERTIES codeprop;
  666. // prepare the code properties struct.
  667. memset(&codeprop, 0, sizeof(codeprop));
  668. codeprop.cbSize = sizeof(SAFER_CODE_PROPERTIES);
  669. codeprop.dwCheckFlags = SAFER_CRITERIA_IMAGEPATH |
  670. SAFER_CRITERIA_IMAGEHASH |
  671. SAFER_CRITERIA_AUTHENTICODE;
  672. codeprop.ImagePath = pszFile;
  673. codeprop.dwWVTUIChoice = WTD_UI_NOBAD;
  674. codeprop.hWndParent = _hwndParent;
  675. //
  676. // check if file extension is of executable type, don't care on error
  677. //
  678. // evaluate all of the criteria and get the resulting level.
  679. if (SaferIdentifyLevel(
  680. 1, // only 1 element in codeprop[]
  681. &codeprop, // pointer to one-element array
  682. &hAuthzLevel, // receives identified level
  683. NULL))
  684. {
  685. //
  686. // try to log an event in case level != SAFER_LEVELID_FULLYTRUSTED
  687. //
  688. // compute the final restricted token that should be used.
  689. ASSERT(_hCloseToken == NULL);
  690. if (SaferComputeTokenFromLevel(
  691. hAuthzLevel, // identified level restrictions
  692. NULL, // source token
  693. &_hUserToken, // resulting restricted token
  694. SAFER_TOKEN_NULL_IF_EQUAL,
  695. NULL))
  696. {
  697. if (_hUserToken)
  698. {
  699. _cpt = CPT_ASUSER;
  700. // WARNING - runas is needed to circumvent DDE - ZekeL - 31 -JAN-2001
  701. // we must set runas as the verb so that we make sure
  702. // that we are not using a type that is going to do window reuse
  703. // via DDE (or anything else). if they dont support runas, then the
  704. // the exec will fail, intentionally.
  705. _pszQueryVerb = L"runas";
  706. tr = TRY_CONTINUE;
  707. }
  708. _hCloseToken = _hUserToken; // potentially NULL
  709. }
  710. else
  711. {
  712. // TODO: add event logging callback here.
  713. _ReportWin32(GetLastError());
  714. SaferRecordEventLogEntry(hAuthzLevel, pszFile, NULL);
  715. tr = TRY_STOP;
  716. }
  717. if (tr != TRY_STOP)
  718. {
  719. // we havent added anything to our log
  720. // try to log an event in case level != AUTHZLEVELID_FULLYTRUST ED
  721. DWORD dwLevelId;
  722. DWORD dwBufferSize;
  723. if (SaferGetLevelInformation(
  724. hAuthzLevel,
  725. SaferObjectLevelId,
  726. &dwLevelId,
  727. sizeof(DWORD),
  728. &dwBufferSize))
  729. {
  730. if ( dwLevelId != SAFER_LEVELID_FULLYTRUSTED )
  731. {
  732. SaferRecordEventLogEntry(hAuthzLevel,
  733. pszFile,
  734. NULL);
  735. }
  736. }
  737. }
  738. SaferCloseLevel(hAuthzLevel);
  739. }
  740. else
  741. {
  742. _ReportWin32(GetLastError());
  743. tr = TRY_STOP;
  744. }
  745. }
  746. return tr;
  747. }
  748. HANDLE _GetSandboxToken()
  749. {
  750. SAFER_LEVEL_HANDLE hConstrainedAuthz;
  751. HANDLE hSandboxToken = NULL;
  752. // right now we always use the SAFER_LEVELID_CONSTRAINED to "sandbox" the process
  753. if (SaferCreateLevel(SAFER_SCOPEID_MACHINE,
  754. SAFER_LEVELID_CONSTRAINED,
  755. SAFER_LEVEL_OPEN,
  756. &hConstrainedAuthz,
  757. NULL))
  758. {
  759. if (!SaferComputeTokenFromLevel(
  760. hConstrainedAuthz,
  761. NULL,
  762. &hSandboxToken,
  763. 0,
  764. NULL)) {
  765. hSandboxToken = NULL;
  766. }
  767. SaferCloseLevel(hConstrainedAuthz);
  768. }
  769. return hSandboxToken;
  770. }
  771. TRYRESULT CShellExecute::_ZoneCheckFile(PCWSTR pszFile)
  772. {
  773. TRYRESULT tr = TRY_STOP;
  774. // now we need to determine if it is intranet or local zone
  775. DWORD dwPolicy = 0, dwContext = 0;
  776. ZoneCheckUrlEx(pszFile, &dwPolicy, sizeof(dwPolicy), &dwContext, sizeof(dwContext),
  777. URLACTION_SHELL_SHELLEXECUTE, PUAF_ISFILE | PUAF_NOUI, NULL);
  778. dwPolicy = GetUrlPolicyPermissions(dwPolicy);
  779. switch (dwPolicy)
  780. {
  781. case URLPOLICY_ALLOW:
  782. tr = TRY_CONTINUE_UNHANDLED;
  783. // continue
  784. break;
  785. case URLPOLICY_QUERY:
  786. if (SafeOpenPromptForShellExec(_hwndParent, pszFile))
  787. {
  788. tr = TRY_CONTINUE;
  789. }
  790. else
  791. {
  792. // user cancelled
  793. tr = TRY_STOP;
  794. _ReportWin32(ERROR_CANCELLED);
  795. }
  796. break;
  797. case URLPOLICY_DISALLOW:
  798. tr = TRY_STOP;
  799. _ReportWin32(ERROR_ACCESS_DENIED);
  800. break;
  801. default:
  802. ASSERT(FALSE);
  803. break;
  804. }
  805. return tr;
  806. }
  807. TRYRESULT CShellExecute::_VerifyZoneTrust(PCWSTR pszFile)
  808. {
  809. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  810. //
  811. // pszFile maybe different than _szFile in the case of being invoked by a LNK or URL
  812. // in this case we could prompt for either but not both
  813. // we only care about the target file's type for determining dangerousness
  814. // so that shortcuts to TXT files should never get a prompt.
  815. // if (pszFile == internet) prompt(pszFile)
  816. // else if (_szFile = internet prompt(_szFile)
  817. //
  818. if (AssocIsDangerous(PathFindExtension(_szFile)))
  819. {
  820. // first try
  821. tr = _ZoneCheckFile(pszFile);
  822. if (tr == TRY_CONTINUE_UNHANDLED && pszFile != _szFile)
  823. tr = _ZoneCheckFile(_szFile);
  824. }
  825. return tr;
  826. }
  827. TRYRESULT CShellExecute::_VerifyExecTrust(LPSHELLEXECUTEINFO pei)
  828. {
  829. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  830. if ((_sfgaoID & (SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_STREAM)) == (SFGAO_FILESYSTEM | SFGAO_STREAM))
  831. {
  832. // if this is a FILE, we check for security implications
  833. // if fHasLinkName is set, then this invoke originates from an LNK file
  834. // the _lpTitle should have the acual path to the LNK. we want to verify
  835. // our trust of that more than the trust of the target
  836. PCWSTR pszFile = (pei->fMask & SEE_MASK_HASLINKNAME && _lpTitle) ? _lpTitle : _szFile;
  837. BOOL fZoneCheck = !(pei->fMask & SEE_MASK_NOZONECHECKS);
  838. if (fZoneCheck)
  839. {
  840. // 630796 - check the env var for policy scripts - ZekeL - 31-MAY-2002
  841. // scripts cannot be updated, and they need to be trusted
  842. // since a script can call into more scripts without passing
  843. // the SEE_MASK_NOZONECHECKS.
  844. if (GetEnvironmentVariable(L"SEE_MASK_NOZONECHECKS", _szTemp, ARRAYSIZE(_szTemp)))
  845. {
  846. fZoneCheck = (0 != StrCmpICW(_szTemp, L"1"));
  847. ASSERT(!IsProcessAnExplorer());
  848. }
  849. }
  850. if (fZoneCheck)
  851. tr = _VerifyZoneTrust(pszFile);
  852. if (tr == TRY_CONTINUE_UNHANDLED)
  853. tr = _VerifySaferTrust(pszFile);
  854. }
  855. return tr;
  856. }
  857. /*----------------------------------------------------------
  858. Purpose: decide whether it is appropriate to TryExecPidl()
  859. Returns: S_OK if it should _DoExecPidl()
  860. S_FALSE it shouldnt _DoExecPidl()
  861. E_FAIL ShellExec should quit Report*() has the real error
  862. Cond: !! Side effect: the szFile field may be changed by
  863. !! this function.
  864. */
  865. TRYRESULT CShellExecute::_TryExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl)
  866. {
  867. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  868. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl entered szFile = %s", _szFile);
  869. //
  870. // If we're explicitly given a class then we don't care if the file exists.
  871. // Just let the handler for the class worry about it, and _TryExecPidl()
  872. // will return the default of FALSE.
  873. //
  874. // these should never coincide
  875. RIP(!(_fInvokeIdList && _fUseClass));
  876. if ((*_szFile || pidl)
  877. && (!_fUseClass || _fInvokeIdList || _fIsNamespaceObject))
  878. {
  879. if (!pidl && !_fNoResolve && !_Resolve(&pidl))
  880. {
  881. tr = TRY_STOP;
  882. }
  883. if (tr == TRY_CONTINUE_UNHANDLED)
  884. {
  885. // The optimal execution path is to check for the default
  886. // verb and exec the pidl. It is smarter than all this path
  887. // code (it calls the context menu handlers, etc...)
  888. if ((!_pszQueryVerb && !(_fNoExecPidl))
  889. || _fIsUrl
  890. || _fInvokeIdList // caller told us to!
  891. || _fIsNamespaceObject // namespace objects can only be invoked through pidls
  892. || (_sfgaoID & SFGAO_LINK)
  893. || (!pidl && PathIsShortcut(_szFile, -1))) // to support LNK files and soon URL files
  894. {
  895. // this means that we can tryexecpidl
  896. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl() succeeded now TEP()");
  897. tr = _DoExecPidl(pei, pidl);
  898. }
  899. else
  900. {
  901. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl dont bother");
  902. }
  903. }
  904. }
  905. else
  906. {
  907. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl dont bother");
  908. }
  909. return tr;
  910. }
  911. HRESULT CShellExecute::_InitClassAssociations(LPCTSTR pszClass, HKEY hkClass, DWORD mask)
  912. {
  913. TraceMsg(TF_SHELLEXEC, "SHEX::InitClassAssoc enter: lpClass = %s, hkClass = %X", pszClass, hkClass);
  914. HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa));
  915. if (SUCCEEDED(hr))
  916. {
  917. if (_UseClassKey(mask))
  918. {
  919. hr = _pqa->Init(0, NULL, hkClass, NULL);
  920. }
  921. else if (_UseClassName(mask))
  922. {
  923. hr = _pqa->Init(0, pszClass, NULL, NULL);
  924. }
  925. else
  926. {
  927. // LEGACY - they didnt pass us anything to go on so we default to folder
  928. // because of the chaos of the original shellexec() we didnt even notice
  929. // when we had nothing to be associated with, and just used
  930. // our base key, which turns out to be explorer.
  931. // this permitted ShellExecute(NULL, "explore", NULL, NULL, NULL, SW_SHOW);
  932. // to succeed. in order to support this, we will fall back to it here.
  933. hr = _pqa->Init(0, L"Folder", NULL, NULL);
  934. }
  935. }
  936. return hr;
  937. }
  938. HRESULT CShellExecute::_InitShellAssociations(LPCTSTR pszFile, LPCITEMIDLIST pidl)
  939. {
  940. TraceMsg(TF_SHELLEXEC, "SHEX::InitShellAssoc enter: pszFile = %s, pidl = %X", pszFile, pidl);
  941. HRESULT hr = E_FAIL;
  942. LPITEMIDLIST pidlFree = NULL;
  943. if (*pszFile)
  944. {
  945. if (!pidl)
  946. {
  947. hr = SHILCreateFromPath(pszFile, &pidlFree, NULL);
  948. if (SUCCEEDED(hr))
  949. pidl = pidlFree;
  950. }
  951. }
  952. else if (pidl)
  953. {
  954. // Other parts of CShellExecute expect that _szFile is
  955. // filled in, so we may as well do it here.
  956. SHGetNameAndFlags(pidl, SHGDN_FORPARSING, _szFile, SIZECHARS(_szFile), NULL);
  957. _fNoResolve = TRUE;
  958. }
  959. if (pidl)
  960. {
  961. // NT#413115 - ShellExec("D:\") does AutoRun.inf instead of Folder.Open - ZekeL - 25-JUN-2001
  962. // this is because drivflder now explicitly supports GetUIObjectOf(IQueryAssociations)
  963. // whereas it didnt in win2k, so that SHGetAssociations() would fallback to "Folder".
  964. // to emulate this, we communicate that this associations object is going to be
  965. // used by ShellExec() for invocation, so we dont want all of the keys in the assoc array.
  966. //
  967. IBindCtx *pbc;
  968. TBCRegisterObjectParam(L"ShellExec SHGetAssociations", NULL, &pbc);
  969. hr = SHGetAssociations(pidl, (void **)&_pqa);
  970. if (pbc)
  971. pbc->Release();
  972. // NOTE: sometimes we can have the extension or even the progid in the registry, but there
  973. // is no "shell" subkey. An example of this is for .xls files in NT5: the index server guys
  974. // create HKCR\.xls and HKCR\Excel.Sheet.8 but all they put under Excel.Sheet.8 is the clsid.
  975. //
  976. // so we need to check and make sure that we have a valid command value for
  977. // this object. if we dont, then that means that this is not valid
  978. // class to shellexec with. we need to fall back to the Unknown key
  979. // so that we can query the Darwin/NT5 ClassStore and/or
  980. // show the openwith dialog box.
  981. //
  982. DWORD cch;
  983. if (FAILED(hr) ||
  984. (FAILED(_pqa->GetString(0, ASSOCSTR_COMMAND, _pszQueryVerb, NULL, &cch))
  985. && FAILED(_pqa->GetData(0, ASSOCDATA_MSIDESCRIPTOR, _pszQueryVerb, NULL, &cch))))
  986. {
  987. if (!_pqa)
  988. hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa));
  989. if (_pqa)
  990. {
  991. hr = _pqa->Init(0, L"Unknown", NULL, NULL);
  992. // this allows us to locate something
  993. // in the class store, but restricts us
  994. // from using the openwith dialog if the
  995. // caller instructed NOUI
  996. if (SUCCEEDED(hr) && _fNoUI)
  997. _fClassStoreOnly = TRUE;
  998. }
  999. }
  1000. }
  1001. else
  1002. {
  1003. LPCTSTR pszExt = PathFindExtension(_szFile);
  1004. if (*pszExt)
  1005. {
  1006. hr = _InitClassAssociations(pszExt, NULL, SEE_MASK_CLASSNAME);
  1007. if (S_OK!=hr)
  1008. {
  1009. TraceMsg(TF_WARNING, "SHEX::InitAssoc parsing failed, but there is a valid association for *.%s", pszExt);
  1010. }
  1011. }
  1012. }
  1013. if (pidlFree)
  1014. ILFree(pidlFree);
  1015. return hr;
  1016. }
  1017. TRYRESULT CShellExecute::_InitAssociations(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl)
  1018. {
  1019. HRESULT hr;
  1020. if (pei && (_fUseClass || (!_szFile[0] && !_lpID)))
  1021. {
  1022. hr = _InitClassAssociations(pei->lpClass, pei->hkeyClass, pei->fMask);
  1023. }
  1024. else
  1025. {
  1026. hr = _InitShellAssociations(_szFile, pidl ? pidl : _lpID);
  1027. }
  1028. TraceMsg(TF_SHELLEXEC, "SHEX::InitAssoc return %X", hr);
  1029. if (FAILED(hr))
  1030. {
  1031. if (PathIsExe(_szFile))
  1032. {
  1033. hr = S_FALSE;
  1034. }
  1035. else
  1036. {
  1037. _ReportWin32(ERROR_NO_ASSOCIATION);
  1038. }
  1039. }
  1040. return SUCCEEDED(hr) ? TRY_CONTINUE : TRY_STOP;
  1041. }
  1042. void CShellExecute::_SetStartup(LPSHELLEXECUTEINFO pei)
  1043. {
  1044. // Was zero filled by Alloc...
  1045. ASSERT(!_startup.cb);
  1046. _startup.cb = sizeof(_startup);
  1047. _startup.dwFlags |= STARTF_USESHOWWINDOW;
  1048. _startup.wShowWindow = (WORD) pei->nShow;
  1049. _startup.lpTitle = (LPTSTR)_lpTitle;
  1050. if (pei->fMask & SEE_MASK_RESERVED)
  1051. {
  1052. _startup.lpReserved = (LPTSTR)pei->hInstApp;
  1053. }
  1054. if ((pei->fMask & SEE_MASK_HASLINKNAME) && _lpTitle)
  1055. {
  1056. _startup.dwFlags |= STARTF_TITLEISLINKNAME;
  1057. }
  1058. if (pei->fMask & SEE_MASK_HOTKEY)
  1059. {
  1060. _startup.hStdInput = LongToHandle(pei->dwHotKey);
  1061. _startup.dwFlags |= STARTF_USEHOTKEY;
  1062. }
  1063. // Multi-monitor support (dli) pass a hMonitor to createprocess
  1064. #ifndef STARTF_HASHMONITOR
  1065. #define STARTF_HASHMONITOR 0x00000400 // same as HASSHELLDATA
  1066. #endif
  1067. if (pei->fMask & SEE_MASK_ICON)
  1068. {
  1069. _startup.hStdOutput = (HANDLE)pei->hIcon;
  1070. _startup.dwFlags |= STARTF_HASSHELLDATA;
  1071. }
  1072. else if (pei->fMask & SEE_MASK_HMONITOR)
  1073. {
  1074. _startup.hStdOutput = (HANDLE)pei->hMonitor;
  1075. _startup.dwFlags |= STARTF_HASHMONITOR;
  1076. }
  1077. else if (pei->hwnd)
  1078. {
  1079. _startup.hStdOutput = (HANDLE)MonitorFromWindow(pei->hwnd,MONITOR_DEFAULTTONEAREST);
  1080. _startup.dwFlags |= STARTF_HASHMONITOR;
  1081. }
  1082. TraceMsg(TF_SHELLEXEC, "SHEX::SetStartup() called");
  1083. }
  1084. DWORD CEnvironmentBlock::_BlockLen(LPCWSTR pszEnv)
  1085. {
  1086. LPCWSTR psz = pszEnv;
  1087. while (*psz)
  1088. {
  1089. psz += lstrlen(psz)+1;
  1090. }
  1091. return (DWORD)(psz - pszEnv) + 1;
  1092. }
  1093. DWORD CEnvironmentBlock::_BlockLenCached()
  1094. {
  1095. if (!_cchBlockLen && _pszBlock)
  1096. {
  1097. _cchBlockLen = _BlockLen(_pszBlock);
  1098. }
  1099. return _cchBlockLen;
  1100. }
  1101. HRESULT CEnvironmentBlock::_InitBlock(DWORD cchNeeded)
  1102. {
  1103. if (_BlockLenCached() + cchNeeded > _cchBlockSize)
  1104. {
  1105. if (!_pszBlock)
  1106. {
  1107. // we need to create a new block.
  1108. LPTSTR pszEnv = GetEnvBlock(_hToken);
  1109. if (pszEnv)
  1110. {
  1111. // Now lets allocate some memory for our block.
  1112. // -- Why 10 and not 11? Or 9? --
  1113. // Comment from BobDay: 2 of the 10 come from nul terminators of the
  1114. // pseem->_szTemp and cchT strings added on. The additional space might
  1115. // come from the fact that 16-bit Windows used to pass around an
  1116. // environment block that had some extra stuff on the end. The extra
  1117. // stuff had things like the path name (argv[0]) and a nCmdShow value.
  1118. DWORD cchEnv = _BlockLen(pszEnv);
  1119. DWORD cchAlloc = ROUNDUP(cchEnv + cchNeeded + 10, 256);
  1120. _pszBlock = (LPWSTR)LocalAlloc(LPTR, CbFromCchW(cchAlloc));
  1121. if (_pszBlock)
  1122. {
  1123. // copy stuff over
  1124. CopyMemory(_pszBlock, pszEnv, CbFromCchW(cchEnv));
  1125. _cchBlockSize = cchAlloc - 10; // leave the 10 out
  1126. _cchBlockLen = cchEnv;
  1127. }
  1128. FreeEnvBlock(_hToken, pszEnv);
  1129. }
  1130. }
  1131. else
  1132. {
  1133. // need to resize the current block
  1134. DWORD cchAlloc = ROUNDUP(_cchBlockSize + cchNeeded + 10, 256);
  1135. LPWSTR pszNew = (LPWSTR)LocalReAlloc(_pszBlock, CbFromCchW(cchAlloc), LMEM_MOVEABLE);
  1136. if (pszNew)
  1137. {
  1138. _cchBlockSize = cchAlloc - 10; // leave the 10 out
  1139. _pszBlock = pszNew;
  1140. }
  1141. }
  1142. }
  1143. return (_BlockLenCached() + cchNeeded <= _cchBlockSize) ? S_OK : E_OUTOFMEMORY;
  1144. }
  1145. BOOL CEnvironmentBlock::_FindVar(LPCWSTR pszVar, DWORD cchVar, LPWSTR *ppszBlockVar)
  1146. {
  1147. int iCmp = CSTR_LESS_THAN;
  1148. LPTSTR psz = _pszBlock;
  1149. ASSERT(_pszBlock);
  1150. for ( ; *psz && iCmp == CSTR_LESS_THAN; psz += lstrlen(psz)+1)
  1151. {
  1152. iCmp = CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, psz, cchVar, pszVar, cchVar);
  1153. *ppszBlockVar = psz;
  1154. }
  1155. if (iCmp == CSTR_LESS_THAN)
  1156. *ppszBlockVar = psz;
  1157. return iCmp == CSTR_EQUAL;
  1158. }
  1159. HRESULT CEnvironmentBlock::SetVar(LPCWSTR pszVar, LPCWSTR pszValue)
  1160. {
  1161. // additional size needed in worst case scenario.
  1162. // var + val + '=' + NULL
  1163. DWORD cchValue = lstrlenW(pszValue);
  1164. DWORD cchVar = lstrlenW(pszVar);
  1165. DWORD cchNeeded = cchVar + cchValue + 2;
  1166. HRESULT hr = _InitBlock(cchNeeded);
  1167. if (SUCCEEDED(hr))
  1168. {
  1169. // we have enough room in our private block
  1170. // to copy the whole thing
  1171. LPWSTR pszBlockVar;
  1172. if (_FindVar(pszVar, cchVar, &pszBlockVar))
  1173. {
  1174. // we need to replace this var
  1175. LPWSTR pszBlockVal = StrChrW(pszBlockVar, L'=');
  1176. DWORD cchBlockVal = lstrlenW(++pszBlockVal);
  1177. LPWSTR pszDst = pszBlockVal + cchValue + 1;
  1178. LPWSTR pszSrc = pszBlockVal + cchBlockVal + 1;
  1179. DWORD cchMove = _BlockLenCached() - (DWORD)(pszSrc - _pszBlock);
  1180. MoveMemory(pszDst, pszSrc, CbFromCchW(cchMove));
  1181. StrCpyW(pszBlockVal, pszValue);
  1182. _cchBlockLen = _cchBlockLen + cchValue - cchBlockVal;
  1183. ASSERT(_BlockLen(_pszBlock) == _cchBlockLen);
  1184. }
  1185. else
  1186. {
  1187. // this means that var doesnt exist yet
  1188. // however pszBlockVar points to where it
  1189. // would be alphabetically. need to make space right here
  1190. LPWSTR pszDst = pszBlockVar + cchNeeded;
  1191. INT cchMove = _BlockLenCached() - (DWORD)(pszBlockVar - _pszBlock);
  1192. MoveMemory(pszDst, pszBlockVar, CbFromCchW(cchMove));
  1193. StrCpyW(pszBlockVar, pszVar);
  1194. pszBlockVar += cchVar;
  1195. *pszBlockVar = L'=';
  1196. StrCpyW(++pszBlockVar, pszValue);
  1197. _cchBlockLen += cchNeeded;
  1198. ASSERT(_BlockLen(_pszBlock) == _cchBlockLen);
  1199. }
  1200. }
  1201. return hr;
  1202. }
  1203. HRESULT CEnvironmentBlock::AppendVar(LPCWSTR pszVar, WCHAR chDelimiter, LPCWSTR pszValue)
  1204. {
  1205. // we could make the delimiter optional
  1206. // additional size needed in worst case scenario.
  1207. // var + val + 'chDelim' + '=' + NULL
  1208. DWORD cchValue = lstrlenW(pszValue);
  1209. DWORD cchVar = lstrlenW(pszVar);
  1210. DWORD cchNeeded = cchVar + cchValue + 3;
  1211. HRESULT hr = _InitBlock(cchNeeded);
  1212. if (SUCCEEDED(hr))
  1213. {
  1214. // we have enough room in our private block
  1215. // to copy the whole thing
  1216. LPWSTR pszBlockVar;
  1217. if (_FindVar(pszVar, cchVar, &pszBlockVar))
  1218. {
  1219. // we need to append to this var
  1220. pszBlockVar += lstrlen(pszBlockVar);
  1221. LPWSTR pszDst = pszBlockVar + cchValue + 1;
  1222. int cchMove = _BlockLenCached() - (DWORD)(pszBlockVar - _pszBlock);
  1223. MoveMemory(pszDst, pszBlockVar, CbFromCchW(cchMove));
  1224. *pszBlockVar = chDelimiter;
  1225. StrCpyW(++pszBlockVar, pszValue);
  1226. _cchBlockLen += cchValue + 1;
  1227. ASSERT(_BlockLen(_pszBlock) == _cchBlockLen);
  1228. }
  1229. else
  1230. hr = SetVar(pszVar, pszValue);
  1231. }
  1232. return hr;
  1233. }
  1234. HRESULT CShellExecute::_BuildEnvironmentForNewProcess(LPCTSTR pszNewEnvString)
  1235. {
  1236. HRESULT hr;
  1237. _envblock.SetToken(_hUserToken);
  1238. // Use the _szTemp to build key to the programs specific
  1239. // key in the registry as well as other things...
  1240. hr = PathToAppPathKey(_szApplication, _szTemp, SIZECHARS(_szTemp));
  1241. if (SUCCEEDED(hr))
  1242. {
  1243. // Currently only clone environment if we have path.
  1244. DWORD cbTemp = sizeof(_szTemp);
  1245. if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, _szTemp, TEXT("PATH"), NULL, _szTemp, &cbTemp))
  1246. {
  1247. // setit up to be appended
  1248. hr = _envblock.AppendVar(L"PATH", L';', _szTemp);
  1249. }
  1250. }
  1251. if (SUCCEEDED(hr) && pszNewEnvString)
  1252. {
  1253. StrCpyN(_szTemp, pszNewEnvString, ARRAYSIZE(_szTemp));
  1254. LPTSTR pszValue = StrChrW(_szTemp, L'=');
  1255. if (pszValue)
  1256. {
  1257. *pszValue++ = 0;
  1258. hr = _envblock.SetVar(_szTemp, pszValue);
  1259. }
  1260. }
  1261. if (SUCCEEDED(hr) && SUCCEEDED(TBCGetEnvironmentVariable(L"__COMPAT_LAYER", _szTemp, ARRAYSIZE(_szTemp))))
  1262. {
  1263. hr = _envblock.SetVar(L"__COMPAT_LAYER", _szTemp);
  1264. }
  1265. return hr;
  1266. }
  1267. // Some apps when run no-active steal the focus anyway so we
  1268. // we set it back to the previously active window.
  1269. void CShellExecute::_FixActivationStealingApps(HWND hwndOldActive, int nShow)
  1270. {
  1271. HWND hwndNew;
  1272. if (nShow == SW_SHOWMINNOACTIVE && (hwndNew = GetForegroundWindow()) != hwndOldActive && IsIconic(hwndNew))
  1273. SetForegroundWindow(hwndOldActive);
  1274. }
  1275. //
  1276. // The flags that need to passed to CreateProcess()
  1277. //
  1278. DWORD CShellExecute::_GetCreateFlags(ULONG fMask)
  1279. {
  1280. DWORD dwFlags = 0;
  1281. dwFlags |= CREATE_DEFAULT_ERROR_MODE;
  1282. if (fMask & SEE_MASK_FLAG_SEPVDM)
  1283. {
  1284. dwFlags |= CREATE_SEPARATE_WOW_VDM;
  1285. }
  1286. dwFlags |= CREATE_UNICODE_ENVIRONMENT;
  1287. if (!(fMask & SEE_MASK_NO_CONSOLE))
  1288. {
  1289. dwFlags |= CREATE_NEW_CONSOLE;
  1290. }
  1291. return dwFlags;
  1292. }
  1293. //*** GetUEMAssoc -- approximate answer to 'is path an executable' (etc.)
  1294. // ENTRY/EXIT
  1295. // pszFile thing we asked to run (e.g. foo.xls)
  1296. // pszImage thing we ultimately ran (e.g. excel.exe)
  1297. int GetUEMAssoc(LPCTSTR pszFile, LPCTSTR pszImage, LPCITEMIDLIST pidl)
  1298. {
  1299. LPTSTR pszExt, pszExt2;
  1300. // .exe's and associations come thru here
  1301. // folders go thru ???
  1302. // links go thru ResolveLink
  1303. pszExt = PathFindExtension(pszFile);
  1304. if (StrCmpIC(pszExt, c_szDotExe) == 0) {
  1305. // only check .exe (assume .com, .bat, etc. are rare)
  1306. return UIBL_DOTEXE;
  1307. }
  1308. pszExt2 = PathFindExtension(pszImage);
  1309. // StrCmpC (non-I, yes-C) o.k ? i think so since
  1310. // all we really care about is that they don't match
  1311. if (StrCmpC(pszExt, pszExt2) != 0) {
  1312. TraceMsg(DM_MISC, "gua: UIBL_DOTASSOC file=%s image=%s", pszExt, pszExt2);
  1313. return UIBL_DOTASSOC;
  1314. }
  1315. int iRet = UIBL_DOTOTHER; // UIBL_DOTEXE?
  1316. if (pidl)
  1317. {
  1318. LPCITEMIDLIST pidlChild;
  1319. IShellFolder *psf;
  1320. if (SUCCEEDED(SHBindToFolderIDListParent(NULL, pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
  1321. {
  1322. if (SHGetAttributes(psf, pidlChild, SFGAO_FOLDER | SFGAO_STREAM) == SFGAO_FOLDER)
  1323. {
  1324. iRet = UIBL_DOTFOLDER;
  1325. }
  1326. psf->Release();
  1327. }
  1328. }
  1329. return iRet;
  1330. }
  1331. typedef struct {
  1332. TCHAR szAppName[MAX_PATH];
  1333. TCHAR szUser[UNLEN + 1];
  1334. TCHAR szDomain[GNLEN + 1];
  1335. TCHAR szPassword[PWLEN + 1];
  1336. CPTYPE cpt;
  1337. } LOGONINFO;
  1338. // this is what gets called in the normal runas case
  1339. void InitUserLogonDlg(LOGONINFO* pli, HWND hDlg, LPCTSTR pszFullUserName)
  1340. {
  1341. HWNDWSPrintf(GetDlgItem(hDlg, IDC_USECURRENTACCOUNT), pszFullUserName);
  1342. CheckRadioButton(hDlg, IDC_USECURRENTACCOUNT, IDC_USEOTHERACCOUNT, IDC_USECURRENTACCOUNT);
  1343. CheckDlgButton(hDlg, IDC_SANDBOX, TRUE);
  1344. EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), FALSE);
  1345. SetFocus(GetDlgItem(hDlg, IDOK));
  1346. }
  1347. // this is what gets called in the install app launching as non admin case
  1348. void InitSetupLogonDlg(LOGONINFO* pli, HWND hDlg, LPCTSTR pszFullUserName)
  1349. {
  1350. HWNDWSPrintf(GetDlgItem(hDlg, IDC_USECURRENTACCOUNT), pszFullUserName);
  1351. HWNDWSPrintf(GetDlgItem(hDlg, IDC_MESSAGEBOXCHECKEX), pszFullUserName);
  1352. CheckRadioButton(hDlg, IDC_USECURRENTACCOUNT, IDC_USEOTHERACCOUNT, IDC_USEOTHERACCOUNT);
  1353. EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), FALSE);
  1354. SetFocus(GetDlgItem(hDlg, IDC_CREDCTL));
  1355. }
  1356. BOOL_PTR CALLBACK UserLogon_DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1357. {
  1358. TCHAR szTemp[UNLEN + 1 + GNLEN + 1]; // enough to hold "reinerf@NTDEV" or "NTDEV\reinerf"
  1359. LOGONINFO *pli= (LOGONINFO*)GetWindowLongPtr(hDlg, DWLP_USER);
  1360. switch (uMsg)
  1361. {
  1362. case WM_INITDIALOG:
  1363. {
  1364. TCHAR szName[UNLEN];
  1365. TCHAR szFullName[UNLEN + 1 + GNLEN]; // enough to hold "reinerf@NTDEV" or "NTDEV\reinerf"
  1366. ULONG cchFullName = ARRAYSIZE(szFullName);
  1367. HWND hwndCred = GetDlgItem(hDlg, IDC_CREDCTL);
  1368. WPARAM wparamCredStyles = CRS_USERNAMES | CRS_CERTIFICATES | CRS_SMARTCARDS | CRS_ADMINISTRATORS | CRS_PREFILLADMIN;
  1369. pli = (LOGONINFO*)lParam;
  1370. SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)pli);
  1371. if (!IsOS(OS_DOMAINMEMBER))
  1372. {
  1373. wparamCredStyles |= CRS_COMPLETEUSERNAME;
  1374. }
  1375. if (!Credential_InitStyle(hwndCred, wparamCredStyles))
  1376. {
  1377. EndDialog(hDlg, IDCANCEL);
  1378. }
  1379. // Limit the user name and password
  1380. Credential_SetUserNameMaxChars(hwndCred, UNLEN + 1 + GNLEN); // enough room for "reinerf@NTDEV" or "NTDEV\reinerf"
  1381. Credential_SetPasswordMaxChars(hwndCred, PWLEN);
  1382. if (!GetUserNameEx(NameSamCompatible, szFullName, &cchFullName))
  1383. {
  1384. ULONG cchName;
  1385. if (GetUserNameEx(NameDisplay, szName, &(cchName = ARRAYSIZE(szName))) ||
  1386. GetUserName(szName, &(cchName = ARRAYSIZE(szName))) ||
  1387. (GetEnvironmentVariable(TEXT("USERNAME"), szName, ARRAYSIZE(szName)) > 0))
  1388. {
  1389. if (GetEnvironmentVariable(TEXT("USERDOMAIN"), szFullName, ARRAYSIZE(szFullName)) > 0)
  1390. {
  1391. StringCchCat(szFullName, ARRAYSIZE(szFullName), TEXT("\\"));
  1392. StringCchCat(szFullName, ARRAYSIZE(szFullName), szName);
  1393. }
  1394. else
  1395. {
  1396. // use just the username if we cannot get a domain name
  1397. StrCpyN(szFullName, szName, ARRAYSIZE(szFullName));
  1398. }
  1399. }
  1400. else
  1401. {
  1402. TraceMsg(TF_WARNING, "UserLogon_DlgProc: failed to get the user's name using various methods");
  1403. szFullName[0] = TEXT('\0');
  1404. }
  1405. }
  1406. // call the proper init function depending on whether this is a setup program launching or the normal runas case
  1407. switch (pli->cpt)
  1408. {
  1409. case CPT_WITHLOGONADMIN:
  1410. {
  1411. InitSetupLogonDlg(pli, hDlg, szFullName);
  1412. break;
  1413. }
  1414. case CPT_WITHLOGON:
  1415. {
  1416. InitUserLogonDlg(pli, hDlg, szFullName);
  1417. break;
  1418. }
  1419. default:
  1420. {
  1421. ASSERTMSG(FALSE, "UserLogon_DlgProc: found CPTYPE that is not CPT_WITHLOGON or CPT_WITHLOGONADMIN!");
  1422. }
  1423. }
  1424. break;
  1425. }
  1426. break;
  1427. case WM_COMMAND:
  1428. {
  1429. CPTYPE cptRet = CPT_WITHLOGONCANCELLED;
  1430. int idCmd = GET_WM_COMMAND_ID(wParam, lParam);
  1431. switch (idCmd)
  1432. {
  1433. /* need some way to tell that valid credentials are present so we will only
  1434. enable the ok button if the user has something that is somewhat valid
  1435. case IDC_USERNAME:
  1436. if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_UPDATE)
  1437. {
  1438. EnableOKButtonFromID(hDlg, IDC_USERNAME);
  1439. GetDlgItemText(hDlg, IDC_USERNAME, szTemp, ARRAYSIZE(szTemp));
  1440. }
  1441. break;
  1442. */
  1443. case IDC_USEOTHERACCOUNT:
  1444. case IDC_USECURRENTACCOUNT:
  1445. if (IsDlgButtonChecked(hDlg, IDC_USECURRENTACCOUNT))
  1446. {
  1447. EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), FALSE);
  1448. EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), TRUE);
  1449. // EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
  1450. }
  1451. else
  1452. {
  1453. EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), TRUE);
  1454. EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), FALSE);
  1455. Credential_SetUserNameFocus(GetDlgItem(hDlg, IDC_CREDCTL));
  1456. // EnableOKButtonFromID(hDlg, IDC_USERNAME);
  1457. }
  1458. break;
  1459. case IDOK:
  1460. if (IsDlgButtonChecked(hDlg, IDC_USEOTHERACCOUNT))
  1461. {
  1462. HWND hwndCred = GetDlgItem(hDlg, IDC_CREDCTL);
  1463. if (Credential_GetUserName(hwndCred, szTemp, ARRAYSIZE(szTemp)) &&
  1464. Credential_GetPassword(hwndCred, pli->szPassword, ARRAYSIZE(pli->szPassword)))
  1465. {
  1466. CredUIParseUserName(szTemp,
  1467. pli->szUser,
  1468. ARRAYSIZE(pli->szUser),
  1469. pli->szDomain,
  1470. ARRAYSIZE(pli->szDomain));
  1471. }
  1472. cptRet = pli->cpt;
  1473. }
  1474. else
  1475. {
  1476. if (IsDlgButtonChecked(hDlg, IDC_SANDBOX))
  1477. cptRet = CPT_SANDBOX;
  1478. else
  1479. cptRet = CPT_NORMAL;
  1480. }
  1481. // fall through
  1482. case IDCANCEL:
  1483. EndDialog(hDlg, cptRet);
  1484. return TRUE;
  1485. break;
  1486. }
  1487. break;
  1488. }
  1489. default:
  1490. return FALSE;
  1491. }
  1492. if (!pli || (pli->cpt == CPT_WITHLOGONADMIN))
  1493. {
  1494. // we want the MessageBoxCheckExDlgProc have a crack at all messages in
  1495. // the CPT_WITHLOGONADMIN case, so return FALSE here
  1496. return FALSE;
  1497. }
  1498. else
  1499. {
  1500. return TRUE;
  1501. }
  1502. }
  1503. // implement this after we figure out what
  1504. // errors that CreateProcessWithLogonW() will return
  1505. // that mean the user should retry the logon.
  1506. BOOL _IsLogonError(DWORD err)
  1507. {
  1508. static const DWORD s_aLogonErrs[] = {
  1509. ERROR_LOGON_FAILURE,
  1510. ERROR_ACCOUNT_RESTRICTION,
  1511. ERROR_INVALID_LOGON_HOURS,
  1512. ERROR_INVALID_WORKSTATION,
  1513. ERROR_PASSWORD_EXPIRED,
  1514. ERROR_ACCOUNT_DISABLED,
  1515. ERROR_NONE_MAPPED,
  1516. ERROR_NO_SUCH_USER,
  1517. ERROR_INVALID_ACCOUNT_NAME
  1518. };
  1519. for (int i = 0; i < ARRAYSIZE(s_aLogonErrs); i++)
  1520. {
  1521. if (err == s_aLogonErrs[i])
  1522. return TRUE;
  1523. }
  1524. return FALSE;
  1525. }
  1526. BOOL CheckForAppPathsBoolValue(LPCTSTR pszImageName, LPCTSTR pszValueName)
  1527. {
  1528. BOOL bRet = FALSE;
  1529. TCHAR szAppPathKeyName[MAX_PATH + ARRAYSIZE(REGSTR_PATH_APPPATHS) + 2]; // +2 = +1 for '\' and +1 for the null terminator
  1530. DWORD cbSize = sizeof(bRet);
  1531. HRESULT hr = PathToAppPathKey(pszImageName, szAppPathKeyName, ARRAYSIZE(szAppPathKeyName));
  1532. if (SUCCEEDED(hr))
  1533. {
  1534. SHGetValue(HKEY_LOCAL_MACHINE, szAppPathKeyName, pszValueName, NULL, &bRet, &cbSize);
  1535. }
  1536. return bRet;
  1537. }
  1538. __inline BOOL IsRunAsSetupExe(LPCTSTR pszImageName)
  1539. {
  1540. return CheckForAppPathsBoolValue(pszImageName, TEXT("RunAsOnNonAdminInstall"));
  1541. }
  1542. __inline BOOL IsTSSetupExe(LPCTSTR pszImageName)
  1543. {
  1544. return CheckForAppPathsBoolValue(pszImageName, TEXT("BlockOnTSNonInstallMode"));
  1545. }
  1546. typedef BOOL (__stdcall * PFNTERMSRVAPPINSTALLMODE)(void);
  1547. // This function is used by hydra (Terminal Server) to see if we
  1548. // are in application install mode
  1549. //
  1550. // exported from kernel32.dll by name but not in kernel32.lib (it is in kernel32p.lib, bogus)
  1551. BOOL TermsrvAppInstallMode()
  1552. {
  1553. static PFNTERMSRVAPPINSTALLMODE s_pfn = NULL;
  1554. if (NULL == s_pfn)
  1555. {
  1556. s_pfn = (PFNTERMSRVAPPINSTALLMODE)GetProcAddress(LoadLibrary(TEXT("KERNEL32.DLL")), "TermsrvAppInstallMode");
  1557. }
  1558. return s_pfn ? s_pfn() : FALSE;
  1559. }
  1560. //
  1561. // this function checks for the different cases where we need to display a "runas" or warning dialog
  1562. // before a program is run.
  1563. //
  1564. // NOTE: pli->raType is an outparam that tells the caller what type of dialog is needed
  1565. //
  1566. // return: TRUE - we need to bring up a dialog
  1567. // FALSE - we do not need to prompt the user
  1568. //
  1569. CPTYPE CheckForInstallApplication(LPCTSTR pszApplicationName, LOGONINFO* pli)
  1570. {
  1571. // if we are on a TS "Application Server" machine, AND this is a TS setup exe (eg install.exe or setup.exe)
  1572. // AND we aren't in install mode...
  1573. if (IsOS(OS_TERMINALSERVER) && IsTSSetupExe(pszApplicationName) && !TermsrvAppInstallMode())
  1574. {
  1575. // ...AND the app we are launching is not TS aware, then we block the install and tell the user to go
  1576. // to Add/Remove Programs.
  1577. if (!IsExeTSAware(pszApplicationName))
  1578. {
  1579. TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: blocking the install on TS because the machine is not in install mode for %s", pszApplicationName);
  1580. return CPT_INSTALLTS;
  1581. }
  1582. }
  1583. // the hyrda case failed, so we check for the user not running as an admin but launching a setup exe (eg winnt32.exe, install.exe, or setup.exe)
  1584. if (!SHRestricted(REST_NORUNASINSTALLPROMPT) && IsRunAsSetupExe(pszApplicationName) && !IsUserAnAdmin())
  1585. {
  1586. BOOL bPromptForInstall = TRUE;
  1587. if (!SHRestricted(REST_PROMPTRUNASINSTALLNETPATH))
  1588. {
  1589. if (PathIsUNC(pszApplicationName) || IsNetDrive(PathGetDriveNumber(pszApplicationName)))
  1590. {
  1591. TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: not prompting for runas install on unc/network path %s", pszApplicationName);
  1592. bPromptForInstall = FALSE;
  1593. }
  1594. }
  1595. if (bPromptForInstall)
  1596. {
  1597. TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: bringing up the Run As... dialog for %s", pszApplicationName);
  1598. return CPT_WITHLOGONADMIN;
  1599. }
  1600. }
  1601. return CPT_NORMAL;
  1602. }
  1603. typedef HRESULT (__stdcall * PFN_INSTALLONTERMINALSERVERWITHUI)(IN HWND hwnd, IN LPCWSTR lpApplicationName, // name of executable module
  1604. LPCWSTR lpCommandLine, // command line string
  1605. LPSECURITY_ATTRIBUTES lpProcessAttributes,
  1606. LPSECURITY_ATTRIBUTES lpThreadAttributes,
  1607. BOOL bInheritHandles, // handle inheritance flag
  1608. DWORD dwCreationFlags, // creation flags
  1609. void *lpEnvironment, // new environment block
  1610. LPCWSTR lpCurrentDirectory, // current directory name
  1611. LPSTARTUPINFOW lpStartupInfo,
  1612. LPPROCESS_INFORMATION lpProcessInformation);
  1613. HRESULT InstallOnTerminalServerWithUIDD(IN HWND hwnd, IN LPCWSTR lpApplicationName, // name of executable module
  1614. IN LPCWSTR lpCommandLine, // command line string
  1615. IN LPSECURITY_ATTRIBUTES lpProcessAttributes,
  1616. IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
  1617. IN BOOL bInheritHandles, // handle inheritance flag
  1618. IN DWORD dwCreationFlags, // creation flags
  1619. IN void *lpEnvironment, // new environment block
  1620. IN LPCWSTR lpCurrentDirectory, // current directory name
  1621. IN LPSTARTUPINFOW lpStartupInfo,
  1622. IN LPPROCESS_INFORMATION lpProcessInformation)
  1623. {
  1624. HRESULT hr = E_FAIL;
  1625. HINSTANCE hDll = LoadLibrary(TEXT("appwiz.cpl"));
  1626. if (hDll)
  1627. {
  1628. PFN_INSTALLONTERMINALSERVERWITHUI pfnInstallOnTerminalServerWithUI = NULL;
  1629. pfnInstallOnTerminalServerWithUI = (PFN_INSTALLONTERMINALSERVERWITHUI) GetProcAddress(hDll, "InstallOnTerminalServerWithUI");
  1630. if (pfnInstallOnTerminalServerWithUI)
  1631. {
  1632. hr = pfnInstallOnTerminalServerWithUI(hwnd, lpApplicationName, lpCommandLine, lpProcessAttributes,
  1633. lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
  1634. lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
  1635. }
  1636. FreeLibrary(hDll);
  1637. }
  1638. return hr;
  1639. }
  1640. CPTYPE _LogonUser(HWND hwnd, CPTYPE cpt, LOGONINFO *pli)
  1641. {
  1642. if (CredUIInitControls())
  1643. {
  1644. pli->cpt = cpt;
  1645. switch (cpt)
  1646. {
  1647. case CPT_WITHLOGON:
  1648. // this is the normal "Run as..." verb dialog
  1649. cpt = (CPTYPE) DialogBoxParam(HINST_THISDLL,
  1650. MAKEINTRESOURCE(DLG_RUNUSERLOGON),
  1651. hwnd,
  1652. UserLogon_DlgProc,
  1653. (LPARAM)pli);
  1654. break;
  1655. case CPT_WITHLOGONADMIN:
  1656. // in the non-administrator setup app case. we want the "don't show me
  1657. // this again" functionality, so we use the SHMessageBoxCheckEx function
  1658. cpt = (CPTYPE) SHMessageBoxCheckEx(hwnd,
  1659. HINST_THISDLL,
  1660. MAKEINTRESOURCE(DLG_RUNSETUPLOGON),
  1661. UserLogon_DlgProc,
  1662. (void*)pli,
  1663. CPT_NORMAL, // if they checked the "dont show me this again", we want to just launch it as the current user
  1664. TEXT("WarnOnNonAdminInstall"));
  1665. break;
  1666. default:
  1667. {
  1668. ASSERTMSG(FALSE, "_SHCreateProcess: pli->raType not recognized!");
  1669. }
  1670. break;
  1671. }
  1672. return cpt;
  1673. }
  1674. return CPT_FAILED;
  1675. }
  1676. //
  1677. // SHCreateProcess()
  1678. // WARNING: lpApplicationName is not actually passed to CreateProcess() it is
  1679. // for internal use only.
  1680. //
  1681. BOOL _SHCreateProcess(HWND hwnd,
  1682. HANDLE hToken,
  1683. LPCTSTR pszDisplayName,
  1684. LPCTSTR lpApplicationName,
  1685. LPTSTR lpCommandLine,
  1686. DWORD dwCreationFlags,
  1687. LPSECURITY_ATTRIBUTES lpProcessAttributes,
  1688. LPSECURITY_ATTRIBUTES lpThreadAttributes,
  1689. BOOL bInheritHandles,
  1690. void *lpEnvironment,
  1691. LPCTSTR lpCurrentDirectory,
  1692. LPSTARTUPINFO lpStartupInfo,
  1693. LPPROCESS_INFORMATION lpProcessInformation,
  1694. CPTYPE cpt,
  1695. BOOL fUEM)
  1696. {
  1697. LOGONINFO li = {0};
  1698. // maybe we should do this for all calls
  1699. // except CPT_ASUSER??
  1700. if (cpt == CPT_NORMAL)
  1701. {
  1702. // see if we need to put up a warning prompt either because the user is not an
  1703. // admin or this is hydra and we are not in install mode.
  1704. cpt = CheckForInstallApplication(lpApplicationName, &li);
  1705. }
  1706. if ((cpt == CPT_WITHLOGON || cpt == CPT_WITHLOGONADMIN) && pszDisplayName)
  1707. {
  1708. StringCchCopy(li.szAppName, ARRAYSIZE(li.szAppName), pszDisplayName);
  1709. RetryUserLogon:
  1710. cpt = _LogonUser(hwnd, cpt, &li);
  1711. }
  1712. BOOL fRet = FALSE;
  1713. DWORD err = NOERROR;
  1714. // BUGFIX #612540 + #616999 - CMD and BAT files are handled specially by CreateProcess() - ZekeL - 14-MAY-2002
  1715. // CreateProcess() handles prepending "cmd.exe /c" differently since
  1716. // we now pass in both the lpApplicationName and the lpCommandLine.
  1717. // changing CreateProcess() is too risky, and so is changing the file assocs
  1718. // for CMD and BAT files only, we will not pass in both parameters
  1719. if (PathMatchSpec(PathFindFileName(lpApplicationName), L"*.CMD;*.BAT"))
  1720. lpApplicationName = NULL;
  1721. switch(cpt)
  1722. {
  1723. case CPT_NORMAL:
  1724. {
  1725. // DEFAULT use CreateProcess
  1726. fRet = CreateProcess(lpApplicationName,
  1727. lpCommandLine,
  1728. lpProcessAttributes,
  1729. lpThreadAttributes,
  1730. bInheritHandles,
  1731. dwCreationFlags,
  1732. lpEnvironment,
  1733. lpCurrentDirectory,
  1734. lpStartupInfo,
  1735. lpProcessInformation);
  1736. }
  1737. break;
  1738. case CPT_SANDBOX:
  1739. {
  1740. ASSERT(!hToken);
  1741. hToken = _GetSandboxToken();
  1742. if (hToken)
  1743. {
  1744. // using our special token
  1745. fRet = CreateProcessAsUser(hToken,
  1746. lpApplicationName,
  1747. lpCommandLine,
  1748. lpProcessAttributes,
  1749. lpThreadAttributes,
  1750. bInheritHandles,
  1751. dwCreationFlags,
  1752. lpEnvironment,
  1753. lpCurrentDirectory,
  1754. lpStartupInfo,
  1755. lpProcessInformation);
  1756. CloseHandle(hToken);
  1757. }
  1758. // no token means failure.
  1759. }
  1760. break;
  1761. case CPT_ASUSER:
  1762. {
  1763. if (hToken)
  1764. {
  1765. // using our special token
  1766. fRet = CreateProcessAsUser(hToken,
  1767. lpApplicationName,
  1768. lpCommandLine,
  1769. lpProcessAttributes,
  1770. lpThreadAttributes,
  1771. bInheritHandles,
  1772. dwCreationFlags | CREATE_PRESERVE_CODE_AUTHZ_LEVEL,
  1773. lpEnvironment,
  1774. lpCurrentDirectory,
  1775. lpStartupInfo,
  1776. lpProcessInformation);
  1777. }
  1778. else
  1779. {
  1780. // no token means normal create process, but with the "preserve authz level" flag.
  1781. fRet = CreateProcess(lpApplicationName,
  1782. lpCommandLine,
  1783. lpProcessAttributes,
  1784. lpThreadAttributes,
  1785. bInheritHandles,
  1786. dwCreationFlags | CREATE_PRESERVE_CODE_AUTHZ_LEVEL,
  1787. lpEnvironment,
  1788. lpCurrentDirectory,
  1789. lpStartupInfo,
  1790. lpProcessInformation);
  1791. }
  1792. }
  1793. break;
  1794. case CPT_INSTALLTS:
  1795. {
  1796. HRESULT hr = InstallOnTerminalServerWithUIDD(hwnd,
  1797. lpApplicationName,
  1798. lpCommandLine,
  1799. lpProcessAttributes,
  1800. lpThreadAttributes,
  1801. bInheritHandles,
  1802. dwCreationFlags,
  1803. lpEnvironment,
  1804. lpCurrentDirectory,
  1805. lpStartupInfo,
  1806. lpProcessInformation);
  1807. fRet = SUCCEEDED(hr);
  1808. err = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : ERROR_ACCESS_DENIED;
  1809. }
  1810. break;
  1811. case CPT_WITHLOGON:
  1812. case CPT_WITHLOGONADMIN:
  1813. {
  1814. LPTSTR pszDesktop = lpStartupInfo->lpDesktop;
  1815. // 99/08/19 #389284 vtan: clip username and domain to 125
  1816. // characters each to avoid hitting the combined MAX_PATH
  1817. // limit in AllowDesktopAccessToUser in advapi32.dll which
  1818. // is invoked by CreateProcessWithLogonW.
  1819. // This can be removed when the API is fixed. Check:
  1820. // %_ntbindir%\mergedcomponents\advapi\cseclogn.cxx
  1821. li.szUser[125] = li.szDomain[125] = 0;
  1822. // we are attempting logon the user. NOTE: pass LOGON_WITH_PROFILE so that we ensure that the profile is loaded
  1823. fRet = CreateProcessWithLogonW(li.szUser,
  1824. li.szDomain,
  1825. li.szPassword,
  1826. LOGON_WITH_PROFILE,
  1827. lpApplicationName,
  1828. lpCommandLine,
  1829. dwCreationFlags,
  1830. NULL, // we pass a null lpEnvironment so that the new process will inherit the new users default env
  1831. lpCurrentDirectory,
  1832. lpStartupInfo,
  1833. lpProcessInformation);
  1834. if (!fRet)
  1835. {
  1836. // HACKHACK: When CreateProcessWithLogon fails, it munges the desktop. This causes
  1837. // the next call to "Appear" to fail because the app show up on another desktop...
  1838. // Why? I don't know...
  1839. // I'm going to assign the bug back to them and have them fix it on their end, this is just to
  1840. // work around their bug.
  1841. if (lpStartupInfo)
  1842. lpStartupInfo->lpDesktop = pszDesktop;
  1843. // ShellMessageBox can alter LastError
  1844. err = GetLastError();
  1845. if (_IsLogonError(err))
  1846. {
  1847. TCHAR szTemp[MAX_PATH];
  1848. LoadString(HINST_THISDLL, IDS_CANTLOGON, szTemp, SIZECHARS(szTemp));
  1849. SHSysErrorMessageBox(
  1850. hwnd,
  1851. li.szAppName,
  1852. IDS_SHLEXEC_ERROR,
  1853. err,
  1854. szTemp,
  1855. MB_OK | MB_ICONSTOP);
  1856. err = NOERROR;
  1857. goto RetryUserLogon;
  1858. }
  1859. }
  1860. }
  1861. break;
  1862. case CPT_WITHLOGONCANCELLED:
  1863. err = ERROR_CANCELLED;
  1864. break;
  1865. }
  1866. // fire *after* the actual process since:
  1867. // - if there's a bug we at least get the process started (hopefully)
  1868. // - don't want to log failed events (for now at least)
  1869. if (fRet)
  1870. {
  1871. if (fUEM && UEMIsLoaded())
  1872. {
  1873. // skip the call if stuff isn't there yet.
  1874. // the load is expensive (forces ole32.dll and browseui.dll in
  1875. // and then pins browseui).
  1876. UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)lpApplicationName);
  1877. // we do the UIBW_RUNASSOC elsewhere. this can cause slight
  1878. // inaccuracies since there's no guarantees the 2 places are
  1879. // 'paired'. however it's way easier to do UIBW_RUNASSOC
  1880. // elsewhere so we'll live w/ it.
  1881. }
  1882. }
  1883. else if (err)
  1884. {
  1885. SetLastError(err);
  1886. }
  1887. else
  1888. {
  1889. // somebody is responsible for setting this...
  1890. ASSERT(GetLastError());
  1891. }
  1892. // eliminate the password
  1893. SecureZeroMemory(&li, sizeof(li));
  1894. return fRet;
  1895. }
  1896. BOOL _ParamIsApp(PCWSTR pszCmdTemplate)
  1897. {
  1898. return (0 == StrCmpNW(pszCmdTemplate, L"%1", ARRAYSIZE(L"%1")-1))
  1899. || (0 == StrCmpNW(pszCmdTemplate, L"\"%1\"", ARRAYSIZE(L"\"%1\"")-1));
  1900. }
  1901. BOOL CShellExecute::_FileIsApp()
  1902. {
  1903. return !_szCmdTemplate[0] && PathIsExe(_szFile) && (!_pszQueryVerb || StrCmpI(_pszQueryVerb, TEXT("open")));
  1904. }
  1905. __inline BOOL IsConsoleApp(PCWSTR pszApp)
  1906. {
  1907. return GetExeType(pszApp) == PEMAGIC;
  1908. }
  1909. BOOL IsCurrentProcessConsole()
  1910. {
  1911. static TRIBIT s_tbConsole = TRIBIT_UNDEFINED;
  1912. if (s_tbConsole == TRIBIT_UNDEFINED)
  1913. {
  1914. WCHAR sz[MAX_PATH];
  1915. if (GetModuleFileNameW(NULL, sz, ARRAYSIZE(sz))
  1916. && IsConsoleApp(sz))
  1917. {
  1918. s_tbConsole = TRIBIT_TRUE;
  1919. }
  1920. else
  1921. {
  1922. s_tbConsole = TRIBIT_FALSE;
  1923. }
  1924. }
  1925. return s_tbConsole == TRIBIT_TRUE;
  1926. }
  1927. BOOL CShellExecute::_SetCommand(void)
  1928. {
  1929. HRESULT hr;
  1930. BOOL fRet = FALSE;
  1931. if (_ParamIsApp(_szCmdTemplate) || _FileIsApp())
  1932. {
  1933. // we need to fix up the parameters
  1934. hr = StringCchCopy(_szApplication, ARRAYSIZE(_szApplication), _szFile);
  1935. DWORD cchImageName = ARRAYSIZE(_szAppFriendly);
  1936. AssocQueryString(ASSOCF_VERIFY | ASSOCF_INIT_BYEXENAME, ASSOCSTR_FRIENDLYAPPNAME, _szApplication, NULL, _szAppFriendly, &cchImageName);
  1937. StringCchCopy(_szPolicyApp, ARRAYSIZE(_szPolicyApp), _szFile);
  1938. }
  1939. else if (_szCmdTemplate[0])
  1940. {
  1941. PWSTR pszApp;
  1942. PWSTR pszCmd;
  1943. hr = SHEvaluateSystemCommandTemplate(_szCmdTemplate, &pszApp, &pszCmd, NULL);
  1944. if (SUCCEEDED(hr))
  1945. {
  1946. // we need to fix up the parameters
  1947. hr = StringCchCopy(_szApplication, ARRAYSIZE(_szApplication), pszApp);
  1948. StringCchCopy(_szCmdTemplate, ARRAYSIZE(_szCmdTemplate), pszCmd);
  1949. // the Assoc code will redirect for RunDll32 to the DLL name
  1950. _QueryString(ASSOCF_VERIFY, ASSOCSTR_FRIENDLYAPPNAME, _szAppFriendly, ARRAYSIZE(_szAppFriendly));
  1951. _QueryString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, _szPolicyApp, ARRAYSIZE(_szPolicyApp));
  1952. CoTaskMemFree(pszApp);
  1953. CoTaskMemFree(pszCmd);
  1954. }
  1955. else if (hr == CO_E_APPNOTFOUND)
  1956. {
  1957. // this replaces the old find associated exe
  1958. if (!_fNoUI)
  1959. {
  1960. // have user choose a new association for this type
  1961. OPENASINFO oai = {0};
  1962. oai.pcszFile = _szFile;
  1963. oai.dwInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
  1964. _ReportWin32(SUCCEEDED(OpenAsDialog(_hwndParent, &oai)) ? ERROR_SUCCESS : ERROR_CANCELLED);
  1965. hr = S_FALSE;
  1966. }
  1967. }
  1968. }
  1969. else
  1970. {
  1971. _ReportWin32(ERROR_NO_ASSOCIATION);
  1972. hr = S_FALSE;
  1973. }
  1974. if (S_OK == hr)
  1975. {
  1976. // parse arguments into command line
  1977. DWORD se_err = ReplaceParameters(_szCommand, ARRAYSIZE(_szCommand),
  1978. _szFile, _szCmdTemplate, _lpParameters,
  1979. _nShow, NULL, FALSE, _lpID, &_pidlGlobal);
  1980. if (0 == se_err)
  1981. fRet = TRUE;
  1982. else
  1983. _ReportHinst(IntToHinst(se_err));
  1984. if (!_fInheritHandles && SHRestricted(REST_INHERITCONSOLEHANDLES))
  1985. {
  1986. _fInheritHandles = IsCurrentProcessConsole() && IsConsoleApp(_szApplication);
  1987. }
  1988. }
  1989. else if (FAILED(hr))
  1990. {
  1991. _ReportWin32(hr);
  1992. }
  1993. return fRet;
  1994. }
  1995. BOOL CShellExecute::_ExecMayCreateProcess(LPCTSTR *ppszNewEnvString)
  1996. {
  1997. DWORD err = ERROR_SUCCESS;
  1998. // Check exec restrictions.
  1999. if (SHRestricted(REST_RESTRICTRUN) && RestrictedApp(_szPolicyApp))
  2000. {
  2001. err = ERROR_RESTRICTED_APP;
  2002. }
  2003. else if (SHRestricted(REST_DISALLOWRUN) && DisallowedApp(_szPolicyApp))
  2004. {
  2005. err = ERROR_RESTRICTED_APP;
  2006. }
  2007. // try to validate the image if it is on a UNC share
  2008. // we dont need to check for Print shares, so we
  2009. // will fail if it is on one.
  2010. else if (STOPTRYING(_TryValidateUNC(_szPolicyApp, NULL, NULL)))
  2011. {
  2012. // returns TRUE if it failed or handled the operation
  2013. // Note that SHValidateUNC calls SetLastError
  2014. // this continue will test based on GetLastError()
  2015. err = GetLastError();
  2016. }
  2017. //
  2018. // WOWShellExecute sets a global variable
  2019. // The cb is only valid when we are being called from wow
  2020. // If valid use it
  2021. //
  2022. else if (STOPTRYING(_TryWowShellExec()))
  2023. return FALSE;
  2024. return !_ReportWin32(err);
  2025. }
  2026. //
  2027. // TryExecCommand() is the most common and default way to get an app started.
  2028. // mostly it uses CreateProcess() with a command line composed from
  2029. // the pei and the registry. it can also do a ddeexec afterwards.
  2030. //
  2031. void CShellExecute::_DoExecCommand(void)
  2032. {
  2033. TraceMsg(TF_SHELLEXEC, "SHEX::DoExecCommand() entered szCommand = %s", _szCommand);
  2034. HWND hwndOld = GetForegroundWindow();
  2035. LPCTSTR pszNewEnvString = NULL;
  2036. if (_SetCommand() && _ExecMayCreateProcess(&pszNewEnvString))
  2037. {
  2038. // See if we need to pass a new environment to the new process
  2039. _BuildEnvironmentForNewProcess(pszNewEnvString);
  2040. TraceMsg(TF_SHELLEXEC, "SHEX::DoExecCommand() CreateProcess(NULL,%s,...)", _szCommand);
  2041. // CreateProcess will SetLastError() if it fails
  2042. if (_SHCreateProcess(_hwndParent,
  2043. _hUserToken,
  2044. _szAppFriendly,
  2045. _szApplication,
  2046. _szCommand,
  2047. _dwCreateFlags,
  2048. _pProcAttrs,
  2049. _pThreadAttrs,
  2050. _fInheritHandles,
  2051. _envblock.GetCustomBlock(),
  2052. _fUseNullCWD ? NULL : _szWorkingDir,
  2053. &_startup,
  2054. &_pi,
  2055. _cpt,
  2056. _fUEM))
  2057. {
  2058. // If we're doing DDE we'd better wait for the app to be up and running
  2059. // before we try to talk to them.
  2060. if (_fDDEInfoSet || _fWaitForInputIdle)
  2061. {
  2062. // Yep, How long to wait? For now, try 60 seconds to handle
  2063. // pig-slow OLE apps.
  2064. WaitForInputIdle(_pi.hProcess, 60*1000);
  2065. }
  2066. // Find the "hinstance" of whatever we just created.
  2067. // PEIOUT - hinst reported for pei->hInstApp
  2068. HINSTANCE hinst = 0;
  2069. // Now fix the focus and do any dde stuff that we need to do
  2070. _FixActivationStealingApps(hwndOld, _nShow);
  2071. if (_fDDEInfoSet)
  2072. {
  2073. // this will _Report() any errors for us if necessary
  2074. _DDEExecute(NULL, _hwndParent, _nShow, _fDDEWait);
  2075. }
  2076. else
  2077. _ReportHinst(hinst);
  2078. //
  2079. // Tell the taskbar about this application so it can re-tickle
  2080. // the associated shortcut if the app runs for a long time.
  2081. // This keeps long-running apps from aging off your Start Menu.
  2082. //
  2083. if (_fUEM && (_startup.dwFlags & STARTF_TITLEISLINKNAME))
  2084. {
  2085. _NotifyShortcutInvoke();
  2086. }
  2087. }
  2088. else
  2089. {
  2090. _ReportWin32(GetLastError());
  2091. }
  2092. }
  2093. // (we used to do a UIBW_RUNASSOC here, but moved it higher up)
  2094. }
  2095. void CShellExecute::_NotifyShortcutInvoke()
  2096. {
  2097. SHShortcutInvokeAsIDList sidl;
  2098. sidl.cb = FIELD_OFFSET(SHShortcutInvokeAsIDList, cbZero);
  2099. sidl.dwItem1 = SHCNEE_SHORTCUTINVOKE;
  2100. sidl.dwPid = _pi.dwProcessId;
  2101. if (_startup.lpTitle)
  2102. {
  2103. lstrcpynW(sidl.szShortcutName, _startup.lpTitle, ARRAYSIZE(sidl.szShortcutName));
  2104. }
  2105. else
  2106. {
  2107. sidl.szShortcutName[0] = TEXT('\0');
  2108. }
  2109. lstrcpynW(sidl.szTargetName, _szApplication, ARRAYSIZE(sidl.szTargetName));
  2110. sidl.cbZero = 0;
  2111. SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_IDLIST, (LPCITEMIDLIST)&sidl, NULL);
  2112. }
  2113. HGLOBAL CShellExecute::_CreateDDECommand(int nShow, BOOL fLFNAware, BOOL fNative)
  2114. {
  2115. // Now that we can handle ShellExec for URLs, we need to have a much bigger
  2116. // command buffer. Explorer's DDE exec command even has two file names in
  2117. // it. (WHY?) So the command buffer have to be a least twice the size of
  2118. // INTERNET_MAX_URL_LENGTH plus room for the command format.
  2119. SHSTR strTemp;
  2120. HGLOBAL hRet = NULL;
  2121. if (SUCCEEDED(strTemp.SetSize((2 * INTERNET_MAX_URL_LENGTH) + 64)))
  2122. {
  2123. if (0 == ReplaceParameters(strTemp.GetInplaceStr(), strTemp.GetSize(), _szFile,
  2124. _szDDECmd, _lpParameters, nShow, ((DWORD*) &_startup.hStdInput), fLFNAware, _lpID, &_pidlGlobal))
  2125. {
  2126. TraceMsg(TF_SHELLEXEC, "SHEX::_CreateDDECommand(%d, %d) : %s", fLFNAware, fNative, strTemp.GetStr());
  2127. // we only have to thunk on NT
  2128. if (!fNative)
  2129. {
  2130. SHSTRA stra;
  2131. if (SUCCEEDED(stra.SetStr(strTemp)))
  2132. {
  2133. // Get dde memory for the command and copy the command line.
  2134. hRet = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, CbFromCch(lstrlenA(stra.GetStr()) + 1));
  2135. if (hRet)
  2136. {
  2137. LPSTR psz = (LPSTR) GlobalLock(hRet);
  2138. StrCpyNA(psz, stra.GetStr(), GlobalSize(hRet) / sizeof(CHAR));
  2139. GlobalUnlock(hRet);
  2140. }
  2141. }
  2142. }
  2143. else
  2144. {
  2145. // Get dde memory for the command and copy the command line.
  2146. hRet = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, CbFromCch(lstrlen(strTemp.GetStr()) + 1));
  2147. if (hRet)
  2148. {
  2149. LPTSTR psz = (LPTSTR) GlobalLock(hRet);
  2150. StrCpyN(psz, strTemp.GetStr(), GlobalSize(hRet) / sizeof(TCHAR));
  2151. GlobalUnlock(hRet);
  2152. }
  2153. }
  2154. }
  2155. }
  2156. return hRet;
  2157. }
  2158. // Short cut all DDE commands with a WM_NOTIFY
  2159. // returns true if this was handled...or unrecoverable error.
  2160. BOOL CShellExecute::_TryDDEShortCircuit(HWND hwnd, HGLOBAL hMem, int nShow)
  2161. {
  2162. if (hwnd && IsWindowInProcess(hwnd))
  2163. {
  2164. HINSTANCE hret = (HINSTANCE)SE_ERR_FNF;
  2165. // get the top most owner.
  2166. hwnd = GetTopParentWindow(hwnd);
  2167. if (IsWindowInProcess(hwnd))
  2168. {
  2169. LPNMVIEWFOLDER lpnm = (LPNMVIEWFOLDER)LocalAlloc(LPTR, sizeof(NMVIEWFOLDER));
  2170. if (lpnm)
  2171. {
  2172. lpnm->hdr.hwndFrom = NULL;
  2173. lpnm->hdr.idFrom = 0;
  2174. lpnm->hdr.code = SEN_DDEEXECUTE;
  2175. lpnm->dwHotKey = HandleToUlong(_startup.hStdInput);
  2176. if ((_startup.dwFlags & STARTF_HASHMONITOR) != 0)
  2177. lpnm->hMonitor = reinterpret_cast<HMONITOR>(_startup.hStdOutput);
  2178. else
  2179. lpnm->hMonitor = NULL;
  2180. StrCpyN(lpnm->szCmd, (LPTSTR) GlobalLock(hMem), ARRAYSIZE(lpnm->szCmd));
  2181. GlobalUnlock(hMem);
  2182. if (SendMessage(hwnd, WM_NOTIFY, 0, (LPARAM)lpnm))
  2183. hret = Window_GetInstance(hwnd);
  2184. LocalFree(lpnm);
  2185. }
  2186. else
  2187. hret = (HINSTANCE)SE_ERR_OOM;
  2188. }
  2189. TraceMsg(TF_SHELLEXEC, "SHEX::_TryDDEShortcut hinst = %d", hret);
  2190. if ((UINT_PTR)hret != SE_ERR_FNF)
  2191. {
  2192. _ReportHinst(hret);
  2193. return TRUE;
  2194. }
  2195. }
  2196. return FALSE;
  2197. }
  2198. // _WaiteForDDEMsg()
  2199. // this does a message loop until DDE msg or a timeout occurs
  2200. //
  2201. STDAPI_(void) _WaitForDDEMsg(HWND hwnd, DWORD dwTimeout, UINT wMsg)
  2202. {
  2203. // termination event
  2204. HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  2205. SetProp(hwnd, SZTERMEVENT, hEvent);
  2206. for (;;)
  2207. {
  2208. MSG msg;
  2209. DWORD dwEndTime = GetTickCount() + dwTimeout;
  2210. LONG lWait = (LONG)dwTimeout;
  2211. DWORD dwReturn = MsgWaitForMultipleObjects(1, &hEvent,
  2212. FALSE, lWait, QS_POSTMESSAGE);
  2213. // if we time out or get an error or get our EVENT!!!
  2214. // we just bag out
  2215. if (dwReturn != (WAIT_OBJECT_0 + 1))
  2216. {
  2217. break;
  2218. }
  2219. // we woke up because of messages.
  2220. while (PeekMessage(&msg, NULL, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE))
  2221. {
  2222. ASSERT(msg.message != WM_QUIT);
  2223. DispatchMessage(&msg);
  2224. if (msg.hwnd == hwnd && msg.message == wMsg)
  2225. goto Quit;
  2226. }
  2227. // calculate new timeout value
  2228. if (dwTimeout != INFINITE)
  2229. {
  2230. lWait = (LONG)dwEndTime - GetTickCount();
  2231. }
  2232. }
  2233. Quit:
  2234. if (hEvent)
  2235. CloseHandle(hEvent);
  2236. RemoveProp(hwnd, SZTERMEVENT);
  2237. return;
  2238. }
  2239. LRESULT CALLBACK DDESubClassWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
  2240. {
  2241. HWND hwndConv = (HWND) GetProp(hWnd, SZCONV);
  2242. WPARAM nLow;
  2243. WPARAM nHigh;
  2244. HANDLE hEvent;
  2245. switch (wMsg)
  2246. {
  2247. case WM_DDE_ACK:
  2248. if (!hwndConv)
  2249. {
  2250. // this is the first ACK for our INITIATE message
  2251. TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd get ACK on INITIATE");
  2252. return SetProp(hWnd, SZCONV, (HANDLE)wParam);
  2253. }
  2254. else if (((UINT_PTR)hwndConv == 1) || ((HWND)wParam == hwndConv))
  2255. {
  2256. // this is the ACK for our EXECUTE message
  2257. TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd got ACK on EXECUTE");
  2258. if (UnpackDDElParam(wMsg, lParam, &nLow, &nHigh))
  2259. {
  2260. GlobalFree((HGLOBAL)nHigh);
  2261. FreeDDElParam(wMsg, lParam);
  2262. }
  2263. // prevent us from destroying again....
  2264. if ((UINT_PTR) hwndConv != 1)
  2265. DestroyWindow(hWnd);
  2266. }
  2267. // This is the ACK for our INITIATE message for all servers
  2268. // besides the first. We return FALSE, so the conversation
  2269. // should terminate.
  2270. break;
  2271. case WM_DDE_TERMINATE:
  2272. if (hwndConv == (HANDLE)wParam)
  2273. {
  2274. // this TERMINATE was originated by another application
  2275. // (otherwise, hwndConv would be 1)
  2276. // they should have freed the memory for the exec message
  2277. TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd got TERMINATE from hwndConv");
  2278. PostMessage((HWND)wParam, WM_DDE_TERMINATE, (WPARAM)hWnd, 0L);
  2279. RemoveProp(hWnd, SZCONV);
  2280. DestroyWindow(hWnd);
  2281. }
  2282. // Signal the termination event to ensure nested dde calls will terminate the
  2283. // appropriate _WaitForDDEMsg loop properly...
  2284. if (hEvent = GetProp(hWnd, SZTERMEVENT))
  2285. SetEvent(hEvent);
  2286. // This is the TERMINATE response for our TERMINATE message
  2287. // or a random terminate (which we don't really care about)
  2288. break;
  2289. case WM_TIMER:
  2290. if (wParam == DDE_DEATH_TIMER_ID)
  2291. {
  2292. // The conversation will be terminated in the destroy code
  2293. DestroyWindow(hWnd);
  2294. TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd TIMER closing DDE Window due to lack of ACK");
  2295. break;
  2296. }
  2297. else
  2298. return DefWindowProc(hWnd, wMsg, wParam, lParam);
  2299. case WM_DESTROY:
  2300. TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd WM_DESTROY'd");
  2301. // kill the timer just incase.... (this may fail if we never set the timer)
  2302. KillTimer(hWnd, DDE_DEATH_TIMER_ID);
  2303. if (hwndConv)
  2304. {
  2305. // Make sure the window is not destroyed twice
  2306. SetProp(hWnd, SZCONV, (HANDLE)1);
  2307. /* Post the TERMINATE message and then
  2308. * Wait for the acknowledging TERMINATE message or a timeout
  2309. */
  2310. PostMessage(hwndConv, WM_DDE_TERMINATE, (WPARAM)hWnd, 0L);
  2311. _WaitForDDEMsg(hWnd, DDE_TERMINATETIMEOUT, WM_DDE_TERMINATE);
  2312. RemoveProp(hWnd, SZCONV);
  2313. }
  2314. // the DDE conversation is officially over, let ShellExec know if it was waiting
  2315. hEvent = RemoveProp(hWnd, SZDDEEVENT);
  2316. if (hEvent)
  2317. {
  2318. SetEvent(hEvent);
  2319. }
  2320. /* Fall through */
  2321. default:
  2322. return DefWindowProc(hWnd, wMsg, wParam, lParam);
  2323. }
  2324. return 0L;
  2325. }
  2326. HWND CShellExecute::_CreateHiddenDDEWindow(HWND hwndParent)
  2327. {
  2328. // lets be lazy and not create a class for it
  2329. HWND hwnd = SHCreateWorkerWindow(DDESubClassWndProc, GetTopParentWindow(hwndParent),
  2330. 0, 0, NULL, NULL);
  2331. TraceMsg(TF_SHELLEXEC, "SHEX::_CreateHiddenDDEWindow returning hwnd = 0x%X", hwnd);
  2332. return hwnd;
  2333. }
  2334. void CShellExecute::_DestroyHiddenDDEWindow(HWND hwnd)
  2335. {
  2336. if (IsWindow(hwnd))
  2337. {
  2338. TraceMsg(TF_SHELLEXEC, "SHEX::_DestroyHiddenDDEWindow on hwnd = 0x%X", hwnd);
  2339. DestroyWindow(hwnd);
  2340. }
  2341. }
  2342. BOOL CShellExecute::_PostDDEExecute(HWND hwndOurs, HWND hwndTheirs, HGLOBAL hDDECommand, HANDLE hWait)
  2343. {
  2344. TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute(0x%X, 0x%X) entered", hwndTheirs, hwndOurs);
  2345. DWORD dwProcessID = 0;
  2346. GetWindowThreadProcessId(hwndTheirs, &dwProcessID);
  2347. if (dwProcessID)
  2348. {
  2349. AllowSetForegroundWindow(dwProcessID);
  2350. }
  2351. if (PostMessage(hwndTheirs, WM_DDE_EXECUTE, (WPARAM)hwndOurs, (LPARAM)PackDDElParam(WM_DDE_EXECUTE, 0,(UINT_PTR)hDDECommand)))
  2352. {
  2353. _ReportHinst(Window_GetInstance(hwndTheirs));
  2354. TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() connected");
  2355. // everything's going fine so far, so return to the application
  2356. // with the instance handle of the guy, and hope he can execute our string
  2357. if (hWait)
  2358. {
  2359. // We can't return from this call until the DDE conversation terminates.
  2360. // Otherwise the thread may go away, nuking our hwndConv window,
  2361. // messing up the DDE conversation, and Word drops funky error messages on us.
  2362. TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() waiting for termination");
  2363. SetProp(hwndOurs, SZDDEEVENT, hWait);
  2364. SHProcessMessagesUntilEvent(NULL, hWait, INFINITE);
  2365. // it is removed during WM_DESTROY (before signaling)
  2366. }
  2367. else if (IsWindow(hwndOurs))
  2368. {
  2369. // set a timer to tidy up the window incase we never get a ACK....
  2370. TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() setting DEATH timer");
  2371. SetTimer(hwndOurs, DDE_DEATH_TIMER_ID, DDE_DEATH_TIMEOUT, NULL);
  2372. }
  2373. return TRUE;
  2374. }
  2375. return FALSE;
  2376. }
  2377. #define DDE_TIMEOUT 30000 // 30 seconds.
  2378. #define DDE_TIMEOUT_LOW_MEM 80000 // 80 seconds - Excel takes 77.87 on 486.33 with 8mb
  2379. typedef struct {
  2380. WORD aName;
  2381. HWND hwndDDE;
  2382. LONG lAppTopic;
  2383. UINT timeout;
  2384. } INITDDECONV;
  2385. HWND CShellExecute::_GetConversationWindow(HWND hwndDDE)
  2386. {
  2387. ULONG_PTR dwResult; //unused
  2388. HWND hwnd = NULL;
  2389. INITDDECONV idc = { NULL,
  2390. hwndDDE,
  2391. MAKELONG(_aApplication, _aTopic),
  2392. SHIsLowMemoryMachine(ILMM_IE4) ? DDE_TIMEOUT_LOW_MEM : DDE_TIMEOUT
  2393. };
  2394. // if we didnt find him, then we better default to the old way...
  2395. if (!hwnd)
  2396. {
  2397. // we found somebody who used to like us...
  2398. // Send the initiate message.
  2399. // NB This doesn't need packing.
  2400. SendMessageTimeout((HWND) -1, WM_DDE_INITIATE, (WPARAM)hwndDDE,
  2401. idc.lAppTopic, SMTO_ABORTIFHUNG,
  2402. idc.timeout,
  2403. &dwResult);
  2404. hwnd = (HWND) GetProp(hwndDDE, SZCONV);
  2405. }
  2406. TraceMsg(TF_SHELLEXEC, "SHEX::GetConvWnd returns [%X]", hwnd);
  2407. return hwnd;
  2408. }
  2409. BOOL CShellExecute::_DDEExecute(
  2410. BOOL fWillRetry,
  2411. HWND hwndParent,
  2412. int nShowCmd,
  2413. BOOL fWaitForDDE
  2414. )
  2415. {
  2416. LONG err = ERROR_OUTOFMEMORY;
  2417. BOOL fReportErr = TRUE;
  2418. // Get the actual command string.
  2419. // NB We'll assume the guy we're going to talk to is LFN aware. If we're wrong
  2420. // we'll rebuild the command string a bit later on.
  2421. HGLOBAL hDDECommand = _CreateDDECommand(nShowCmd, TRUE, TRUE);
  2422. if (hDDECommand)
  2423. {
  2424. // we have a DDE command to try
  2425. if (_TryDDEShortCircuit(hwndParent, hDDECommand, nShowCmd))
  2426. {
  2427. // the shortcut tried and now we have an error reported
  2428. fReportErr = FALSE;
  2429. }
  2430. else
  2431. {
  2432. HANDLE hWait = fWaitForDDE ? CreateEvent(NULL, FALSE, FALSE, NULL) : NULL;
  2433. if (hWait || !fWaitForDDE)
  2434. {
  2435. // Create a hidden window for the conversation
  2436. HWND hwndDDE = _CreateHiddenDDEWindow(hwndParent);
  2437. if (hwndDDE)
  2438. {
  2439. HWND hwndConv = _GetConversationWindow(hwndDDE);
  2440. if (hwndConv)
  2441. {
  2442. // somebody answered us.
  2443. // This doesn't work if the other guy is using ddeml.
  2444. if (_fActivateHandler)
  2445. ActivateHandler(hwndConv, (DWORD_PTR) _startup.hStdInput);
  2446. // Can the guy we're talking to handle LFNs?
  2447. BOOL fLFNAware = Window_IsLFNAware(hwndConv);
  2448. BOOL fNative = IsWindowUnicode(hwndConv);
  2449. if (!fLFNAware || !fNative)
  2450. {
  2451. // we need to redo the command string.
  2452. // Nope - App isn't LFN aware - redo the command string.
  2453. GlobalFree(hDDECommand);
  2454. // we may need a new _pidlGlobal too.
  2455. if (_pidlGlobal)
  2456. {
  2457. SHFreeShared((HANDLE)_pidlGlobal,GetCurrentProcessId());
  2458. _pidlGlobal = NULL;
  2459. }
  2460. hDDECommand = _CreateDDECommand(nShowCmd, fLFNAware, fNative);
  2461. }
  2462. // Send the execute message to the application.
  2463. err = ERROR_DDE_FAIL;
  2464. if (_PostDDEExecute(hwndDDE, hwndConv, hDDECommand, hWait))
  2465. {
  2466. fReportErr = FALSE;
  2467. hDDECommand = NULL;
  2468. // hwnd owns itself now
  2469. if (!hWait)
  2470. hwndDDE = NULL;
  2471. }
  2472. }
  2473. else
  2474. {
  2475. err = (ERROR_FILE_NOT_FOUND);
  2476. }
  2477. // cleanup
  2478. _DestroyHiddenDDEWindow(hwndDDE);
  2479. }
  2480. if (hWait)
  2481. CloseHandle(hWait);
  2482. }
  2483. }
  2484. // cleanup
  2485. if (hDDECommand)
  2486. GlobalFree(hDDECommand);
  2487. }
  2488. if (fReportErr)
  2489. {
  2490. if (fWillRetry && ERROR_FILE_NOT_FOUND == err)
  2491. {
  2492. // this means that we need to update the
  2493. // command so that we can try DDE again after
  2494. // starting the app up...
  2495. // if it wasn't found, determine the correct command
  2496. _QueryString(0, ASSOCSTR_DDEIFEXEC, _szDDECmd, SIZECHARS(_szDDECmd));
  2497. return FALSE;
  2498. }
  2499. else
  2500. {
  2501. _ReportWin32(err);
  2502. }
  2503. }
  2504. return TRUE;
  2505. }
  2506. BOOL CShellExecute::_SetDDEInfo(void)
  2507. {
  2508. ASSERT(_pqa);
  2509. if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDECOMMAND, _szDDECmd, SIZECHARS(_szDDECmd))))
  2510. {
  2511. TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo command: %s", _szDDECmd);
  2512. // Any activation info?
  2513. _fActivateHandler = FAILED(_pqa->GetData(0, ASSOCDATA_NOACTIVATEHANDLER, _pszQueryVerb, NULL, NULL));
  2514. if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDEAPPLICATION, _szTemp, SIZECHARS(_szTemp))))
  2515. {
  2516. TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo application: %s", _szTemp);
  2517. if (_aApplication)
  2518. GlobalDeleteAtom(_aApplication);
  2519. _aApplication = GlobalAddAtom(_szTemp);
  2520. if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDETOPIC, _szTemp, SIZECHARS(_szTemp))))
  2521. {
  2522. TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo topic: %s", _szTemp);
  2523. if (_aTopic)
  2524. GlobalDeleteAtom(_aTopic);
  2525. _aTopic = GlobalAddAtom(_szTemp);
  2526. _fDDEInfoSet = TRUE;
  2527. }
  2528. }
  2529. }
  2530. TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo returns %d", _fDDEInfoSet);
  2531. return _fDDEInfoSet;
  2532. }
  2533. TRYRESULT CShellExecute::_TryExecDDE(void)
  2534. {
  2535. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2536. TraceMsg(TF_SHELLEXEC, "SHEX::TryExecDDE entered ");
  2537. if (_SetDDEInfo())
  2538. {
  2539. // try the real deal here. we pass TRUE for fWillRetry because
  2540. // if this fails to find the app, we will attempt to start
  2541. // the app and then use DDE again.
  2542. if (_DDEExecute(TRUE, _hwndParent, _nShow, _fDDEWait))
  2543. tr = TRY_STOP;
  2544. }
  2545. TraceMsg(TF_SHELLEXEC, "SHEX::TryDDEExec() returning %d", tr);
  2546. return tr;
  2547. }
  2548. TRYRESULT CShellExecute::_SetDarwinCmdTemplate(BOOL fSync)
  2549. {
  2550. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2551. if (SUCCEEDED(_pqa->GetData(0, ASSOCDATA_MSIDESCRIPTOR, _pszQueryVerb, (void *)_wszTemp, (LPDWORD)MAKEINTRESOURCE(sizeof(_wszTemp)))))
  2552. {
  2553. if (fSync)
  2554. {
  2555. // call darwin to give us the real location of the app.
  2556. //
  2557. // Note: this call could possibly fault the application in thus
  2558. // installing it on the users machine.
  2559. HRESULT hr = ParseDarwinID(_wszTemp, _szCmdTemplate, ARRAYSIZE(_szCmdTemplate));
  2560. if (SUCCEEDED(hr))
  2561. {
  2562. tr = TRY_CONTINUE;
  2563. }
  2564. else
  2565. {
  2566. _ReportWin32(hr);
  2567. tr = TRY_STOP;
  2568. }
  2569. }
  2570. else
  2571. tr = TRY_RETRYASYNC;
  2572. }
  2573. return tr;
  2574. }
  2575. HRESULT CShellExecute::_QueryString(ASSOCF flags, ASSOCSTR str, LPTSTR psz, DWORD cch)
  2576. {
  2577. if (_pqa)
  2578. {
  2579. HRESULT hr = _pqa->GetString(flags, str, _pszQueryVerb, _wszTemp, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(_wszTemp)));
  2580. if (SUCCEEDED(hr))
  2581. SHUnicodeToTChar(_wszTemp, psz, cch);
  2582. return hr;
  2583. }
  2584. return E_FAIL;
  2585. }
  2586. BOOL CShellExecute::_SetAppRunAsCmdTemplate(void)
  2587. {
  2588. DWORD cb = sizeof(_szCmdTemplate);
  2589. // we want to use a special command
  2590. HRESULT hr = PathToAppPathKey(_szFile, _szTemp, SIZECHARS(_szTemp));
  2591. if (SUCCEEDED(hr))
  2592. {
  2593. return (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, _szTemp, TEXT("RunAsCommand"), NULL, _szCmdTemplate, &cb) && *_szCmdTemplate);
  2594. }
  2595. else
  2596. {
  2597. return FALSE;
  2598. }
  2599. }
  2600. #if DBG && defined(_X86_)
  2601. #pragma optimize("", off) // work around compiler bug
  2602. #endif
  2603. TRYRESULT CShellExecute::_MaybeInstallApp(BOOL fSync)
  2604. {
  2605. // we check darwin first since it should override everything else
  2606. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2607. if (IsDarwinEnabled())
  2608. {
  2609. // if darwin is enabled, then check for the darwin ID in
  2610. // the registry and set the value based on that.
  2611. tr = _SetDarwinCmdTemplate(fSync);
  2612. }
  2613. if (TRY_CONTINUE_UNHANDLED == tr)
  2614. {
  2615. // no darwin information in the registry
  2616. // so now we have to check to see if the NT5 class store will populate our registry
  2617. // with some helpful information (darwin or otherwise)
  2618. tr = _ShouldRetryWithNewClassKey(fSync);
  2619. }
  2620. return tr;
  2621. }
  2622. TRYRESULT CShellExecute::_SetCmdTemplate(BOOL fSync)
  2623. {
  2624. TRYRESULT tr = _MaybeInstallApp(fSync);
  2625. if (tr == TRY_CONTINUE_UNHANDLED)
  2626. {
  2627. //
  2628. // both darwin and the class store were unsuccessful, so fall back to
  2629. // the good ole' default command value.
  2630. //
  2631. // but if we the caller requested NOUI and we
  2632. // decided to use Unknown as the class
  2633. // then we should fail here so that
  2634. // we dont popup the OpenWith dialog box.
  2635. //
  2636. HRESULT hr = E_FAIL;
  2637. if (!_fClassStoreOnly)
  2638. {
  2639. if ((_cpt != CPT_NORMAL)
  2640. || !PathIsExe(_szFile)
  2641. || !_SetAppRunAsCmdTemplate())
  2642. {
  2643. hr = _QueryString(0, ASSOCSTR_COMMAND, _szCmdTemplate, SIZECHARS(_szCmdTemplate));
  2644. }
  2645. }
  2646. if (SUCCEEDED(hr))
  2647. {
  2648. tr = TRY_CONTINUE;
  2649. }
  2650. else
  2651. {
  2652. _ReportWin32(ERROR_NO_ASSOCIATION);
  2653. tr = TRY_STOP;
  2654. }
  2655. }
  2656. TraceMsg(TF_SHELLEXEC, "SHEX::SetCmdTemplate() value = %s", _szCmdTemplate);
  2657. return tr;
  2658. }
  2659. #if DBG && defined(_X86_)
  2660. #pragma optimize("", on) // return to previous optimization level
  2661. #endif
  2662. TRYRESULT CShellExecute::_TryWowShellExec(void)
  2663. {
  2664. // WOWShellExecute sets this global variable
  2665. // The cb is only valid when we are being called from wow
  2666. // If valid use it
  2667. if (g_pfnWowShellExecCB)
  2668. {
  2669. SHSTRA strCmd;
  2670. SHSTRA strDir;
  2671. HINSTANCE hinst = (HINSTANCE)SE_ERR_OOM;
  2672. if (SUCCEEDED(strCmd.SetStr(_szCommand)) && SUCCEEDED(strDir.SetStr(_szWorkingDir)))
  2673. {
  2674. hinst = IntToHinst((*(LPFNWOWSHELLEXECCB)g_pfnWowShellExecCB)(strCmd.GetInplaceStr(), _startup.wShowWindow, strDir.GetInplaceStr()));
  2675. }
  2676. if (!_ReportHinst(hinst))
  2677. {
  2678. // SUCCESS!
  2679. //
  2680. // If we were doing DDE, then retry now that the app has been
  2681. // exec'd. Note we don't keep HINSTANCE returned from _DDEExecute
  2682. // because it will be constant 33 instead of the valid WOW HINSTANCE
  2683. // returned from *g_pfnWowShellExecCB above.
  2684. //
  2685. if (_fDDEInfoSet)
  2686. {
  2687. _DDEExecute(NULL, _hwndParent, _nShow, _fDDEWait);
  2688. }
  2689. }
  2690. TraceMsg(TF_SHELLEXEC, "SHEX::TryWowShellExec() used Wow");
  2691. return TRY_STOP;
  2692. }
  2693. return TRY_CONTINUE_UNHANDLED;
  2694. }
  2695. TRYRESULT CShellExecute::_ShouldRetryWithNewClassKey(BOOL fSync)
  2696. {
  2697. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2698. // If this is an app who's association is unknown, we might need to query the ClassStore if
  2699. // we have not already done so.
  2700. // The easiest way we can tell if the file we are going to execute is "Unknown" is by looking for
  2701. // the "QueryClassStore" string value under the hkey we have. DllInstall in shell32 writes this key
  2702. // so that we know when we are dealing with HKCR\Unknown (or any other progid that always wants to
  2703. // do a classtore lookup)
  2704. if (!_fAlreadyQueriedClassStore && !_fNoQueryClassStore &&
  2705. SUCCEEDED(_pqa->GetData(0, ASSOCDATA_QUERYCLASSSTORE, NULL, NULL, NULL)))
  2706. {
  2707. if (fSync)
  2708. {
  2709. // go hit the NT5 Directory Services class store
  2710. if (_szFile[0])
  2711. {
  2712. INSTALLDATA id;
  2713. LPTSTR pszExtPart;
  2714. WCHAR szFileExt[MAX_PATH];
  2715. // all we have is a filename so whatever PathFindExtension
  2716. // finds, we will use
  2717. pszExtPart = PathFindExtension(_szFile);
  2718. StrCpyN(szFileExt, pszExtPart, ARRAYSIZE(szFileExt));
  2719. // Need to zero init id (can't do a = {0} when we declated it, because it has a non-zero enum type)
  2720. ZeroMemory(&id, sizeof(INSTALLDATA));
  2721. id.Type = FILEEXT;
  2722. id.Spec.FileExt = szFileExt;
  2723. // call the DS to lookup the file type in the class store
  2724. if (ERROR_SUCCESS == InstallApplication(&id))
  2725. {
  2726. // Since InstallApplication succeeded, it could have possibly installed and app
  2727. // or munged the registry so that we now have the necesssary reg info to
  2728. // launch the app. So basically re-read the class association to see if there is any
  2729. // new darwin info or new normal info, and jump back up and retry to execute.
  2730. LPITEMIDLIST pidlUnkFile = ILCreateFromPath(_szFile);
  2731. if (pidlUnkFile)
  2732. {
  2733. IQueryAssociations *pqa;
  2734. if (SUCCEEDED(SHGetAssociations(pidlUnkFile, (void **)&pqa)))
  2735. {
  2736. _pqa->Release();
  2737. _pqa = pqa;
  2738. if (_pszQueryVerb && (lstrcmpi(_pszQueryVerb, TEXT("openas")) == 0))
  2739. {
  2740. // Since we just sucessfully queried the class store, if our verb was "openas" (meaning
  2741. // that we used the Unknown key to do the execute) we always reset the verb to the default.
  2742. // If we do not do this, then we could fail the execute since "openas" is most likely not a
  2743. // supported verb of the application
  2744. _pszQueryVerb = NULL;
  2745. }
  2746. }
  2747. ILFree(pidlUnkFile);
  2748. _fAlreadyQueriedClassStore = TRUE;
  2749. _fClassStoreOnly = FALSE;
  2750. }
  2751. } // CoGetClassInfo
  2752. } // _szFile[0]
  2753. }
  2754. else
  2755. tr = TRY_RETRYASYNC;
  2756. }
  2757. TraceMsg(TF_SHELLEXEC, "SHEX::ShouldRWNCK() returning %d", tr);
  2758. return tr;
  2759. }
  2760. TRYRESULT CShellExecute::_TryHooks(LPSHELLEXECUTEINFO pei)
  2761. {
  2762. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2763. if (_UseHooks(pei->fMask))
  2764. {
  2765. // REARCHITECT: the only client of this are URLs.
  2766. // if we change psfInternet to return IID_IQueryAssociations,
  2767. // then we can kill the urlexechook (our only client)
  2768. if (S_FALSE != TryShellExecuteHooks(pei))
  2769. {
  2770. // either way we always exit. should get TryShellhook to use SetLastError()
  2771. _ReportHinst(pei->hInstApp);
  2772. tr = TRY_STOP;;
  2773. }
  2774. }
  2775. return tr;
  2776. }
  2777. void _PathStripTrailingDots(LPTSTR psz)
  2778. {
  2779. // don't strip "." or ".."
  2780. if (!PathIsDotOrDotDot(psz))
  2781. {
  2782. // remove any trailing dots
  2783. TCHAR *pchLast = psz + lstrlen(psz) - 1;
  2784. while (*pchLast == TEXT('.'))
  2785. {
  2786. *pchLast = 0;
  2787. pchLast = CharPrev(psz, pchLast);
  2788. }
  2789. }
  2790. }
  2791. #define STR_PARSE_REQUIRE_REAL_NETWORK L"Parse Require Real Network"
  2792. #define STR_PARSE_INTERNET_DONT_ESCAPE_SPACES L"Parse Internet Dont Escape Spaces"
  2793. IBindCtx *CShellExecute::_PerfBindCtx()
  2794. {
  2795. //
  2796. // 180557 - make sure that we prefer the EXE to the folder - ZekeL - 9-SEP-2000
  2797. // this so that if both "D:\Setup" and "D:\Setup.exe" exist
  2798. // and the user types "D:\Setup" we will prefer to use "D:\Setup.exe"
  2799. // we also have to be careful not to send URLs down to SimpleIDList
  2800. // because of the weird results we get with the DavRedir
  2801. //
  2802. // 360353 - dont do resolve if we are passed the class key - ZekeL - 9-APR-2001
  2803. // if the caller passes us a key or class name then we must assume that
  2804. // the item is already fully qualified. specifically this can result in
  2805. // a double resolve when doing an Open With....
  2806. //
  2807. // 206795 - dont use simple if the path is a root - ZekeL - 12-APR-2001
  2808. // specifically \\server\share needs this for printer shares with '.' to work.
  2809. // (eg \\printsvr\printer.first) this fails since a simple share will
  2810. // be interpreted as SFGAO_FILESYSTEM always which will cause us to avoid
  2811. // the SHValidateUNC() which is what forces us to use the pidl for print shares.
  2812. // i think there are some similar issues with other shares that are not on the
  2813. // default provider for the server (ie DAV shares).
  2814. //
  2815. IBindCtx *pbc = NULL;
  2816. if (_fIsUrl)
  2817. {
  2818. // 403781 - avoid escaping spaces in URLs from ShellExec() - ZekeL - 25-May-2001
  2819. // this is because of removing the ShellExec hooks as the mechanism
  2820. // for invoking URLs and switching to just using Parse/Invoke().
  2821. // however, the old code evidently avoided doing the UrlEscapeSpaces()
  2822. // which the InternetNamespace typically does on parse.
  2823. // force xlate even though we are doing simple parse
  2824. static BINDCTX_PARAM rgUrlParams[] =
  2825. {
  2826. { STR_PARSE_TRANSLATE_ALIASES, NULL},
  2827. { STR_DONT_PARSE_RELATIVE, NULL},
  2828. { STR_PARSE_INTERNET_DONT_ESCAPE_SPACES, NULL},
  2829. };
  2830. BindCtx_RegisterObjectParams(NULL, rgUrlParams, ARRAYSIZE(rgUrlParams), &pbc);
  2831. }
  2832. else if (!_fUseClass && !PathIsRoot(_szFile))
  2833. {
  2834. DWORD dwAttribs;
  2835. if (PathFileExistsDefExtAndAttributes(_szFile, PFOPEX_DEFAULT | PFOPEX_OPTIONAL, &dwAttribs))
  2836. {
  2837. // we found this with the extension.
  2838. // avoid hitting the disk again to do the parse
  2839. WIN32_FIND_DATA wfd = {0};
  2840. wfd.dwFileAttributes = dwAttribs;
  2841. _PathStripTrailingDots(_szFile);
  2842. IBindCtx *pbcFile;
  2843. if (SUCCEEDED(SHCreateFileSysBindCtx(&wfd, &pbcFile)))
  2844. {
  2845. // force xlate even though we are doing simple parse
  2846. static BINDCTX_PARAM rgSimpleParams[] =
  2847. {
  2848. { STR_PARSE_TRANSLATE_ALIASES, NULL},
  2849. { STR_DONT_PARSE_RELATIVE, NULL},
  2850. //{ STR_PARSE_REQUIRE_REAL_NETWORK, NULL},
  2851. };
  2852. BindCtx_RegisterObjectParams(pbcFile, rgSimpleParams, ARRAYSIZE(rgSimpleParams), &pbc);
  2853. pbcFile->Release();
  2854. }
  2855. }
  2856. else
  2857. {
  2858. static BINDCTX_PARAM rgDefaultParams[] =
  2859. {
  2860. { STR_DONT_PARSE_RELATIVE, NULL},
  2861. };
  2862. BindCtx_RegisterObjectParams(NULL, rgDefaultParams, ARRAYSIZE(rgDefaultParams), &pbc);
  2863. }
  2864. }
  2865. return pbc;
  2866. }
  2867. TRYRESULT CShellExecute::_PerfPidl(LPCITEMIDLIST *ppidl)
  2868. {
  2869. *ppidl = _lpID;
  2870. if (!_lpID)
  2871. {
  2872. IBindCtx *pbc = _PerfBindCtx();
  2873. HRESULT hr = SHParseDisplayName(_szFile, pbc, &_pidlFree, SFGAO_STORAGECAPMASK, &_sfgaoID);
  2874. if (pbc)
  2875. pbc->Release();
  2876. if (FAILED(hr) && !pbc && UrlIs(_szFile, URLIS_FILEURL))
  2877. {
  2878. DWORD err = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : ERROR_FILE_NOT_FOUND;
  2879. _ReportWin32(err);
  2880. return TRY_STOP;
  2881. }
  2882. *ppidl = _lpID = _pidlFree;
  2883. }
  2884. else
  2885. {
  2886. _sfgaoID = SFGAO_STORAGECAPMASK;
  2887. HRESULT hrT;
  2888. if (*_szFile)
  2889. {
  2890. hrT = SHGetNameAndFlags(_lpID, 0, NULL, 0, &_sfgaoID);
  2891. }
  2892. else
  2893. {
  2894. hrT = SHGetNameAndFlags(_lpID, SHGDN_FORPARSING, _szFile, ARRAYSIZE(_szFile), &_sfgaoID);
  2895. // NTBUG#629947 - dont replace the path for the remote printers folder - ZekeL - 30-MAY-2002
  2896. // the remote printers pidl comes back with a path that looks like
  2897. // \\server\::{GUID Remote Printers}, which tanks in _TryValidateUNC().
  2898. // so, lets ditch the name if it is not a stream, since
  2899. // we may need the path for zone checking files
  2900. //
  2901. if (SUCCEEDED(hrT) && !(_sfgaoID & SFGAO_STREAM))
  2902. *_szFile = 0;
  2903. }
  2904. if (FAILED(hrT))
  2905. _sfgaoID = 0;
  2906. }
  2907. return TRY_CONTINUE;
  2908. }
  2909. DWORD CShellExecute::_InvokeAppThreadProc()
  2910. {
  2911. _fDDEWait = TRUE;
  2912. _TryInvokeApplication(TRUE);
  2913. Release();
  2914. return 0;
  2915. }
  2916. DWORD WINAPI CShellExecute::s_InvokeAppThreadProc(void *pv)
  2917. {
  2918. return ((CShellExecute *)pv)->_InvokeAppThreadProc();
  2919. }
  2920. TRYRESULT CShellExecute::_RetryAsync()
  2921. {
  2922. if (_lpID && !_pidlFree)
  2923. _lpID = _pidlFree = ILClone(_lpID);
  2924. if (_lpParameters)
  2925. _lpParameters = _pszAllocParams = StrDup(_lpParameters);
  2926. if (_lpTitle)
  2927. _lpTitle = _startup.lpTitle = _pszAllocTitle = StrDup(_lpTitle);
  2928. _fAsync = TRUE;
  2929. AddRef();
  2930. if (!SHCreateThread(s_InvokeAppThreadProc, this, CTF_FREELIBANDEXIT | CTF_COINIT, NULL))
  2931. {
  2932. _ReportWin32(GetLastError());
  2933. Release();
  2934. return TRY_STOP;
  2935. }
  2936. return TRY_RETRYASYNC;
  2937. }
  2938. TRYRESULT CShellExecute::_TryInvokeApplication(BOOL fSync)
  2939. {
  2940. TRYRESULT tr = TRY_CONTINUE_UNHANDLED;
  2941. if (fSync)
  2942. tr = _SetCmdTemplate(fSync);
  2943. if (KEEPTRYING(tr))
  2944. {
  2945. // check for both the CacheFilename and URL being passed to us,
  2946. // if this is the case, we need to check to see which one the App
  2947. // wants us to pass to it.
  2948. _SetFileAndUrl();
  2949. tr = _TryExecDDE();
  2950. // check to see if darwin is enabled on the machine
  2951. if (KEEPTRYING(tr))
  2952. {
  2953. if (!fSync)
  2954. tr = _SetCmdTemplate(fSync);
  2955. if (KEEPTRYING(tr))
  2956. {
  2957. // At this point, the _szFile should have been determined one way
  2958. // or another.
  2959. ASSERT(_szFile[0] || _szCmdTemplate[0]);
  2960. // do we have the necessary RegDB info to do an exec?
  2961. _DoExecCommand();
  2962. tr = TRY_STOP;
  2963. }
  2964. }
  2965. }
  2966. if (tr == TRY_RETRYASYNC)
  2967. {
  2968. // install this on another thread
  2969. tr = _RetryAsync();
  2970. }
  2971. return tr;
  2972. }
  2973. void CShellExecute::ExecuteNormal(LPSHELLEXECUTEINFO pei)
  2974. {
  2975. SetAppStartingCursor(pei->hwnd, TRUE);
  2976. _Init(pei);
  2977. //
  2978. // Copy the specified directory in _szWorkingDir if the working
  2979. // directory is specified; otherwise, get the current directory there.
  2980. //
  2981. _SetWorkingDir(pei->lpDirectory);
  2982. //
  2983. // Copy the file name to _szFile, if it is specified. Then,
  2984. // perform environment substitution.
  2985. //
  2986. _SetFile(pei->lpFile, pei->fMask & SEE_MASK_FILEANDURL);
  2987. LPCITEMIDLIST pidl;
  2988. if (STOPTRYING(_PerfPidl(&pidl)))
  2989. goto Quit;
  2990. // If the specified filename is a UNC path, validate it now.
  2991. if (STOPTRYING(_TryValidateUNC(_szFile, pei, pidl)))
  2992. goto Quit;
  2993. if (STOPTRYING(_TryHooks(pei)))
  2994. goto Quit;
  2995. if (STOPTRYING(_TryExecPidl(pei, pidl)))
  2996. goto Quit;
  2997. if (STOPTRYING(_VerifyExecTrust(pei)))
  2998. goto Quit;
  2999. // Is the class key provided?
  3000. if (STOPTRYING(_InitAssociations(pei, pidl)))
  3001. goto Quit;
  3002. _TryInvokeApplication(_fDDEWait || (pei->fMask & SEE_MASK_NOCLOSEPROCESS));
  3003. Quit:
  3004. //
  3005. // we should only see this if the registry is corrupted.
  3006. // but we still want to be able to open EXE's
  3007. if (_err == ERROR_SUCCESS && UEMIsLoaded())
  3008. {
  3009. // skip the call if stuff isn't there yet.
  3010. // the load is expensive (forces ole32.dll and browseui.dll in
  3011. // and then pins browseui).
  3012. // however we ran the app (exec, dde, etc.), we succeeded. do our
  3013. // best to guess the association etc. and log it.
  3014. int i = GetUEMAssoc(_szFile, _szApplication, _lpID);
  3015. TraceMsg(DM_MISC, "cse.e: GetUEMAssoc()=%d", i);
  3016. UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_RUNASSOC, (LPARAM)i);
  3017. }
  3018. SetAppStartingCursor(pei->hwnd, FALSE);
  3019. }
  3020. DWORD CShellExecute::_FinalMapError(HINSTANCE UNALIGNED64 *phinst)
  3021. {
  3022. if (_err != ERROR_SUCCESS)
  3023. {
  3024. // REVIEW: if errWin32 == ERROR_CANCELLED, we may want to
  3025. // set hInstApp to 42 so people who don't check the return
  3026. // code properly won't put up bogus messages. We should still
  3027. // return FALSE. But this won't help everything and we should
  3028. // really evangelize the proper use of ShellExecuteEx. In fact,
  3029. // if we do want to do this, we should do it in ShellExecute
  3030. // only. (This will force new people to do it right.)
  3031. // Map FNF for drives to something slightly more sensible.
  3032. if (_err == ERROR_FILE_NOT_FOUND && PathIsRoot(_szFile) &&
  3033. !PathIsUNC(_szFile))
  3034. {
  3035. // NB CD-Rom drives with disk missing will hit this.
  3036. if ((DriveType(DRIVEID(_szFile)) == DRIVE_CDROM) ||
  3037. (DriveType(DRIVEID(_szFile)) == DRIVE_REMOVABLE))
  3038. _err = ERROR_NOT_READY;
  3039. else
  3040. _err = ERROR_BAD_UNIT;
  3041. }
  3042. SetLastError(_err);
  3043. if (phinst)
  3044. *phinst = _MapWin32ErrToHINST(_err);
  3045. }
  3046. else if (phinst)
  3047. {
  3048. if (!_hInstance)
  3049. {
  3050. *phinst = (HINSTANCE) 42;
  3051. }
  3052. else
  3053. *phinst = _hInstance;
  3054. ASSERT(ISSHELLEXECSUCCEEDED(*phinst));
  3055. }
  3056. TraceMsg(TF_SHELLEXEC, "SHEX::FinalMapError() returning err = %d, hinst = %d", _err, _hInstance);
  3057. return _err;
  3058. }
  3059. DWORD CShellExecute::Finalize(LPSHELLEXECUTEINFO pei)
  3060. {
  3061. ASSERT(!_fAsync || !(pei->fMask & SEE_MASK_NOCLOSEPROCESS));
  3062. if (!_fAsync
  3063. && _pi.hProcess
  3064. && _err == ERROR_SUCCESS
  3065. && (pei->fMask & SEE_MASK_NOCLOSEPROCESS))
  3066. {
  3067. //
  3068. // change from win95 behavior - zekel 3-APR-98
  3069. // in win95 we would close the proces but return a handle.
  3070. // the handle was invalid of course, but some crazy app could be
  3071. // using this value to test for success. i am assuming that they
  3072. // are using one of the other three ways to determine success,
  3073. // and we can follow the spec and return NULL if we close it.
  3074. //
  3075. // PEIOUT - set the hProcess if they are going to use it.
  3076. pei->hProcess = _pi.hProcess;
  3077. _pi.hProcess = NULL;
  3078. }
  3079. //
  3080. // NOTE: _FinalMapError() actually calls SetLastError() with our best error
  3081. // if any win32 apis are called after this, they can reset LastError!!
  3082. //
  3083. return _FinalMapError(&(pei->hInstApp));
  3084. }
  3085. //
  3086. // Both the Reports return back TRUE if there was an error
  3087. // or FALSE if it was a Success.
  3088. //
  3089. BOOL CShellExecute::_ReportWin32(DWORD err)
  3090. {
  3091. ASSERT(!_err);
  3092. TraceMsg(TF_SHELLEXEC, "SHEX::ReportWin32 reporting err = %d", err);
  3093. _err = err;
  3094. return (err != ERROR_SUCCESS);
  3095. }
  3096. BOOL CShellExecute::_ReportHinst(HINSTANCE hinst)
  3097. {
  3098. ASSERT(!_hInstance);
  3099. TraceMsg(TF_SHELLEXEC, "SHEX::ReportHinst reporting hinst = %d", hinst);
  3100. if (ISSHELLEXECSUCCEEDED(hinst) || !hinst)
  3101. {
  3102. _hInstance = hinst;
  3103. return FALSE;
  3104. }
  3105. else
  3106. return _ReportWin32(_MapHINSTToWin32Err(hinst));
  3107. }
  3108. typedef struct {
  3109. DWORD errWin32;
  3110. UINT se_err;
  3111. } SHEXERR;
  3112. // one to one errs
  3113. // ERROR_FILE_NOT_FOUND SE_ERR_FNF 2 // file not found
  3114. // ERROR_PATH_NOT_FOUND SE_ERR_PNF 3 // path not found
  3115. // ERROR_ACCESS_DENIED SE_ERR_ACCESSDENIED 5 // access denied
  3116. // ERROR_NOT_ENOUGH_MEMORY SE_ERR_OOM 8 // out of memory
  3117. #define ISONE2ONE(e) (e == SE_ERR_FNF || e == SE_ERR_PNF || e == SE_ERR_ACCESSDENIED || e == SE_ERR_OOM)
  3118. // no win32 mapping SE_ERR_DDETIMEOUT 28
  3119. // no win32 mapping SE_ERR_DDEBUSY 30
  3120. // but i dont see any places where this is returned.
  3121. // before they became the win32 equivalent...ERROR_OUT_OF_PAPER or ERROR_READ_FAULT
  3122. // now they become ERROR_DDE_FAIL.
  3123. // but we wont preserve these errors in the pei->hInstApp
  3124. #define ISUNMAPPEDHINST(e) (e == 28 || e == 30)
  3125. // **WARNING** . ORDER is IMPORTANT.
  3126. // if there is more than one mapping for an error,
  3127. // (like SE_ERR_PNF) then the first
  3128. const SHEXERR c_rgShexErrs[] = {
  3129. {ERROR_SHARING_VIOLATION, SE_ERR_SHARE},
  3130. {ERROR_OUTOFMEMORY, SE_ERR_OOM},
  3131. {ERROR_BAD_PATHNAME,SE_ERR_PNF},
  3132. {ERROR_BAD_NETPATH,SE_ERR_PNF},
  3133. {ERROR_PATH_BUSY,SE_ERR_PNF},
  3134. {ERROR_NO_NET_OR_BAD_PATH,SE_ERR_PNF},
  3135. {ERROR_OLD_WIN_VERSION,10},
  3136. {ERROR_APP_WRONG_OS,12},
  3137. {ERROR_RMODE_APP,15},
  3138. {ERROR_SINGLE_INSTANCE_APP,16},
  3139. {ERROR_INVALID_DLL,20},
  3140. {ERROR_NO_ASSOCIATION,SE_ERR_NOASSOC},
  3141. {ERROR_DDE_FAIL,SE_ERR_DDEFAIL},
  3142. {ERROR_DDE_FAIL,SE_ERR_DDEBUSY},
  3143. {ERROR_DDE_FAIL,SE_ERR_DDETIMEOUT},
  3144. {ERROR_DLL_NOT_FOUND,SE_ERR_DLLNOTFOUND}
  3145. };
  3146. DWORD CShellExecute::_MapHINSTToWin32Err(HINSTANCE hinst)
  3147. {
  3148. DWORD errWin32 = 0;
  3149. UINT_PTR se_err = (UINT_PTR) hinst;
  3150. ASSERT(se_err);
  3151. ASSERT(!ISSHELLEXECSUCCEEDED(se_err));
  3152. // i actually handle these, but it used to be that these
  3153. // became mutant win32s. now they will be lost
  3154. // i dont think these occur anymore
  3155. AssertMsg(!ISUNMAPPEDHINST(se_err), TEXT("SHEX::COMPATIBILITY SE_ERR = %d, Get ZekeL!!!"), se_err);
  3156. if (ISONE2ONE(se_err))
  3157. {
  3158. errWin32 = (DWORD) se_err;
  3159. }
  3160. else for (int i = 0; i < ARRAYSIZE(c_rgShexErrs) ; i++)
  3161. {
  3162. if (se_err == c_rgShexErrs[i].se_err)
  3163. {
  3164. errWin32= c_rgShexErrs[i].errWin32;
  3165. break;
  3166. }
  3167. }
  3168. ASSERT(errWin32);
  3169. return errWin32;
  3170. }
  3171. HINSTANCE CShellExecute::_MapWin32ErrToHINST(UINT errWin32)
  3172. {
  3173. ASSERT(errWin32);
  3174. UINT se_err = 0;
  3175. if (ISONE2ONE(errWin32))
  3176. {
  3177. se_err = errWin32;
  3178. }
  3179. else for (int i = 0; i < ARRAYSIZE(c_rgShexErrs) ; i++)
  3180. {
  3181. if (errWin32 == c_rgShexErrs[i].errWin32)
  3182. {
  3183. se_err = c_rgShexErrs[i].se_err;
  3184. break;
  3185. }
  3186. }
  3187. if (!se_err)
  3188. {
  3189. // NOTE legacy error handling - zekel - 20-NOV-97
  3190. // for any unhandled win32 errors, we default to ACCESS_DENIED
  3191. se_err = SE_ERR_ACCESSDENIED;
  3192. }
  3193. return IntToHinst(se_err);
  3194. }
  3195. DWORD ShellExecuteNormal(LPSHELLEXECUTEINFO pei)
  3196. {
  3197. DWORD err;
  3198. TraceMsg(TF_SHELLEXEC, "ShellExecuteNormal Using CShellExecute");
  3199. // WARNING Dont use up Stack Space
  3200. // we allocate because of win16 stack issues
  3201. // and the shex is a big object
  3202. CShellExecute *shex = new CShellExecute();
  3203. if (shex)
  3204. {
  3205. shex->ExecuteNormal(pei);
  3206. err = shex->Finalize(pei);
  3207. shex->Release();
  3208. }
  3209. else
  3210. {
  3211. pei->hInstApp = (HINSTANCE)SE_ERR_OOM;
  3212. err = ERROR_OUTOFMEMORY;
  3213. }
  3214. TraceMsg(TF_SHELLEXEC, "ShellExecuteNormal returning win32 = %d, hinst = %d", err, pei->hInstApp);
  3215. return err;
  3216. }
  3217. BOOL CShellExecute::Init(PSHCREATEPROCESSINFO pscpi)
  3218. {
  3219. TraceMsg(TF_SHELLEXEC, "SHEX::Init(pscpi)");
  3220. _SetMask(pscpi->fMask);
  3221. _lpParameters= pscpi->pszParameters;
  3222. // we always do "runas"
  3223. _pszQueryVerb = _wszVerb;
  3224. _cpt = pscpi->hUserToken ? CPT_ASUSER : CPT_WITHLOGON;
  3225. if (pscpi->lpStartupInfo)
  3226. {
  3227. _nShow = pscpi->lpStartupInfo->wShowWindow;
  3228. _startup = *(pscpi->lpStartupInfo);
  3229. }
  3230. else // require startupinfo
  3231. return !(_ReportWin32(ERROR_INVALID_PARAMETER));
  3232. //
  3233. // Copy the specified directory in _szWorkingDir if the working
  3234. // directory is specified; otherwise, get the current directory there.
  3235. //
  3236. _SetWorkingDir(pscpi->pszCurrentDirectory);
  3237. //
  3238. // Copy the file name to _szFile, if it is specified. Then,
  3239. // perform environment substitution.
  3240. //
  3241. _SetFile(pscpi->pszFile, FALSE);
  3242. _pProcAttrs = pscpi->lpProcessAttributes;
  3243. _pThreadAttrs = pscpi->lpThreadAttributes;
  3244. _fInheritHandles = pscpi->bInheritHandles;
  3245. _hUserToken = pscpi->hUserToken;
  3246. // createflags already inited by _SetMask() just
  3247. // add the users in.
  3248. _dwCreateFlags |= pscpi->dwCreationFlags;
  3249. _hwndParent = pscpi->hwnd;
  3250. return TRUE;
  3251. }
  3252. void CShellExecute::ExecuteProcess(void)
  3253. {
  3254. SetAppStartingCursor(_hwndParent, TRUE);
  3255. //
  3256. // If the specified filename is a UNC path, validate it now.
  3257. //
  3258. if (STOPTRYING(_TryValidateUNC(_szFile, NULL, NULL)))
  3259. goto Quit;
  3260. LPCITEMIDLIST pidl;
  3261. if (STOPTRYING(_Resolve(&pidl)))
  3262. goto Quit;
  3263. if (STOPTRYING(_InitAssociations(NULL, NULL)))
  3264. goto Quit;
  3265. // check to see if darwin is enabled on the machine
  3266. if (STOPTRYING(_SetCmdTemplate(TRUE)))
  3267. goto Quit;
  3268. // At this point, the _szFile should have been determined one way
  3269. // or another.
  3270. ASSERT(_szFile[0] || _szCmdTemplate[0]);
  3271. // do we have the necessary RegDB info to do an exec?
  3272. _DoExecCommand();
  3273. Quit:
  3274. if (_err == ERROR_SUCCESS && UEMIsLoaded())
  3275. {
  3276. int i;
  3277. // skip the call if stuff isn't there yet.
  3278. // the load is expensive (forces ole32.dll and browseui.dll in
  3279. // and then pins browseui).
  3280. // however we ran the app (exec, dde, etc.), we succeeded. do our
  3281. // best to guess the association etc. and log it.
  3282. i = GetUEMAssoc(_szFile, _szApplication, NULL);
  3283. TraceMsg(DM_MISC, "cse.e: GetUEMAssoc()=%d", i);
  3284. UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_RUNASSOC, (LPARAM)i);
  3285. }
  3286. SetAppStartingCursor(_hwndParent, FALSE);
  3287. }
  3288. DWORD CShellExecute::Finalize(PSHCREATEPROCESSINFO pscpi)
  3289. {
  3290. if (!_fAsync && _pi.hProcess)
  3291. {
  3292. if (!(pscpi->fMask & SEE_MASK_NOCLOSEPROCESS))
  3293. {
  3294. CloseHandle(_pi.hProcess);
  3295. _pi.hProcess = NULL;
  3296. CloseHandle(_pi.hThread);
  3297. _pi.hThread = NULL;
  3298. }
  3299. if (_err == ERROR_SUCCESS
  3300. && pscpi->lpProcessInformation)
  3301. {
  3302. *(pscpi->lpProcessInformation) = _pi;
  3303. }
  3304. }
  3305. else if (pscpi->lpProcessInformation)
  3306. ZeroMemory(pscpi->lpProcessInformation, sizeof(_pi));
  3307. //
  3308. // NOTE: _FinalMapError() actually calls SetLastError() with our best error
  3309. // if any win32 apis are called after this, they can reset LastError!!
  3310. //
  3311. return _FinalMapError(NULL);
  3312. }
  3313. SHSTDAPI_(BOOL) SHCreateProcessAsUserW(PSHCREATEPROCESSINFOW pscpi)
  3314. {
  3315. DWORD err;
  3316. TraceMsg(TF_SHELLEXEC, "SHCreateProcess using CShellExecute");
  3317. // WARNING Don't use up Stack Space
  3318. // we allocate because of win16 stack issues
  3319. // and the shex is a big object
  3320. CShellExecute *pshex = new CShellExecute();
  3321. if (pshex)
  3322. {
  3323. if (pshex->Init(pscpi))
  3324. pshex->ExecuteProcess();
  3325. err = pshex->Finalize(pscpi);
  3326. pshex->Release();
  3327. }
  3328. else
  3329. err = ERROR_OUTOFMEMORY;
  3330. TraceMsg(TF_SHELLEXEC, "SHCreateProcess returning win32 = %d", err);
  3331. if (err != ERROR_SUCCESS)
  3332. {
  3333. _DisplayShellExecError(pscpi->fMask, pscpi->hwnd, pscpi->pszFile, NULL, err);
  3334. SetLastError(err);
  3335. }
  3336. return err == ERROR_SUCCESS;
  3337. }
  3338. HINSTANCE APIENTRY WOWShellExecute(
  3339. HWND hwnd,
  3340. LPCSTR lpOperation,
  3341. LPCSTR lpFile,
  3342. LPSTR lpParameters,
  3343. LPCSTR lpDirectory,
  3344. INT nShowCmd,
  3345. void *lpfnCBWinExec)
  3346. {
  3347. g_pfnWowShellExecCB = lpfnCBWinExec;
  3348. if (!lpParameters)
  3349. lpParameters = "";
  3350. HINSTANCE hinstRet = RealShellExecuteExA(hwnd, lpOperation, lpFile, lpParameters,
  3351. lpDirectory, NULL, "", NULL, (WORD)nShowCmd, NULL, 0);
  3352. g_pfnWowShellExecCB = NULL;
  3353. return hinstRet;
  3354. }
  3355. void _ShellExec_RunDLL(HWND hwnd, HINSTANCE hAppInstance, LPCTSTR pszCmdLine, int nCmdShow)
  3356. {
  3357. TCHAR szQuotedCmdLine[MAX_PATH * 2];
  3358. SHELLEXECUTEINFO ei = {0};
  3359. ULONG fMask = SEE_MASK_FLAG_DDEWAIT;
  3360. LPTSTR pszArgs;
  3361. // Don't let empty strings through, they will endup doing something dumb
  3362. // like opening a command prompt or the like
  3363. if (!pszCmdLine || !*pszCmdLine)
  3364. return;
  3365. //
  3366. // the flags are prepended to the command line like:
  3367. // "?0x00000001?" "cmd line"
  3368. //
  3369. if (pszCmdLine[0] == TEXT('?'))
  3370. {
  3371. // these are the fMask flags
  3372. int i;
  3373. if (StrToIntEx(++pszCmdLine, STIF_SUPPORT_HEX, &i))
  3374. {
  3375. fMask |= i;
  3376. }
  3377. pszCmdLine = StrChr(pszCmdLine, TEXT('?'));
  3378. if (!pszCmdLine)
  3379. return;
  3380. pszCmdLine++;
  3381. }
  3382. // Gross, but if the process command fails, copy the command line to let
  3383. // shell execute report the errors
  3384. if (PathProcessCommand(pszCmdLine, szQuotedCmdLine, ARRAYSIZE(szQuotedCmdLine),
  3385. PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1)
  3386. StrCpyN(szQuotedCmdLine, pszCmdLine, SIZECHARS(szQuotedCmdLine));
  3387. pszArgs = PathGetArgs(szQuotedCmdLine);
  3388. if (*pszArgs)
  3389. *(pszArgs - 1) = 0; // Strip args
  3390. PathUnquoteSpaces(szQuotedCmdLine);
  3391. ei.cbSize = sizeof(SHELLEXECUTEINFO);
  3392. ei.hwnd = hwnd;
  3393. ei.lpFile = szQuotedCmdLine;
  3394. ei.lpParameters = pszArgs;
  3395. ei.nShow = nCmdShow;
  3396. ei.fMask = fMask;
  3397. // if shellexec() fails we want to pass back the error.
  3398. if (!ShellExecuteEx(&ei))
  3399. {
  3400. DWORD err = GetLastError();
  3401. if (InRunDllProcess())
  3402. ExitProcess(err);
  3403. }
  3404. }
  3405. STDAPI_(void) ShellExec_RunDLLA(HWND hwnd, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow)
  3406. {
  3407. SHSTR str;
  3408. if (SUCCEEDED(str.SetStr(pszCmdLine)))
  3409. _ShellExec_RunDLL(hwnd, hAppInstance, str, nCmdShow);
  3410. }
  3411. STDAPI_(void) ShellExec_RunDLLW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR pszCmdLine, int nCmdShow)
  3412. {
  3413. SHSTR str;
  3414. if (SUCCEEDED(str.SetStr(pszCmdLine)))
  3415. _ShellExec_RunDLL(hwnd, hAppInstance, str, nCmdShow);
  3416. }