Source code of Windows XP (NT5)
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.

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