#include "shellprv.h" #include "shlexec.h" #include #include #include "ids.h" #include #include "pidl.h" #include "apithk.h" // for TermsrvAppInstallMode() #include "fstreex.h" #include "uemapp.h" #include "views.h" // for SHRunControlPanelEx #include "control.h" // for MakeCPLCommandLine, etc #include // for CredUIInitControls #include // for ComputeAccessTokenFromCodeAuthzLevel, etc #include // for Saferi APIs #include // for WinVerifyTrust constants #include // for UNLEN (max username length), GNLEN (max groupname length), PWLEN (max password length) #define DM_MISC 0 // miscellany #define SZWNDCLASS TEXT("WndClass") #define SZTERMEVENT TEXT("TermEvent") typedef PSHCREATEPROCESSINFOW PSHCREATEPROCESSINFO; // stolen from sdk\inc\winbase.h #define LOGON_WITH_PROFILE 0x00000001 #define IntToHinst(i) IntToPtr_(HINSTANCE, i) // the timer id for the kill this DDE window... #define DDE_DEATH_TIMER_ID 0x00005555 // the timeout value (180 seconds) for killing a dead dde window... #define DDE_DEATH_TIMEOUT (1000 * 180) // timeout for conversation window terminating with us... #define DDE_TERMINATETIMEOUT (10 * 1000) #define SZCONV TEXT("ddeconv") #define SZDDEEVENT TEXT("ddeevent") #define PEMAGIC ((WORD)'P'+((WORD)'E'<<8)) void *g_pfnWowShellExecCB = NULL; class CEnvironmentBlock { public: ~CEnvironmentBlock() { if (_pszBlock) LocalFree(_pszBlock); } void SetToken(HANDLE hToken) { _hToken = hToken; } void *GetCustomBlock() { return _pszBlock; } HRESULT SetVar(LPCWSTR pszVar, LPCWSTR pszValue); HRESULT AppendVar(LPCWSTR pszVar, WCHAR chDelimiter, LPCWSTR pszValue); private: // methods HRESULT _InitBlock(DWORD cchNeeded); DWORD _BlockLen(LPCWSTR pszEnv); DWORD _BlockLenCached(); BOOL _FindVar(LPCWSTR pszVar, DWORD cchVar, LPWSTR *ppszBlockVar); private: // members HANDLE _hToken; LPWSTR _pszBlock; DWORD _cchBlockSize; DWORD _cchBlockLen; }; typedef enum { CPT_FAILED = -1, CPT_NORMAL = 0, CPT_ASUSER, CPT_SANDBOX, CPT_INSTALLTS, CPT_WITHLOGON, CPT_WITHLOGONADMIN, CPT_WITHLOGONCANCELLED, } CPTYPE; typedef enum { TRY_RETRYASYNC = -1, // stop execution (complete on async thread) TRY_STOP = 0, // stop execution (completed or failed) TRY_CONTINUE, // continue exec (did something useful) TRY_CONTINUE_UNHANDLED, // continue exec (did nothing) } TRYRESULT; #define KEEPTRYING(tr) (tr >= TRY_CONTINUE ? TRUE : FALSE) #define STOPTRYING(tr) (tr <= TRY_STOP ? TRUE : FALSE) class CShellExecute { public: CShellExecute(); STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } void ExecuteNormal(LPSHELLEXECUTEINFO pei); DWORD Finalize(LPSHELLEXECUTEINFO pei); BOOL Init(PSHCREATEPROCESSINFO pscpi); void ExecuteProcess(void); DWORD Finalize(PSHCREATEPROCESSINFO pscpi); protected: ~CShellExecute(); // default inits HRESULT _Init(LPSHELLEXECUTEINFO pei); // member init methods TRYRESULT _InitAssociations(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl); HRESULT _InitClassAssociations(LPCTSTR pszClass, HKEY hkClass, DWORD mask); HRESULT _InitShellAssociations(LPCTSTR pszFile, LPCITEMIDLIST pidl); void _SetMask(ULONG fMask); void _SetWorkingDir(LPCTSTR pszIn); void _SetFile(LPCTSTR pszIn, BOOL fFileAndUrl); void _SetFileAndUrl(); BOOL _SetDDEInfo(void); TRYRESULT _MaybeInstallApp(BOOL fSync); TRYRESULT _ShouldRetryWithNewClassKey(BOOL fSync); TRYRESULT _SetDarwinCmdTemplate(BOOL fSync); BOOL _SetAppRunAsCmdTemplate(void); TRYRESULT _SetCmdTemplate(BOOL fSync); BOOL _FileIsApp(); BOOL _SetCommand(void); void _SetStartup(LPSHELLEXECUTEINFO pei); IBindCtx *_PerfBindCtx(); TRYRESULT _PerfPidl(LPCITEMIDLIST *ppidl); // utility methods HRESULT _QueryString(ASSOCF flags, ASSOCSTR str, LPTSTR psz, DWORD cch); BOOL _CheckForRegisteredProgram(); BOOL _ExecMayCreateProcess(LPCTSTR *ppszNewEnvString); HRESULT _BuildEnvironmentForNewProcess(LPCTSTR pszNewEnvString); void _FixActivationStealingApps(HWND hwndOldActive, int nShow); DWORD _GetCreateFlags(ULONG fMask); BOOL _Resolve(LPCITEMIDLIST *ppidl); // DDE stuff HWND _GetConversationWindow(HWND hwndDDE); HWND _CreateHiddenDDEWindow(HWND hwndParent); HGLOBAL _CreateDDECommand(int nShow, BOOL fLFNAware, BOOL fNative); void _DestroyHiddenDDEWindow(HWND hwnd); BOOL _TryDDEShortCircuit(HWND hwnd, HGLOBAL hMem, int nShow); BOOL _PostDDEExecute(HWND hwndOurs, HWND hwndTheirs, HGLOBAL hDDECommand, HANDLE hWait); BOOL _DDEExecute(BOOL fWillRetry, HWND hwndParent, int nShowCmd, BOOL fWaitForDDE); // exec methods TRYRESULT _TryHooks(LPSHELLEXECUTEINFO pei); TRYRESULT _TryValidateUNC(LPTSTR pszFile, LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl); void _DoExecCommand(void); void _NotifyShortcutInvoke(); TRYRESULT _TryExecDDE(void); TRYRESULT _ZoneCheckFile(PCWSTR pszFile); TRYRESULT _VerifyZoneTrust(PCWSTR pszFile); TRYRESULT _VerifySaferTrust(PCWSTR pszFile); TRYRESULT _VerifyExecTrust(LPSHELLEXECUTEINFO pei); TRYRESULT _TryExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl); TRYRESULT _DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl); BOOL _ShellExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidlExec); TRYRESULT _TryInvokeApplication(BOOL fSync); // uninit/error handling methods DWORD _FinalMapError(HINSTANCE UNALIGNED64 *phinst); BOOL _ReportWin32(DWORD err); BOOL _ReportHinst(HINSTANCE hinst); DWORD _MapHINSTToWin32Err(HINSTANCE se_err); HINSTANCE _MapWin32ErrToHINST(UINT errWin32); TRYRESULT _TryWowShellExec(void); TRYRESULT _RetryAsync(); DWORD _InvokeAppThreadProc(); static DWORD WINAPI s_InvokeAppThreadProc(void *pv); // // PRIVATE MEMBERS // LONG _cRef; TCHAR _szFile[INTERNET_MAX_URL_LENGTH]; TCHAR _szWorkingDir[MAX_PATH]; TCHAR _szCommand[INTERNET_MAX_URL_LENGTH]; TCHAR _szCmdTemplate[INTERNET_MAX_URL_LENGTH]; TCHAR _szDDECmd[MAX_PATH]; TCHAR _szApplication[MAX_PATH]; TCHAR _szPolicyApp[MAX_PATH]; TCHAR _szAppFriendly[MAX_PATH]; TCHAR _szUrl[INTERNET_MAX_URL_LENGTH]; DWORD _dwCreateFlags; STARTUPINFO _startup; int _nShow; UINT _uConnect; PROCESS_INFORMATION _pi; // used only within restricted scope // to avoid stack usage; WCHAR _wszTemp[INTERNET_MAX_URL_LENGTH]; TCHAR _szTemp[MAX_PATH]; // we always pass a UNICODE verb to the _pqa WCHAR _wszVerb[MAX_PATH]; LPCWSTR _pszQueryVerb; LPCTSTR _lpParameters; LPTSTR _pszAllocParams; LPCTSTR _lpClass; LPCTSTR _lpTitle; LPTSTR _pszAllocTitle; LPCITEMIDLIST _lpID; SFGAOF _sfgaoID; LPITEMIDLIST _pidlFree; ATOM _aApplication; ATOM _aTopic; LPITEMIDLIST _pidlGlobal; IQueryAssociations *_pqa; HWND _hwndParent; LPSECURITY_ATTRIBUTES _pProcAttrs; LPSECURITY_ATTRIBUTES _pThreadAttrs; HANDLE _hUserToken; HANDLE _hCloseToken; CEnvironmentBlock _envblock; CPTYPE _cpt; // error state HINSTANCE _hInstance; // hinstance value should only be set with ReportHinst DWORD _err; // win32 error value should only be set with ReportWin32 // FLAGS BOOL _fNoUI; // dont show any UI BOOL _fUEM; // fire UEM events BOOL _fDoEnvSubst; // do environment substitution on paths BOOL _fUseClass; BOOL _fNoQueryClassStore; // blocks calling darwins class store BOOL _fClassStoreOnly; BOOL _fIsUrl; //_szFile is actually an URL BOOL _fActivateHandler; BOOL _fDDEInfoSet; BOOL _fDDEWait; BOOL _fNoExecPidl; BOOL _fNoResolve; // unnecessary to resolve this path BOOL _fAlreadyQueriedClassStore; // have we already queried the NT5 class store? BOOL _fInheritHandles; BOOL _fIsNamespaceObject; // is namespace object like ::{GUID}, must pidlexec BOOL _fWaitForInputIdle; BOOL _fUseNullCWD; // should we pass NULL as the lpCurrentDirectory param to _SHCreateProcess? BOOL _fInvokeIdList; BOOL _fAsync; // shellexec() switched }; CShellExecute::CShellExecute() : _cRef(1) { TraceMsg(TF_SHELLEXEC, "SHEX::SHEX Created [%X]", this); } CShellExecute::~CShellExecute() { if (_hCloseToken) CloseHandle(_hCloseToken); // Clean this up if the exec failed if (_err != ERROR_SUCCESS && _pidlGlobal) SHFreeShared((HANDLE)_pidlGlobal,GetCurrentProcessId()); if (_aTopic) GlobalDeleteAtom(_aTopic); if (_aApplication) GlobalDeleteAtom(_aApplication); if (_pqa) _pqa->Release(); if (_pi.hProcess) CloseHandle(_pi.hProcess); if (_pi.hThread) CloseHandle(_pi.hThread); if (_pszAllocParams) LocalFree(_pszAllocParams); if (_pszAllocTitle) LocalFree(_pszAllocTitle); ILFree(_pidlFree); TraceMsg(TF_SHELLEXEC, "SHEX::SHEX deleted [%X]", this); } void CShellExecute::_SetMask(ULONG fMask) { _fDoEnvSubst = (fMask & SEE_MASK_DOENVSUBST); _fNoUI = (fMask & SEE_MASK_FLAG_NO_UI); _fUEM = (fMask & SEE_MASK_FLAG_LOG_USAGE); _fNoQueryClassStore = (fMask & SEE_MASK_NOQUERYCLASSSTORE) || !IsOS(OS_DOMAINMEMBER); _fDDEWait = fMask & SEE_MASK_FLAG_DDEWAIT; _fWaitForInputIdle = fMask & SEE_MASK_WAITFORINPUTIDLE; _fUseClass = _UseClassName(fMask) || _UseClassKey(fMask); _fInvokeIdList = _InvokeIDList(fMask); _dwCreateFlags = _GetCreateFlags(fMask); _uConnect = fMask & SEE_MASK_CONNECTNETDRV ? VALIDATEUNC_CONNECT : 0; if (_fNoUI) _uConnect |= VALIDATEUNC_NOUI; // must be off for this condition to pass. // PARTIAL ANSWER (reinerf): the SEE_MASK_FILEANDURL has to be off // so we can wait until we find out what the associated App is and query // to find out whether they want the the cache filename or the URL name passed // on the command line. #define NOEXECPIDLMASK (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FORCENOIDLIST | SEE_MASK_FILEANDURL) _fNoExecPidl = BOOLIFY(fMask & NOEXECPIDLMASK); } HRESULT CShellExecute::_Init(LPSHELLEXECUTEINFO pei) { TraceMsg(TF_SHELLEXEC, "SHEX::_Init()"); _SetMask(pei->fMask); _lpParameters = pei->lpParameters; _lpID = (LPITEMIDLIST)((pei->fMask) & SEE_MASK_PIDL ? pei->lpIDList : NULL); _lpTitle = _UseTitleName(pei->fMask) ? pei->lpClass : NULL; // default to TRUE; _fActivateHandler = TRUE; if (pei->lpVerb && *(pei->lpVerb)) { SHTCharToUnicode(pei->lpVerb, _wszVerb, SIZECHARS(_wszVerb)); _pszQueryVerb = _wszVerb; if (0 == lstrcmpi(pei->lpVerb, TEXT("runas"))) _cpt = CPT_WITHLOGON; } _hwndParent = pei->hwnd; pei->hProcess = 0; _nShow = pei->nShow; // initialize the startup struct _SetStartup(pei); return S_OK; } void CShellExecute::_SetWorkingDir(LPCTSTR pszIn) { // if we were given a directory, we attempt to use it if (pszIn && *pszIn) { StrCpyN(_szWorkingDir, pszIn, SIZECHARS(_szWorkingDir)); if (_fDoEnvSubst) DoEnvironmentSubst(_szWorkingDir, SIZECHARS(_szWorkingDir)); // // if the passed directory is not valid (after env subst) dont // fail, act just like Win31 and use whatever the current dir is. // // Win31 is stranger than I could imagine, if you pass ShellExecute // an invalid directory, it will change the current drive. // if (!PathIsDirectory(_szWorkingDir)) { if (PathGetDriveNumber(_szWorkingDir) >= 0) { TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() bad directory %s, using %c:", _szWorkingDir, _szWorkingDir[0]); PathStripToRoot(_szWorkingDir); } else { TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() bad directory %s, using current dir", _szWorkingDir); GetCurrentDirectory(SIZECHARS(_szWorkingDir), _szWorkingDir); } } else { goto Done; } } else { // if we are doing a SHCreateProcessAsUser or a normal shellexecute w/ the "runas" verb, and // the caller passed NULL for lpCurrentDirectory then we we do NOT want to fall back and use // the CWD because the newly logged on user might not have permissions in the current users CWD. // We will have better luck just passing NULL and letting the OS figure it out. if (_cpt != CPT_NORMAL) { _fUseNullCWD = TRUE; goto Done; } else { GetCurrentDirectory(SIZECHARS(_szWorkingDir), _szWorkingDir); } } // there are some cases where even CD is bad. // and CreateProcess() will then fail. if (!PathIsDirectory(_szWorkingDir)) { GetWindowsDirectory(_szWorkingDir, SIZECHARS(_szWorkingDir)); } Done: TraceMsg(TF_SHELLEXEC, "SHEX::_SetWorkingDir() pszIn = %s, NewDir = %s", pszIn, _szWorkingDir); } inline BOOL _IsNamespaceObject(LPCTSTR psz) { return (psz[0] == L':' && psz[1] == L':' && psz[2] == L'{'); } void CShellExecute::_SetFile(LPCTSTR pszIn, BOOL fFileAndUrl) { if (pszIn && pszIn[0]) { TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileName() Entered pszIn = %s", pszIn); _fIsUrl = UrlIs(pszIn, URLIS_URL); StrCpyN(_szFile, pszIn, SIZECHARS(_szFile)); _fIsNamespaceObject = (!_fInvokeIdList && !_fUseClass && _IsNamespaceObject(_szFile)); if (_fDoEnvSubst) DoEnvironmentSubst(_szFile, SIZECHARS(_szFile)); if (fFileAndUrl) { ASSERT(!_fIsUrl); // our lpFile points to a string that contains both an Internet Cache // File location and the URL name that is associated with that cache file // (they are seperated by a single NULL). The application that we are // about to execute wants the URL name instead of the cache file, so // use it instead. int iLength = lstrlen(pszIn); LPCTSTR pszUrlPart = &pszIn[iLength + 1]; if (IsBadStringPtr(pszUrlPart, INTERNET_MAX_URL_LENGTH) || !PathIsURL(pszUrlPart)) { ASSERT(FALSE); } else { // we have a vaild URL, so use it StrCpyN(_szUrl, pszUrlPart, ARRAYSIZE(_szUrl)); } } } else { // LEGACY - to support shellexec() of directories. if (!_lpID) StrCpyN(_szFile, _szWorkingDir, SIZECHARS(_szFile)); } PathUnquoteSpaces(_szFile); TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileName() exit: szFile = %s", _szFile); } void CShellExecute::_SetFileAndUrl() { TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileAndUrl() enter: pszIn = %s", _szUrl); if (*_szUrl && SUCCEEDED(_QueryString(0, ASSOCSTR_EXECUTABLE, _szTemp, SIZECHARS(_szTemp))) && DoesAppWantUrl(_szTemp)) { // we have a vaild URL, so use it StrCpyN(_szFile, _szUrl, ARRAYSIZE(_szFile)); } TraceMsg(TF_SHELLEXEC, "SHEX::_SetFileAndUrl() exit: szFile = %s",_szFile); } // // _TryValidateUNC() has queer return values // TRYRESULT CShellExecute::_TryValidateUNC(LPTSTR pszFile, LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl) { HRESULT hr = S_FALSE; TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if (PathIsUNC(pszFile)) { TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC Is UNC: %s", pszFile); // Notes: // SHValidateUNC() returns FALSE if it failed. In such a case, // GetLastError will gives us the right error code. // if (!(_sfgaoID & SFGAO_FILESYSTEM) && !SHValidateUNC(_hwndParent, pszFile, _uConnect)) { tr = TRY_STOP; // Note that SHValidateUNC calls SetLastError() and we need // to preserve that so that the caller makes the right decision DWORD err = GetLastError(); if (ERROR_CANCELLED == err) { // Not a print share, use the error returned from the first call // _ReportWin32(ERROR_CANCELLED); // we dont need to report this error, it is the callers responsibility // the caller should GetLastError() on E_FAIL and do a _ReportWin32() TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC FAILED with ERROR_CANCELLED"); } else if (pei && ERROR_NOT_SUPPORTED == err && PathIsUNC(pszFile)) { // // Now check to see if it's a print share, if it is, we need to exec as pidl // // we only check for print shares when ERROR_NOT_SUPPORTED is returned // from the first call to SHValidateUNC(). this error means that // the RESOURCETYPE did not match the requested. // // Note: This call should not display "connect ui" because SHValidateUNC() // will already have shown UI if necessary/possible. // CONNECT_CURRENT_MEDIA is not used by any provider (per JSchwart) // if (SHValidateUNC(_hwndParent, pszFile, VALIDATEUNC_NOUI | VALIDATEUNC_PRINT)) { tr = TRY_CONTINUE; TraceMsg(TF_SHELLEXEC, "SHEX::TVUNC found print share"); } else // need to reset the orginal error ,cuz SHValidateUNC() has set it again SetLastError(err); } } else { TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC UNC is accessible"); } } TraceMsg(TF_SHELLEXEC, "SHEX::_TVUNC exit: hr = %X", hr); switch (tr) { // TRY_CONTINUE_UNHANDLED pszFile is not a UNC or is a valid UNC according to the flags // TRY_CONTINUE pszFile is a valid UNC to a print share // TRY_STOP pszFile is a UNC but cannot be validated use GetLastError() to get the real error case TRY_CONTINUE: // we got a good UNC ASSERT(pei); tr = _DoExecPidl(pei, pidl); // if we dont get a pidl we just try something else. break; case TRY_STOP: if (pei) _ReportWin32(GetLastError()); tr = TRY_STOP; break; default: break; } return tr; } HRESULT _InvokeInProcExec(IContextMenu *pcm, LPSHELLEXECUTEINFO pei) { HRESULT hr = E_OUTOFMEMORY; HMENU hmenu = CreatePopupMenu(); if (hmenu) { CMINVOKECOMMANDINFOEX ici; void * pvFree; if (SUCCEEDED(SEI2ICIX(pei, &ici, &pvFree))) { BOOL fDefVerb (ici.lpVerb == NULL || *ici.lpVerb == 0); // This optimization eliminate creating handlers that // will not change the default verb UINT uFlags = fDefVerb ? CMF_DEFAULTONLY : 0; ici.fMask |= CMIC_MASK_FLAG_NO_UI; hr = pcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, uFlags); if (SUCCEEDED(hr)) { if (fDefVerb) { UINT idCmd = GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0); if (-1 == idCmd) { // i dont think we should ever get here... ici.lpVerb = (LPSTR)MAKEINTRESOURCE(0); // best guess } else ici.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - CONTEXTMENU_IDCMD_FIRST); } hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici); } if (pvFree) LocalFree(pvFree); } DestroyMenu(hmenu); } return hr; } BOOL CShellExecute::_ShellExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidlExec) { IContextMenu *pcm; HRESULT hr = SHGetUIObjectFromFullPIDL(pidlExec, pei->hwnd, IID_PPV_ARG(IContextMenu, &pcm)); if (SUCCEEDED(hr)) { hr = _InvokeInProcExec(pcm, pei); pcm->Release(); } if (FAILED(hr)) { DWORD errWin32 = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : GetLastError(); if (!errWin32) errWin32 = ERROR_ACCESS_DENIED; if (errWin32 != ERROR_CANCELLED) _ReportWin32(errWin32); } TraceMsg(TF_SHELLEXEC, "SHEX::_ShellExecPidl() exiting hr = %X", hr); return(SUCCEEDED(hr)); } // // BOOL CShellExecute::_DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl) // // returns TRUE if a pidl was created, FALSE otherwise // TRYRESULT CShellExecute::_DoExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl) { TraceMsg(TF_SHELLEXEC, "SHEX::_DoExecPidl enter: szFile = %s", _szFile); LPITEMIDLIST pidlFree = NULL; if (!pidl) pidl = pidlFree = ILCreateFromPath(_szFile); if (pidl) { // // if _ShellExecPidl() FAILS, it does // Report() for us // _ShellExecPidl(pei, pidl); if (pidlFree) ILFree(pidlFree); return TRY_STOP; } else { TraceMsg(TF_SHELLEXEC, "SHEX::_DoExecPidl() unhandled cuz ILCreateFromPath() failed"); return TRY_CONTINUE; } } /*---------------------------------------------------------- Purpose: This function looks up the given file in "HKLM\Software\ Microsoft\Windows\CurrentVersion\App Paths" to see if it has an absolute path registered. Returns: TRUE if the file has a registered path FALSE if it does not or if the provided filename has a relative path already Cond: !! Side effect: the szFile field may be changed by !! this function. */ BOOL CShellExecute::_CheckForRegisteredProgram() { TCHAR szTemp[MAX_PATH]; TraceMsg(TF_SHELLEXEC, "SHEX::CFRP entered"); // Only supported for files with no paths specified if (PathIsFileSpec(_szFile) && PathToAppPath(_szFile, szTemp) && PathResolve(szTemp, NULL, PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS)) { TraceMsg(TF_SHELLEXEC, "SHEX::CFRP Set szFile = %s", szTemp); StrCpyN(_szFile, szTemp, ARRAYSIZE(_szFile)); return TRUE; } return FALSE; } BOOL CShellExecute::_Resolve(LPCITEMIDLIST *ppidl) { // No; get the fully qualified path and add .exe extension // if needed LPCTSTR rgszDirs[2] = { _szWorkingDir, NULL }; const UINT uFlags = PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF; // if the Path is not an URL // and the path cant be resolved // // PathResolve() now does SetLastError() when we pass VERIFYEXISTS // this means that we can be assure if all these tests fail // that LastError is set. // // _CheckForRegisteredProgram() changes _szFile if // there is a registered program in the registry // so we recheck to see if it exists. if (!_fNoResolve && !_fIsUrl && !_fIsNamespaceObject && !_CheckForRegisteredProgram()) { if (!PathResolve(_szFile, rgszDirs, uFlags)) { DWORD cchFile = ARRAYSIZE(_szFile); if (S_OK != UrlApplyScheme(_szFile, _szFile, &cchFile, URL_APPLY_GUESSSCHEME)) { // No; file not found, bail out // // WARNING LEGACY - we must return ERROR_FILE_NOT_FOUND - ZekeL - 14-APR-99 // some apps, specifically Netscape Navigator 4.5, rely on this // failing with ERROR_FILE_NOT_FOUND. so even though PathResolve() does // a SetLastError() to the correct error we cannot propagate that up // _ReportWin32(ERROR_FILE_NOT_FOUND); ASSERT(_err); TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl FAILED %d", _err); return FALSE; } else _fIsUrl = TRUE; } } // _PerfPidl(ppidl); return TRUE; } // this is the SAFER exe detection API // only use if this is really a file system file // and we are planning on using CreateProcess() TRYRESULT CShellExecute::_VerifySaferTrust(PCWSTR pszFile) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; DWORD dwPolicy, cbPolicy; if (_cpt == CPT_NORMAL && SaferGetPolicyInformation( SAFER_SCOPEID_MACHINE, SaferPolicyEnableTransparentEnforcement, sizeof(dwPolicy), &dwPolicy, &cbPolicy, NULL) && dwPolicy != 0 && SaferiIsExecutableFileType(pszFile, TRUE) && (_pszQueryVerb && !StrCmpIW(_pszQueryVerb, L"open"))) { SAFER_LEVEL_HANDLE hAuthzLevel; SAFER_CODE_PROPERTIES codeprop; // prepare the code properties struct. memset(&codeprop, 0, sizeof(codeprop)); codeprop.cbSize = sizeof(SAFER_CODE_PROPERTIES); codeprop.dwCheckFlags = SAFER_CRITERIA_IMAGEPATH | SAFER_CRITERIA_IMAGEHASH | SAFER_CRITERIA_AUTHENTICODE; codeprop.ImagePath = pszFile; codeprop.dwWVTUIChoice = WTD_UI_NOBAD; codeprop.hWndParent = _hwndParent; // // check if file extension is of executable type, don't care on error // // evaluate all of the criteria and get the resulting level. if (SaferIdentifyLevel( 1, // only 1 element in codeprop[] &codeprop, // pointer to one-element array &hAuthzLevel, // receives identified level NULL)) { // // try to log an event in case level != SAFER_LEVELID_FULLYTRUSTED // // compute the final restricted token that should be used. ASSERT(_hCloseToken == NULL); if (SaferComputeTokenFromLevel( hAuthzLevel, // identified level restrictions NULL, // source token &_hUserToken, // resulting restricted token SAFER_TOKEN_NULL_IF_EQUAL, NULL)) { if (_hUserToken) { _cpt = CPT_ASUSER; // WARNING - runas is needed to circumvent DDE - ZekeL - 31 -JAN-2001 // we must set runas as the verb so that we make sure // that we are not using a type that is going to do window reuse // via DDE (or anything else). if they dont support runas, then the // the exec will fail, intentionally. _pszQueryVerb = L"runas"; tr = TRY_CONTINUE; } _hCloseToken = _hUserToken; // potentially NULL } else { // TODO: add event logging callback here. _ReportWin32(GetLastError()); SaferRecordEventLogEntry(hAuthzLevel, pszFile, NULL); tr = TRY_STOP; } if (tr != TRY_STOP) { // we havent added anything to our log // try to log an event in case level != AUTHZLEVELID_FULLYTRUST ED DWORD dwLevelId; DWORD dwBufferSize; if (SaferGetLevelInformation( hAuthzLevel, SaferObjectLevelId, &dwLevelId, sizeof(DWORD), &dwBufferSize)) { if ( dwLevelId != SAFER_LEVELID_FULLYTRUSTED ) { SaferRecordEventLogEntry(hAuthzLevel, pszFile, NULL); } } } SaferCloseLevel(hAuthzLevel); } else { _ReportWin32(GetLastError()); tr = TRY_STOP; } } return tr; } HANDLE _GetSandboxToken() { SAFER_LEVEL_HANDLE hConstrainedAuthz; HANDLE hSandboxToken = NULL; // right now we always use the SAFER_LEVELID_CONSTRAINED to "sandbox" the process if (SaferCreateLevel(SAFER_SCOPEID_MACHINE, SAFER_LEVELID_CONSTRAINED, SAFER_LEVEL_OPEN, &hConstrainedAuthz, NULL)) { if (!SaferComputeTokenFromLevel( hConstrainedAuthz, NULL, &hSandboxToken, 0, NULL)) { hSandboxToken = NULL; } SaferCloseLevel(hConstrainedAuthz); } return hSandboxToken; } TRYRESULT CShellExecute::_ZoneCheckFile(PCWSTR pszFile) { TRYRESULT tr = TRY_STOP; // now we need to determine if it is intranet or local zone DWORD dwPolicy = 0, dwContext = 0; ZoneCheckUrlEx(pszFile, &dwPolicy, sizeof(dwPolicy), &dwContext, sizeof(dwContext), URLACTION_SHELL_SHELLEXECUTE, PUAF_ISFILE | PUAF_NOUI, NULL); dwPolicy = GetUrlPolicyPermissions(dwPolicy); switch (dwPolicy) { case URLPOLICY_ALLOW: tr = TRY_CONTINUE_UNHANDLED; // continue break; case URLPOLICY_QUERY: if (SafeOpenPromptForShellExec(_hwndParent, pszFile)) { tr = TRY_CONTINUE; } else { // user cancelled tr = TRY_STOP; _ReportWin32(ERROR_CANCELLED); } break; case URLPOLICY_DISALLOW: tr = TRY_STOP; _ReportWin32(ERROR_ACCESS_DENIED); break; default: ASSERT(FALSE); break; } return tr; } TRYRESULT CShellExecute::_VerifyZoneTrust(PCWSTR pszFile) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; // // pszFile maybe different than _szFile in the case of being invoked by a LNK or URL // in this case we could prompt for either but not both // we only care about the target file's type for determining dangerousness // so that shortcuts to TXT files should never get a prompt. // if (pszFile == internet) prompt(pszFile) // else if (_szFile = internet prompt(_szFile) // if (AssocIsDangerous(PathFindExtension(_szFile))) { // first try tr = _ZoneCheckFile(pszFile); if (tr == TRY_CONTINUE_UNHANDLED && pszFile != _szFile) tr = _ZoneCheckFile(_szFile); } return tr; } TRYRESULT CShellExecute::_VerifyExecTrust(LPSHELLEXECUTEINFO pei) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if ((_sfgaoID & (SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_STREAM)) == (SFGAO_FILESYSTEM | SFGAO_STREAM)) { // if this is a FILE, we check for security implications // if fHasLinkName is set, then this invoke originates from an LNK file // the _lpTitle should have the acual path to the LNK. we want to verify // our trust of that more than the trust of the target PCWSTR pszFile = (pei->fMask & SEE_MASK_HASLINKNAME && _lpTitle) ? _lpTitle : _szFile; BOOL fZoneCheck = !(pei->fMask & SEE_MASK_NOZONECHECKS); if (fZoneCheck) { // 630796 - check the env var for policy scripts - ZekeL - 31-MAY-2002 // scripts cannot be updated, and they need to be trusted // since a script can call into more scripts without passing // the SEE_MASK_NOZONECHECKS. if (GetEnvironmentVariable(L"SEE_MASK_NOZONECHECKS", _szTemp, ARRAYSIZE(_szTemp))) { fZoneCheck = (0 != StrCmpICW(_szTemp, L"1")); ASSERT(!IsProcessAnExplorer()); } } if (fZoneCheck) tr = _VerifyZoneTrust(pszFile); if (tr == TRY_CONTINUE_UNHANDLED) tr = _VerifySaferTrust(pszFile); } return tr; } /*---------------------------------------------------------- Purpose: decide whether it is appropriate to TryExecPidl() Returns: S_OK if it should _DoExecPidl() S_FALSE it shouldnt _DoExecPidl() E_FAIL ShellExec should quit Report*() has the real error Cond: !! Side effect: the szFile field may be changed by !! this function. */ TRYRESULT CShellExecute::_TryExecPidl(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl entered szFile = %s", _szFile); // // If we're explicitly given a class then we don't care if the file exists. // Just let the handler for the class worry about it, and _TryExecPidl() // will return the default of FALSE. // // these should never coincide RIP(!(_fInvokeIdList && _fUseClass)); if ((*_szFile || pidl) && (!_fUseClass || _fInvokeIdList || _fIsNamespaceObject)) { if (!pidl && !_fNoResolve && !_Resolve(&pidl)) { tr = TRY_STOP; } if (tr == TRY_CONTINUE_UNHANDLED) { // The optimal execution path is to check for the default // verb and exec the pidl. It is smarter than all this path // code (it calls the context menu handlers, etc...) if ((!_pszQueryVerb && !(_fNoExecPidl)) || _fIsUrl || _fInvokeIdList // caller told us to! || _fIsNamespaceObject // namespace objects can only be invoked through pidls || (_sfgaoID & SFGAO_LINK) || (!pidl && PathIsShortcut(_szFile, -1))) // to support LNK files and soon URL files { // this means that we can tryexecpidl TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl() succeeded now TEP()"); tr = _DoExecPidl(pei, pidl); } else { TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl dont bother"); } } } else { TraceMsg(TF_SHELLEXEC, "SHEX::TryExecPidl dont bother"); } return tr; } HRESULT CShellExecute::_InitClassAssociations(LPCTSTR pszClass, HKEY hkClass, DWORD mask) { TraceMsg(TF_SHELLEXEC, "SHEX::InitClassAssoc enter: lpClass = %s, hkClass = %X", pszClass, hkClass); HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa)); if (SUCCEEDED(hr)) { if (_UseClassKey(mask)) { hr = _pqa->Init(0, NULL, hkClass, NULL); } else if (_UseClassName(mask)) { hr = _pqa->Init(0, pszClass, NULL, NULL); } else { // LEGACY - they didnt pass us anything to go on so we default to folder // because of the chaos of the original shellexec() we didnt even notice // when we had nothing to be associated with, and just used // our base key, which turns out to be explorer. // this permitted ShellExecute(NULL, "explore", NULL, NULL, NULL, SW_SHOW); // to succeed. in order to support this, we will fall back to it here. hr = _pqa->Init(0, L"Folder", NULL, NULL); } } return hr; } HRESULT CShellExecute::_InitShellAssociations(LPCTSTR pszFile, LPCITEMIDLIST pidl) { TraceMsg(TF_SHELLEXEC, "SHEX::InitShellAssoc enter: pszFile = %s, pidl = %X", pszFile, pidl); HRESULT hr = E_FAIL; LPITEMIDLIST pidlFree = NULL; if (*pszFile) { if (!pidl) { hr = SHILCreateFromPath(pszFile, &pidlFree, NULL); if (SUCCEEDED(hr)) pidl = pidlFree; } } else if (pidl) { // Other parts of CShellExecute expect that _szFile is // filled in, so we may as well do it here. SHGetNameAndFlags(pidl, SHGDN_FORPARSING, _szFile, SIZECHARS(_szFile), NULL); _fNoResolve = TRUE; } if (pidl) { // NT#413115 - ShellExec("D:\") does AutoRun.inf instead of Folder.Open - ZekeL - 25-JUN-2001 // this is because drivflder now explicitly supports GetUIObjectOf(IQueryAssociations) // whereas it didnt in win2k, so that SHGetAssociations() would fallback to "Folder". // to emulate this, we communicate that this associations object is going to be // used by ShellExec() for invocation, so we dont want all of the keys in the assoc array. // IBindCtx *pbc; TBCRegisterObjectParam(L"ShellExec SHGetAssociations", NULL, &pbc); hr = SHGetAssociations(pidl, (void **)&_pqa); if (pbc) pbc->Release(); // NOTE: sometimes we can have the extension or even the progid in the registry, but there // is no "shell" subkey. An example of this is for .xls files in NT5: the index server guys // create HKCR\.xls and HKCR\Excel.Sheet.8 but all they put under Excel.Sheet.8 is the clsid. // // so we need to check and make sure that we have a valid command value for // this object. if we dont, then that means that this is not valid // class to shellexec with. we need to fall back to the Unknown key // so that we can query the Darwin/NT5 ClassStore and/or // show the openwith dialog box. // DWORD cch; if (FAILED(hr) || (FAILED(_pqa->GetString(0, ASSOCSTR_COMMAND, _pszQueryVerb, NULL, &cch)) && FAILED(_pqa->GetData(0, ASSOCDATA_MSIDESCRIPTOR, _pszQueryVerb, NULL, &cch)))) { if (!_pqa) hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa)); if (_pqa) { hr = _pqa->Init(0, L"Unknown", NULL, NULL); // this allows us to locate something // in the class store, but restricts us // from using the openwith dialog if the // caller instructed NOUI if (SUCCEEDED(hr) && _fNoUI) _fClassStoreOnly = TRUE; } } } else { LPCTSTR pszExt = PathFindExtension(_szFile); if (*pszExt) { hr = _InitClassAssociations(pszExt, NULL, SEE_MASK_CLASSNAME); if (S_OK!=hr) { TraceMsg(TF_WARNING, "SHEX::InitAssoc parsing failed, but there is a valid association for *.%s", pszExt); } } } if (pidlFree) ILFree(pidlFree); return hr; } TRYRESULT CShellExecute::_InitAssociations(LPSHELLEXECUTEINFO pei, LPCITEMIDLIST pidl) { HRESULT hr; if (pei && (_fUseClass || (!_szFile[0] && !_lpID))) { hr = _InitClassAssociations(pei->lpClass, pei->hkeyClass, pei->fMask); } else { hr = _InitShellAssociations(_szFile, pidl ? pidl : _lpID); } TraceMsg(TF_SHELLEXEC, "SHEX::InitAssoc return %X", hr); if (FAILED(hr)) { if (PathIsExe(_szFile)) { hr = S_FALSE; } else { _ReportWin32(ERROR_NO_ASSOCIATION); } } return SUCCEEDED(hr) ? TRY_CONTINUE : TRY_STOP; } void CShellExecute::_SetStartup(LPSHELLEXECUTEINFO pei) { // Was zero filled by Alloc... ASSERT(!_startup.cb); _startup.cb = sizeof(_startup); _startup.dwFlags |= STARTF_USESHOWWINDOW; _startup.wShowWindow = (WORD) pei->nShow; _startup.lpTitle = (LPTSTR)_lpTitle; if (pei->fMask & SEE_MASK_RESERVED) { _startup.lpReserved = (LPTSTR)pei->hInstApp; } if ((pei->fMask & SEE_MASK_HASLINKNAME) && _lpTitle) { _startup.dwFlags |= STARTF_TITLEISLINKNAME; } if (pei->fMask & SEE_MASK_HOTKEY) { _startup.hStdInput = LongToHandle(pei->dwHotKey); _startup.dwFlags |= STARTF_USEHOTKEY; } // Multi-monitor support (dli) pass a hMonitor to createprocess #ifndef STARTF_HASHMONITOR #define STARTF_HASHMONITOR 0x00000400 // same as HASSHELLDATA #endif if (pei->fMask & SEE_MASK_ICON) { _startup.hStdOutput = (HANDLE)pei->hIcon; _startup.dwFlags |= STARTF_HASSHELLDATA; } else if (pei->fMask & SEE_MASK_HMONITOR) { _startup.hStdOutput = (HANDLE)pei->hMonitor; _startup.dwFlags |= STARTF_HASHMONITOR; } else if (pei->hwnd) { _startup.hStdOutput = (HANDLE)MonitorFromWindow(pei->hwnd,MONITOR_DEFAULTTONEAREST); _startup.dwFlags |= STARTF_HASHMONITOR; } TraceMsg(TF_SHELLEXEC, "SHEX::SetStartup() called"); } DWORD CEnvironmentBlock::_BlockLen(LPCWSTR pszEnv) { LPCWSTR psz = pszEnv; while (*psz) { psz += lstrlen(psz)+1; } return (DWORD)(psz - pszEnv) + 1; } DWORD CEnvironmentBlock::_BlockLenCached() { if (!_cchBlockLen && _pszBlock) { _cchBlockLen = _BlockLen(_pszBlock); } return _cchBlockLen; } HRESULT CEnvironmentBlock::_InitBlock(DWORD cchNeeded) { if (_BlockLenCached() + cchNeeded > _cchBlockSize) { if (!_pszBlock) { // we need to create a new block. LPTSTR pszEnv = GetEnvBlock(_hToken); if (pszEnv) { // Now lets allocate some memory for our block. // -- Why 10 and not 11? Or 9? -- // Comment from BobDay: 2 of the 10 come from nul terminators of the // pseem->_szTemp and cchT strings added on. The additional space might // come from the fact that 16-bit Windows used to pass around an // environment block that had some extra stuff on the end. The extra // stuff had things like the path name (argv[0]) and a nCmdShow value. DWORD cchEnv = _BlockLen(pszEnv); DWORD cchAlloc = ROUNDUP(cchEnv + cchNeeded + 10, 256); _pszBlock = (LPWSTR)LocalAlloc(LPTR, CbFromCchW(cchAlloc)); if (_pszBlock) { // copy stuff over CopyMemory(_pszBlock, pszEnv, CbFromCchW(cchEnv)); _cchBlockSize = cchAlloc - 10; // leave the 10 out _cchBlockLen = cchEnv; } FreeEnvBlock(_hToken, pszEnv); } } else { // need to resize the current block DWORD cchAlloc = ROUNDUP(_cchBlockSize + cchNeeded + 10, 256); LPWSTR pszNew = (LPWSTR)LocalReAlloc(_pszBlock, CbFromCchW(cchAlloc), LMEM_MOVEABLE); if (pszNew) { _cchBlockSize = cchAlloc - 10; // leave the 10 out _pszBlock = pszNew; } } } return (_BlockLenCached() + cchNeeded <= _cchBlockSize) ? S_OK : E_OUTOFMEMORY; } BOOL CEnvironmentBlock::_FindVar(LPCWSTR pszVar, DWORD cchVar, LPWSTR *ppszBlockVar) { int iCmp = CSTR_LESS_THAN; LPTSTR psz = _pszBlock; ASSERT(_pszBlock); for ( ; *psz && iCmp == CSTR_LESS_THAN; psz += lstrlen(psz)+1) { iCmp = CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, psz, cchVar, pszVar, cchVar); *ppszBlockVar = psz; } if (iCmp == CSTR_LESS_THAN) *ppszBlockVar = psz; return iCmp == CSTR_EQUAL; } HRESULT CEnvironmentBlock::SetVar(LPCWSTR pszVar, LPCWSTR pszValue) { // additional size needed in worst case scenario. // var + val + '=' + NULL DWORD cchValue = lstrlenW(pszValue); DWORD cchVar = lstrlenW(pszVar); DWORD cchNeeded = cchVar + cchValue + 2; HRESULT hr = _InitBlock(cchNeeded); if (SUCCEEDED(hr)) { // we have enough room in our private block // to copy the whole thing LPWSTR pszBlockVar; if (_FindVar(pszVar, cchVar, &pszBlockVar)) { // we need to replace this var LPWSTR pszBlockVal = StrChrW(pszBlockVar, L'='); DWORD cchBlockVal = lstrlenW(++pszBlockVal); LPWSTR pszDst = pszBlockVal + cchValue + 1; LPWSTR pszSrc = pszBlockVal + cchBlockVal + 1; DWORD cchMove = _BlockLenCached() - (DWORD)(pszSrc - _pszBlock); MoveMemory(pszDst, pszSrc, CbFromCchW(cchMove)); StrCpyW(pszBlockVal, pszValue); _cchBlockLen = _cchBlockLen + cchValue - cchBlockVal; ASSERT(_BlockLen(_pszBlock) == _cchBlockLen); } else { // this means that var doesnt exist yet // however pszBlockVar points to where it // would be alphabetically. need to make space right here LPWSTR pszDst = pszBlockVar + cchNeeded; INT cchMove = _BlockLenCached() - (DWORD)(pszBlockVar - _pszBlock); MoveMemory(pszDst, pszBlockVar, CbFromCchW(cchMove)); StrCpyW(pszBlockVar, pszVar); pszBlockVar += cchVar; *pszBlockVar = L'='; StrCpyW(++pszBlockVar, pszValue); _cchBlockLen += cchNeeded; ASSERT(_BlockLen(_pszBlock) == _cchBlockLen); } } return hr; } HRESULT CEnvironmentBlock::AppendVar(LPCWSTR pszVar, WCHAR chDelimiter, LPCWSTR pszValue) { // we could make the delimiter optional // additional size needed in worst case scenario. // var + val + 'chDelim' + '=' + NULL DWORD cchValue = lstrlenW(pszValue); DWORD cchVar = lstrlenW(pszVar); DWORD cchNeeded = cchVar + cchValue + 3; HRESULT hr = _InitBlock(cchNeeded); if (SUCCEEDED(hr)) { // we have enough room in our private block // to copy the whole thing LPWSTR pszBlockVar; if (_FindVar(pszVar, cchVar, &pszBlockVar)) { // we need to append to this var pszBlockVar += lstrlen(pszBlockVar); LPWSTR pszDst = pszBlockVar + cchValue + 1; int cchMove = _BlockLenCached() - (DWORD)(pszBlockVar - _pszBlock); MoveMemory(pszDst, pszBlockVar, CbFromCchW(cchMove)); *pszBlockVar = chDelimiter; StrCpyW(++pszBlockVar, pszValue); _cchBlockLen += cchValue + 1; ASSERT(_BlockLen(_pszBlock) == _cchBlockLen); } else hr = SetVar(pszVar, pszValue); } return hr; } HRESULT CShellExecute::_BuildEnvironmentForNewProcess(LPCTSTR pszNewEnvString) { HRESULT hr; _envblock.SetToken(_hUserToken); // Use the _szTemp to build key to the programs specific // key in the registry as well as other things... hr = PathToAppPathKey(_szApplication, _szTemp, SIZECHARS(_szTemp)); if (SUCCEEDED(hr)) { // Currently only clone environment if we have path. DWORD cbTemp = sizeof(_szTemp); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, _szTemp, TEXT("PATH"), NULL, _szTemp, &cbTemp)) { // setit up to be appended hr = _envblock.AppendVar(L"PATH", L';', _szTemp); } } if (SUCCEEDED(hr) && pszNewEnvString) { StrCpyN(_szTemp, pszNewEnvString, ARRAYSIZE(_szTemp)); LPTSTR pszValue = StrChrW(_szTemp, L'='); if (pszValue) { *pszValue++ = 0; hr = _envblock.SetVar(_szTemp, pszValue); } } if (SUCCEEDED(hr) && SUCCEEDED(TBCGetEnvironmentVariable(L"__COMPAT_LAYER", _szTemp, ARRAYSIZE(_szTemp)))) { hr = _envblock.SetVar(L"__COMPAT_LAYER", _szTemp); } return hr; } // Some apps when run no-active steal the focus anyway so we // we set it back to the previously active window. void CShellExecute::_FixActivationStealingApps(HWND hwndOldActive, int nShow) { HWND hwndNew; if (nShow == SW_SHOWMINNOACTIVE && (hwndNew = GetForegroundWindow()) != hwndOldActive && IsIconic(hwndNew)) SetForegroundWindow(hwndOldActive); } // // The flags that need to passed to CreateProcess() // DWORD CShellExecute::_GetCreateFlags(ULONG fMask) { DWORD dwFlags = 0; dwFlags |= CREATE_DEFAULT_ERROR_MODE; if (fMask & SEE_MASK_FLAG_SEPVDM) { dwFlags |= CREATE_SEPARATE_WOW_VDM; } dwFlags |= CREATE_UNICODE_ENVIRONMENT; if (!(fMask & SEE_MASK_NO_CONSOLE)) { dwFlags |= CREATE_NEW_CONSOLE; } return dwFlags; } //*** GetUEMAssoc -- approximate answer to 'is path an executable' (etc.) // ENTRY/EXIT // pszFile thing we asked to run (e.g. foo.xls) // pszImage thing we ultimately ran (e.g. excel.exe) int GetUEMAssoc(LPCTSTR pszFile, LPCTSTR pszImage, LPCITEMIDLIST pidl) { LPTSTR pszExt, pszExt2; // .exe's and associations come thru here // folders go thru ??? // links go thru ResolveLink pszExt = PathFindExtension(pszFile); if (StrCmpIC(pszExt, c_szDotExe) == 0) { // only check .exe (assume .com, .bat, etc. are rare) return UIBL_DOTEXE; } pszExt2 = PathFindExtension(pszImage); // StrCmpC (non-I, yes-C) o.k ? i think so since // all we really care about is that they don't match if (StrCmpC(pszExt, pszExt2) != 0) { TraceMsg(DM_MISC, "gua: UIBL_DOTASSOC file=%s image=%s", pszExt, pszExt2); return UIBL_DOTASSOC; } int iRet = UIBL_DOTOTHER; // UIBL_DOTEXE? if (pidl) { LPCITEMIDLIST pidlChild; IShellFolder *psf; if (SUCCEEDED(SHBindToFolderIDListParent(NULL, pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild))) { if (SHGetAttributes(psf, pidlChild, SFGAO_FOLDER | SFGAO_STREAM) == SFGAO_FOLDER) { iRet = UIBL_DOTFOLDER; } psf->Release(); } } return iRet; } typedef struct { TCHAR szAppName[MAX_PATH]; TCHAR szUser[UNLEN + 1]; TCHAR szDomain[GNLEN + 1]; TCHAR szPassword[PWLEN + 1]; CPTYPE cpt; } LOGONINFO; // this is what gets called in the normal runas case void InitUserLogonDlg(LOGONINFO* pli, HWND hDlg, LPCTSTR pszFullUserName) { HWNDWSPrintf(GetDlgItem(hDlg, IDC_USECURRENTACCOUNT), pszFullUserName); CheckRadioButton(hDlg, IDC_USECURRENTACCOUNT, IDC_USEOTHERACCOUNT, IDC_USECURRENTACCOUNT); CheckDlgButton(hDlg, IDC_SANDBOX, TRUE); EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), FALSE); SetFocus(GetDlgItem(hDlg, IDOK)); } // this is what gets called in the install app launching as non admin case void InitSetupLogonDlg(LOGONINFO* pli, HWND hDlg, LPCTSTR pszFullUserName) { HWNDWSPrintf(GetDlgItem(hDlg, IDC_USECURRENTACCOUNT), pszFullUserName); HWNDWSPrintf(GetDlgItem(hDlg, IDC_MESSAGEBOXCHECKEX), pszFullUserName); CheckRadioButton(hDlg, IDC_USECURRENTACCOUNT, IDC_USEOTHERACCOUNT, IDC_USEOTHERACCOUNT); EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), FALSE); SetFocus(GetDlgItem(hDlg, IDC_CREDCTL)); } BOOL_PTR CALLBACK UserLogon_DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { TCHAR szTemp[UNLEN + 1 + GNLEN + 1]; // enough to hold "reinerf@NTDEV" or "NTDEV\reinerf" LOGONINFO *pli= (LOGONINFO*)GetWindowLongPtr(hDlg, DWLP_USER); switch (uMsg) { case WM_INITDIALOG: { TCHAR szName[UNLEN]; TCHAR szFullName[UNLEN + 1 + GNLEN]; // enough to hold "reinerf@NTDEV" or "NTDEV\reinerf" ULONG cchFullName = ARRAYSIZE(szFullName); HWND hwndCred = GetDlgItem(hDlg, IDC_CREDCTL); WPARAM wparamCredStyles = CRS_USERNAMES | CRS_CERTIFICATES | CRS_SMARTCARDS | CRS_ADMINISTRATORS | CRS_PREFILLADMIN; pli = (LOGONINFO*)lParam; SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)pli); if (!IsOS(OS_DOMAINMEMBER)) { wparamCredStyles |= CRS_COMPLETEUSERNAME; } if (!Credential_InitStyle(hwndCred, wparamCredStyles)) { EndDialog(hDlg, IDCANCEL); } // Limit the user name and password Credential_SetUserNameMaxChars(hwndCred, UNLEN + 1 + GNLEN); // enough room for "reinerf@NTDEV" or "NTDEV\reinerf" Credential_SetPasswordMaxChars(hwndCred, PWLEN); if (!GetUserNameEx(NameSamCompatible, szFullName, &cchFullName)) { ULONG cchName; if (GetUserNameEx(NameDisplay, szName, &(cchName = ARRAYSIZE(szName))) || GetUserName(szName, &(cchName = ARRAYSIZE(szName))) || (GetEnvironmentVariable(TEXT("USERNAME"), szName, ARRAYSIZE(szName)) > 0)) { if (GetEnvironmentVariable(TEXT("USERDOMAIN"), szFullName, ARRAYSIZE(szFullName)) > 0) { StringCchCat(szFullName, ARRAYSIZE(szFullName), TEXT("\\")); StringCchCat(szFullName, ARRAYSIZE(szFullName), szName); } else { // use just the username if we cannot get a domain name StrCpyN(szFullName, szName, ARRAYSIZE(szFullName)); } } else { TraceMsg(TF_WARNING, "UserLogon_DlgProc: failed to get the user's name using various methods"); szFullName[0] = TEXT('\0'); } } // call the proper init function depending on whether this is a setup program launching or the normal runas case switch (pli->cpt) { case CPT_WITHLOGONADMIN: { InitSetupLogonDlg(pli, hDlg, szFullName); break; } case CPT_WITHLOGON: { InitUserLogonDlg(pli, hDlg, szFullName); break; } default: { ASSERTMSG(FALSE, "UserLogon_DlgProc: found CPTYPE that is not CPT_WITHLOGON or CPT_WITHLOGONADMIN!"); } } break; } break; case WM_COMMAND: { CPTYPE cptRet = CPT_WITHLOGONCANCELLED; int idCmd = GET_WM_COMMAND_ID(wParam, lParam); switch (idCmd) { /* need some way to tell that valid credentials are present so we will only enable the ok button if the user has something that is somewhat valid case IDC_USERNAME: if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_UPDATE) { EnableOKButtonFromID(hDlg, IDC_USERNAME); GetDlgItemText(hDlg, IDC_USERNAME, szTemp, ARRAYSIZE(szTemp)); } break; */ case IDC_USEOTHERACCOUNT: case IDC_USECURRENTACCOUNT: if (IsDlgButtonChecked(hDlg, IDC_USECURRENTACCOUNT)) { EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), FALSE); EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), TRUE); // EnableWindow(GetDlgItem(hDlg, IDOK), TRUE); } else { EnableWindow(GetDlgItem(hDlg, IDC_CREDCTL), TRUE); EnableWindow(GetDlgItem(hDlg, IDC_SANDBOX), FALSE); Credential_SetUserNameFocus(GetDlgItem(hDlg, IDC_CREDCTL)); // EnableOKButtonFromID(hDlg, IDC_USERNAME); } break; case IDOK: if (IsDlgButtonChecked(hDlg, IDC_USEOTHERACCOUNT)) { HWND hwndCred = GetDlgItem(hDlg, IDC_CREDCTL); if (Credential_GetUserName(hwndCred, szTemp, ARRAYSIZE(szTemp)) && Credential_GetPassword(hwndCred, pli->szPassword, ARRAYSIZE(pli->szPassword))) { CredUIParseUserName(szTemp, pli->szUser, ARRAYSIZE(pli->szUser), pli->szDomain, ARRAYSIZE(pli->szDomain)); } cptRet = pli->cpt; } else { if (IsDlgButtonChecked(hDlg, IDC_SANDBOX)) cptRet = CPT_SANDBOX; else cptRet = CPT_NORMAL; } // fall through case IDCANCEL: EndDialog(hDlg, cptRet); return TRUE; break; } break; } default: return FALSE; } if (!pli || (pli->cpt == CPT_WITHLOGONADMIN)) { // we want the MessageBoxCheckExDlgProc have a crack at all messages in // the CPT_WITHLOGONADMIN case, so return FALSE here return FALSE; } else { return TRUE; } } // implement this after we figure out what // errors that CreateProcessWithLogonW() will return // that mean the user should retry the logon. BOOL _IsLogonError(DWORD err) { static const DWORD s_aLogonErrs[] = { ERROR_LOGON_FAILURE, ERROR_ACCOUNT_RESTRICTION, ERROR_INVALID_LOGON_HOURS, ERROR_INVALID_WORKSTATION, ERROR_PASSWORD_EXPIRED, ERROR_ACCOUNT_DISABLED, ERROR_NONE_MAPPED, ERROR_NO_SUCH_USER, ERROR_INVALID_ACCOUNT_NAME }; for (int i = 0; i < ARRAYSIZE(s_aLogonErrs); i++) { if (err == s_aLogonErrs[i]) return TRUE; } return FALSE; } BOOL CheckForAppPathsBoolValue(LPCTSTR pszImageName, LPCTSTR pszValueName) { BOOL bRet = FALSE; TCHAR szAppPathKeyName[MAX_PATH + ARRAYSIZE(REGSTR_PATH_APPPATHS) + 2]; // +2 = +1 for '\' and +1 for the null terminator DWORD cbSize = sizeof(bRet); HRESULT hr = PathToAppPathKey(pszImageName, szAppPathKeyName, ARRAYSIZE(szAppPathKeyName)); if (SUCCEEDED(hr)) { SHGetValue(HKEY_LOCAL_MACHINE, szAppPathKeyName, pszValueName, NULL, &bRet, &cbSize); } return bRet; } __inline BOOL IsRunAsSetupExe(LPCTSTR pszImageName) { return CheckForAppPathsBoolValue(pszImageName, TEXT("RunAsOnNonAdminInstall")); } __inline BOOL IsTSSetupExe(LPCTSTR pszImageName) { return CheckForAppPathsBoolValue(pszImageName, TEXT("BlockOnTSNonInstallMode")); } typedef BOOL (__stdcall * PFNTERMSRVAPPINSTALLMODE)(void); // This function is used by hydra (Terminal Server) to see if we // are in application install mode // // exported from kernel32.dll by name but not in kernel32.lib (it is in kernel32p.lib, bogus) BOOL TermsrvAppInstallMode() { static PFNTERMSRVAPPINSTALLMODE s_pfn = NULL; if (NULL == s_pfn) { s_pfn = (PFNTERMSRVAPPINSTALLMODE)GetProcAddress(LoadLibrary(TEXT("KERNEL32.DLL")), "TermsrvAppInstallMode"); } return s_pfn ? s_pfn() : FALSE; } // // this function checks for the different cases where we need to display a "runas" or warning dialog // before a program is run. // // NOTE: pli->raType is an outparam that tells the caller what type of dialog is needed // // return: TRUE - we need to bring up a dialog // FALSE - we do not need to prompt the user // CPTYPE CheckForInstallApplication(LPCTSTR pszApplicationName, LOGONINFO* pli) { // if we are on a TS "Application Server" machine, AND this is a TS setup exe (eg install.exe or setup.exe) // AND we aren't in install mode... if (IsOS(OS_TERMINALSERVER) && IsTSSetupExe(pszApplicationName) && !TermsrvAppInstallMode()) { // ...AND the app we are launching is not TS aware, then we block the install and tell the user to go // to Add/Remove Programs. if (!IsExeTSAware(pszApplicationName)) { TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: blocking the install on TS because the machine is not in install mode for %s", pszApplicationName); return CPT_INSTALLTS; } } // 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) if (!SHRestricted(REST_NORUNASINSTALLPROMPT) && IsRunAsSetupExe(pszApplicationName) && !IsUserAnAdmin()) { BOOL bPromptForInstall = TRUE; if (!SHRestricted(REST_PROMPTRUNASINSTALLNETPATH)) { if (PathIsUNC(pszApplicationName) || IsNetDrive(PathGetDriveNumber(pszApplicationName))) { TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: not prompting for runas install on unc/network path %s", pszApplicationName); bPromptForInstall = FALSE; } } if (bPromptForInstall) { TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: bringing up the Run As... dialog for %s", pszApplicationName); return CPT_WITHLOGONADMIN; } } return CPT_NORMAL; } typedef HRESULT (__stdcall * PFN_INSTALLONTERMINALSERVERWITHUI)(IN HWND hwnd, IN LPCWSTR lpApplicationName, // name of executable module LPCWSTR lpCommandLine, // command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, // handle inheritance flag DWORD dwCreationFlags, // creation flags void *lpEnvironment, // new environment block LPCWSTR lpCurrentDirectory, // current directory name LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation); HRESULT InstallOnTerminalServerWithUIDD(IN HWND hwnd, IN LPCWSTR lpApplicationName, // name of executable module IN LPCWSTR lpCommandLine, // command line string IN LPSECURITY_ATTRIBUTES lpProcessAttributes, IN LPSECURITY_ATTRIBUTES lpThreadAttributes, IN BOOL bInheritHandles, // handle inheritance flag IN DWORD dwCreationFlags, // creation flags IN void *lpEnvironment, // new environment block IN LPCWSTR lpCurrentDirectory, // current directory name IN LPSTARTUPINFOW lpStartupInfo, IN LPPROCESS_INFORMATION lpProcessInformation) { HRESULT hr = E_FAIL; HINSTANCE hDll = LoadLibrary(TEXT("appwiz.cpl")); if (hDll) { PFN_INSTALLONTERMINALSERVERWITHUI pfnInstallOnTerminalServerWithUI = NULL; pfnInstallOnTerminalServerWithUI = (PFN_INSTALLONTERMINALSERVERWITHUI) GetProcAddress(hDll, "InstallOnTerminalServerWithUI"); if (pfnInstallOnTerminalServerWithUI) { hr = pfnInstallOnTerminalServerWithUI(hwnd, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } FreeLibrary(hDll); } return hr; } CPTYPE _LogonUser(HWND hwnd, CPTYPE cpt, LOGONINFO *pli) { if (CredUIInitControls()) { pli->cpt = cpt; switch (cpt) { case CPT_WITHLOGON: // this is the normal "Run as..." verb dialog cpt = (CPTYPE) DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_RUNUSERLOGON), hwnd, UserLogon_DlgProc, (LPARAM)pli); break; case CPT_WITHLOGONADMIN: // in the non-administrator setup app case. we want the "don't show me // this again" functionality, so we use the SHMessageBoxCheckEx function cpt = (CPTYPE) SHMessageBoxCheckEx(hwnd, HINST_THISDLL, MAKEINTRESOURCE(DLG_RUNSETUPLOGON), UserLogon_DlgProc, (void*)pli, CPT_NORMAL, // if they checked the "dont show me this again", we want to just launch it as the current user TEXT("WarnOnNonAdminInstall")); break; default: { ASSERTMSG(FALSE, "_SHCreateProcess: pli->raType not recognized!"); } break; } return cpt; } return CPT_FAILED; } // // SHCreateProcess() // WARNING: lpApplicationName is not actually passed to CreateProcess() it is // for internal use only. // BOOL _SHCreateProcess(HWND hwnd, HANDLE hToken, LPCTSTR pszDisplayName, LPCTSTR lpApplicationName, LPTSTR lpCommandLine, DWORD dwCreationFlags, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, void *lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation, CPTYPE cpt, BOOL fUEM) { LOGONINFO li = {0}; // maybe we should do this for all calls // except CPT_ASUSER?? if (cpt == CPT_NORMAL) { // see if we need to put up a warning prompt either because the user is not an // admin or this is hydra and we are not in install mode. cpt = CheckForInstallApplication(lpApplicationName, &li); } if ((cpt == CPT_WITHLOGON || cpt == CPT_WITHLOGONADMIN) && pszDisplayName) { StringCchCopy(li.szAppName, ARRAYSIZE(li.szAppName), pszDisplayName); RetryUserLogon: cpt = _LogonUser(hwnd, cpt, &li); } BOOL fRet = FALSE; DWORD err = NOERROR; // BUGFIX #612540 + #616999 - CMD and BAT files are handled specially by CreateProcess() - ZekeL - 14-MAY-2002 // CreateProcess() handles prepending "cmd.exe /c" differently since // we now pass in both the lpApplicationName and the lpCommandLine. // changing CreateProcess() is too risky, and so is changing the file assocs // for CMD and BAT files only, we will not pass in both parameters if (PathMatchSpec(PathFindFileName(lpApplicationName), L"*.CMD;*.BAT")) lpApplicationName = NULL; switch(cpt) { case CPT_NORMAL: { // DEFAULT use CreateProcess fRet = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } break; case CPT_SANDBOX: { ASSERT(!hToken); hToken = _GetSandboxToken(); if (hToken) { // using our special token fRet = CreateProcessAsUser(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); CloseHandle(hToken); } // no token means failure. } break; case CPT_ASUSER: { if (hToken) { // using our special token fRet = CreateProcessAsUser(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_PRESERVE_CODE_AUTHZ_LEVEL, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } else { // no token means normal create process, but with the "preserve authz level" flag. fRet = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_PRESERVE_CODE_AUTHZ_LEVEL, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } } break; case CPT_INSTALLTS: { HRESULT hr = InstallOnTerminalServerWithUIDD(hwnd, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); fRet = SUCCEEDED(hr); err = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : ERROR_ACCESS_DENIED; } break; case CPT_WITHLOGON: case CPT_WITHLOGONADMIN: { LPTSTR pszDesktop = lpStartupInfo->lpDesktop; // 99/08/19 #389284 vtan: clip username and domain to 125 // characters each to avoid hitting the combined MAX_PATH // limit in AllowDesktopAccessToUser in advapi32.dll which // is invoked by CreateProcessWithLogonW. // This can be removed when the API is fixed. Check: // %_ntbindir%\mergedcomponents\advapi\cseclogn.cxx li.szUser[125] = li.szDomain[125] = 0; // we are attempting logon the user. NOTE: pass LOGON_WITH_PROFILE so that we ensure that the profile is loaded fRet = CreateProcessWithLogonW(li.szUser, li.szDomain, li.szPassword, LOGON_WITH_PROFILE, lpApplicationName, lpCommandLine, dwCreationFlags, NULL, // we pass a null lpEnvironment so that the new process will inherit the new users default env lpCurrentDirectory, lpStartupInfo, lpProcessInformation); if (!fRet) { // HACKHACK: When CreateProcessWithLogon fails, it munges the desktop. This causes // the next call to "Appear" to fail because the app show up on another desktop... // Why? I don't know... // I'm going to assign the bug back to them and have them fix it on their end, this is just to // work around their bug. if (lpStartupInfo) lpStartupInfo->lpDesktop = pszDesktop; // ShellMessageBox can alter LastError err = GetLastError(); if (_IsLogonError(err)) { TCHAR szTemp[MAX_PATH]; LoadString(HINST_THISDLL, IDS_CANTLOGON, szTemp, SIZECHARS(szTemp)); SHSysErrorMessageBox( hwnd, li.szAppName, IDS_SHLEXEC_ERROR, err, szTemp, MB_OK | MB_ICONSTOP); err = NOERROR; goto RetryUserLogon; } } } break; case CPT_WITHLOGONCANCELLED: err = ERROR_CANCELLED; break; } // fire *after* the actual process since: // - if there's a bug we at least get the process started (hopefully) // - don't want to log failed events (for now at least) if (fRet) { if (fUEM && UEMIsLoaded()) { // skip the call if stuff isn't there yet. // the load is expensive (forces ole32.dll and browseui.dll in // and then pins browseui). UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)lpApplicationName); // we do the UIBW_RUNASSOC elsewhere. this can cause slight // inaccuracies since there's no guarantees the 2 places are // 'paired'. however it's way easier to do UIBW_RUNASSOC // elsewhere so we'll live w/ it. } } else if (err) { SetLastError(err); } else { // somebody is responsible for setting this... ASSERT(GetLastError()); } // eliminate the password SecureZeroMemory(&li, sizeof(li)); return fRet; } BOOL _ParamIsApp(PCWSTR pszCmdTemplate) { return (0 == StrCmpNW(pszCmdTemplate, L"%1", ARRAYSIZE(L"%1")-1)) || (0 == StrCmpNW(pszCmdTemplate, L"\"%1\"", ARRAYSIZE(L"\"%1\"")-1)); } BOOL CShellExecute::_FileIsApp() { return !_szCmdTemplate[0] && PathIsExe(_szFile) && (!_pszQueryVerb || StrCmpI(_pszQueryVerb, TEXT("open"))); } __inline BOOL IsConsoleApp(PCWSTR pszApp) { return GetExeType(pszApp) == PEMAGIC; } BOOL IsCurrentProcessConsole() { static TRIBIT s_tbConsole = TRIBIT_UNDEFINED; if (s_tbConsole == TRIBIT_UNDEFINED) { WCHAR sz[MAX_PATH]; if (GetModuleFileNameW(NULL, sz, ARRAYSIZE(sz)) && IsConsoleApp(sz)) { s_tbConsole = TRIBIT_TRUE; } else { s_tbConsole = TRIBIT_FALSE; } } return s_tbConsole == TRIBIT_TRUE; } BOOL CShellExecute::_SetCommand(void) { HRESULT hr; BOOL fRet = FALSE; if (_ParamIsApp(_szCmdTemplate) || _FileIsApp()) { // we need to fix up the parameters hr = StringCchCopy(_szApplication, ARRAYSIZE(_szApplication), _szFile); DWORD cchImageName = ARRAYSIZE(_szAppFriendly); AssocQueryString(ASSOCF_VERIFY | ASSOCF_INIT_BYEXENAME, ASSOCSTR_FRIENDLYAPPNAME, _szApplication, NULL, _szAppFriendly, &cchImageName); StringCchCopy(_szPolicyApp, ARRAYSIZE(_szPolicyApp), _szFile); } else if (_szCmdTemplate[0]) { PWSTR pszApp; PWSTR pszCmd; hr = SHEvaluateSystemCommandTemplate(_szCmdTemplate, &pszApp, &pszCmd, NULL); if (SUCCEEDED(hr)) { // we need to fix up the parameters hr = StringCchCopy(_szApplication, ARRAYSIZE(_szApplication), pszApp); StringCchCopy(_szCmdTemplate, ARRAYSIZE(_szCmdTemplate), pszCmd); // the Assoc code will redirect for RunDll32 to the DLL name _QueryString(ASSOCF_VERIFY, ASSOCSTR_FRIENDLYAPPNAME, _szAppFriendly, ARRAYSIZE(_szAppFriendly)); _QueryString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, _szPolicyApp, ARRAYSIZE(_szPolicyApp)); CoTaskMemFree(pszApp); CoTaskMemFree(pszCmd); } else if (hr == CO_E_APPNOTFOUND) { // this replaces the old find associated exe if (!_fNoUI) { // have user choose a new association for this type OPENASINFO oai = {0}; oai.pcszFile = _szFile; oai.dwInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; _ReportWin32(SUCCEEDED(OpenAsDialog(_hwndParent, &oai)) ? ERROR_SUCCESS : ERROR_CANCELLED); hr = S_FALSE; } } } else { _ReportWin32(ERROR_NO_ASSOCIATION); hr = S_FALSE; } if (S_OK == hr) { // parse arguments into command line DWORD se_err = ReplaceParameters(_szCommand, ARRAYSIZE(_szCommand), _szFile, _szCmdTemplate, _lpParameters, _nShow, NULL, FALSE, _lpID, &_pidlGlobal); if (0 == se_err) fRet = TRUE; else _ReportHinst(IntToHinst(se_err)); if (!_fInheritHandles && SHRestricted(REST_INHERITCONSOLEHANDLES)) { _fInheritHandles = IsCurrentProcessConsole() && IsConsoleApp(_szApplication); } } else if (FAILED(hr)) { _ReportWin32(hr); } return fRet; } BOOL CShellExecute::_ExecMayCreateProcess(LPCTSTR *ppszNewEnvString) { DWORD err = ERROR_SUCCESS; // Check exec restrictions. if (SHRestricted(REST_RESTRICTRUN) && RestrictedApp(_szPolicyApp)) { err = ERROR_RESTRICTED_APP; } else if (SHRestricted(REST_DISALLOWRUN) && DisallowedApp(_szPolicyApp)) { err = ERROR_RESTRICTED_APP; } // try to validate the image if it is on a UNC share // we dont need to check for Print shares, so we // will fail if it is on one. else if (STOPTRYING(_TryValidateUNC(_szPolicyApp, NULL, NULL))) { // returns TRUE if it failed or handled the operation // Note that SHValidateUNC calls SetLastError // this continue will test based on GetLastError() err = GetLastError(); } // // WOWShellExecute sets a global variable // The cb is only valid when we are being called from wow // If valid use it // else if (STOPTRYING(_TryWowShellExec())) return FALSE; return !_ReportWin32(err); } // // TryExecCommand() is the most common and default way to get an app started. // mostly it uses CreateProcess() with a command line composed from // the pei and the registry. it can also do a ddeexec afterwards. // void CShellExecute::_DoExecCommand(void) { TraceMsg(TF_SHELLEXEC, "SHEX::DoExecCommand() entered szCommand = %s", _szCommand); HWND hwndOld = GetForegroundWindow(); LPCTSTR pszNewEnvString = NULL; if (_SetCommand() && _ExecMayCreateProcess(&pszNewEnvString)) { // See if we need to pass a new environment to the new process _BuildEnvironmentForNewProcess(pszNewEnvString); TraceMsg(TF_SHELLEXEC, "SHEX::DoExecCommand() CreateProcess(NULL,%s,...)", _szCommand); // CreateProcess will SetLastError() if it fails if (_SHCreateProcess(_hwndParent, _hUserToken, _szAppFriendly, _szApplication, _szCommand, _dwCreateFlags, _pProcAttrs, _pThreadAttrs, _fInheritHandles, _envblock.GetCustomBlock(), _fUseNullCWD ? NULL : _szWorkingDir, &_startup, &_pi, _cpt, _fUEM)) { // If we're doing DDE we'd better wait for the app to be up and running // before we try to talk to them. if (_fDDEInfoSet || _fWaitForInputIdle) { // Yep, How long to wait? For now, try 60 seconds to handle // pig-slow OLE apps. WaitForInputIdle(_pi.hProcess, 60*1000); } // Find the "hinstance" of whatever we just created. // PEIOUT - hinst reported for pei->hInstApp HINSTANCE hinst = 0; // Now fix the focus and do any dde stuff that we need to do _FixActivationStealingApps(hwndOld, _nShow); if (_fDDEInfoSet) { // this will _Report() any errors for us if necessary _DDEExecute(NULL, _hwndParent, _nShow, _fDDEWait); } else _ReportHinst(hinst); // // Tell the taskbar about this application so it can re-tickle // the associated shortcut if the app runs for a long time. // This keeps long-running apps from aging off your Start Menu. // if (_fUEM && (_startup.dwFlags & STARTF_TITLEISLINKNAME)) { _NotifyShortcutInvoke(); } } else { _ReportWin32(GetLastError()); } } // (we used to do a UIBW_RUNASSOC here, but moved it higher up) } void CShellExecute::_NotifyShortcutInvoke() { SHShortcutInvokeAsIDList sidl; sidl.cb = FIELD_OFFSET(SHShortcutInvokeAsIDList, cbZero); sidl.dwItem1 = SHCNEE_SHORTCUTINVOKE; sidl.dwPid = _pi.dwProcessId; if (_startup.lpTitle) { lstrcpynW(sidl.szShortcutName, _startup.lpTitle, ARRAYSIZE(sidl.szShortcutName)); } else { sidl.szShortcutName[0] = TEXT('\0'); } lstrcpynW(sidl.szTargetName, _szApplication, ARRAYSIZE(sidl.szTargetName)); sidl.cbZero = 0; SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_IDLIST, (LPCITEMIDLIST)&sidl, NULL); } HGLOBAL CShellExecute::_CreateDDECommand(int nShow, BOOL fLFNAware, BOOL fNative) { // Now that we can handle ShellExec for URLs, we need to have a much bigger // command buffer. Explorer's DDE exec command even has two file names in // it. (WHY?) So the command buffer have to be a least twice the size of // INTERNET_MAX_URL_LENGTH plus room for the command format. SHSTR strTemp; HGLOBAL hRet = NULL; if (SUCCEEDED(strTemp.SetSize((2 * INTERNET_MAX_URL_LENGTH) + 64))) { if (0 == ReplaceParameters(strTemp.GetInplaceStr(), strTemp.GetSize(), _szFile, _szDDECmd, _lpParameters, nShow, ((DWORD*) &_startup.hStdInput), fLFNAware, _lpID, &_pidlGlobal)) { TraceMsg(TF_SHELLEXEC, "SHEX::_CreateDDECommand(%d, %d) : %s", fLFNAware, fNative, strTemp.GetStr()); // we only have to thunk on NT if (!fNative) { SHSTRA stra; if (SUCCEEDED(stra.SetStr(strTemp))) { // Get dde memory for the command and copy the command line. hRet = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, CbFromCch(lstrlenA(stra.GetStr()) + 1)); if (hRet) { LPSTR psz = (LPSTR) GlobalLock(hRet); StrCpyNA(psz, stra.GetStr(), GlobalSize(hRet) / sizeof(CHAR)); GlobalUnlock(hRet); } } } else { // Get dde memory for the command and copy the command line. hRet = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, CbFromCch(lstrlen(strTemp.GetStr()) + 1)); if (hRet) { LPTSTR psz = (LPTSTR) GlobalLock(hRet); StrCpyN(psz, strTemp.GetStr(), GlobalSize(hRet) / sizeof(TCHAR)); GlobalUnlock(hRet); } } } } return hRet; } // Short cut all DDE commands with a WM_NOTIFY // returns true if this was handled...or unrecoverable error. BOOL CShellExecute::_TryDDEShortCircuit(HWND hwnd, HGLOBAL hMem, int nShow) { if (hwnd && IsWindowInProcess(hwnd)) { HINSTANCE hret = (HINSTANCE)SE_ERR_FNF; // get the top most owner. hwnd = GetTopParentWindow(hwnd); if (IsWindowInProcess(hwnd)) { LPNMVIEWFOLDER lpnm = (LPNMVIEWFOLDER)LocalAlloc(LPTR, sizeof(NMVIEWFOLDER)); if (lpnm) { lpnm->hdr.hwndFrom = NULL; lpnm->hdr.idFrom = 0; lpnm->hdr.code = SEN_DDEEXECUTE; lpnm->dwHotKey = HandleToUlong(_startup.hStdInput); if ((_startup.dwFlags & STARTF_HASHMONITOR) != 0) lpnm->hMonitor = reinterpret_cast(_startup.hStdOutput); else lpnm->hMonitor = NULL; StrCpyN(lpnm->szCmd, (LPTSTR) GlobalLock(hMem), ARRAYSIZE(lpnm->szCmd)); GlobalUnlock(hMem); if (SendMessage(hwnd, WM_NOTIFY, 0, (LPARAM)lpnm)) hret = Window_GetInstance(hwnd); LocalFree(lpnm); } else hret = (HINSTANCE)SE_ERR_OOM; } TraceMsg(TF_SHELLEXEC, "SHEX::_TryDDEShortcut hinst = %d", hret); if ((UINT_PTR)hret != SE_ERR_FNF) { _ReportHinst(hret); return TRUE; } } return FALSE; } // _WaiteForDDEMsg() // this does a message loop until DDE msg or a timeout occurs // STDAPI_(void) _WaitForDDEMsg(HWND hwnd, DWORD dwTimeout, UINT wMsg) { // termination event HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); SetProp(hwnd, SZTERMEVENT, hEvent); for (;;) { MSG msg; DWORD dwEndTime = GetTickCount() + dwTimeout; LONG lWait = (LONG)dwTimeout; DWORD dwReturn = MsgWaitForMultipleObjects(1, &hEvent, FALSE, lWait, QS_POSTMESSAGE); // if we time out or get an error or get our EVENT!!! // we just bag out if (dwReturn != (WAIT_OBJECT_0 + 1)) { break; } // we woke up because of messages. while (PeekMessage(&msg, NULL, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)) { ASSERT(msg.message != WM_QUIT); DispatchMessage(&msg); if (msg.hwnd == hwnd && msg.message == wMsg) goto Quit; } // calculate new timeout value if (dwTimeout != INFINITE) { lWait = (LONG)dwEndTime - GetTickCount(); } } Quit: if (hEvent) CloseHandle(hEvent); RemoveProp(hwnd, SZTERMEVENT); return; } LRESULT CALLBACK DDESubClassWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { HWND hwndConv = (HWND) GetProp(hWnd, SZCONV); WPARAM nLow; WPARAM nHigh; HANDLE hEvent; switch (wMsg) { case WM_DDE_ACK: if (!hwndConv) { // this is the first ACK for our INITIATE message TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd get ACK on INITIATE"); return SetProp(hWnd, SZCONV, (HANDLE)wParam); } else if (((UINT_PTR)hwndConv == 1) || ((HWND)wParam == hwndConv)) { // this is the ACK for our EXECUTE message TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd got ACK on EXECUTE"); if (UnpackDDElParam(wMsg, lParam, &nLow, &nHigh)) { GlobalFree((HGLOBAL)nHigh); FreeDDElParam(wMsg, lParam); } // prevent us from destroying again.... if ((UINT_PTR) hwndConv != 1) DestroyWindow(hWnd); } // This is the ACK for our INITIATE message for all servers // besides the first. We return FALSE, so the conversation // should terminate. break; case WM_DDE_TERMINATE: if (hwndConv == (HANDLE)wParam) { // this TERMINATE was originated by another application // (otherwise, hwndConv would be 1) // they should have freed the memory for the exec message TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd got TERMINATE from hwndConv"); PostMessage((HWND)wParam, WM_DDE_TERMINATE, (WPARAM)hWnd, 0L); RemoveProp(hWnd, SZCONV); DestroyWindow(hWnd); } // Signal the termination event to ensure nested dde calls will terminate the // appropriate _WaitForDDEMsg loop properly... if (hEvent = GetProp(hWnd, SZTERMEVENT)) SetEvent(hEvent); // This is the TERMINATE response for our TERMINATE message // or a random terminate (which we don't really care about) break; case WM_TIMER: if (wParam == DDE_DEATH_TIMER_ID) { // The conversation will be terminated in the destroy code DestroyWindow(hWnd); TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd TIMER closing DDE Window due to lack of ACK"); break; } else return DefWindowProc(hWnd, wMsg, wParam, lParam); case WM_DESTROY: TraceMsg(TF_SHELLEXEC, "SHEX::DDEStubWnd WM_DESTROY'd"); // kill the timer just incase.... (this may fail if we never set the timer) KillTimer(hWnd, DDE_DEATH_TIMER_ID); if (hwndConv) { // Make sure the window is not destroyed twice SetProp(hWnd, SZCONV, (HANDLE)1); /* Post the TERMINATE message and then * Wait for the acknowledging TERMINATE message or a timeout */ PostMessage(hwndConv, WM_DDE_TERMINATE, (WPARAM)hWnd, 0L); _WaitForDDEMsg(hWnd, DDE_TERMINATETIMEOUT, WM_DDE_TERMINATE); RemoveProp(hWnd, SZCONV); } // the DDE conversation is officially over, let ShellExec know if it was waiting hEvent = RemoveProp(hWnd, SZDDEEVENT); if (hEvent) { SetEvent(hEvent); } /* Fall through */ default: return DefWindowProc(hWnd, wMsg, wParam, lParam); } return 0L; } HWND CShellExecute::_CreateHiddenDDEWindow(HWND hwndParent) { // lets be lazy and not create a class for it HWND hwnd = SHCreateWorkerWindow(DDESubClassWndProc, GetTopParentWindow(hwndParent), 0, 0, NULL, NULL); TraceMsg(TF_SHELLEXEC, "SHEX::_CreateHiddenDDEWindow returning hwnd = 0x%X", hwnd); return hwnd; } void CShellExecute::_DestroyHiddenDDEWindow(HWND hwnd) { if (IsWindow(hwnd)) { TraceMsg(TF_SHELLEXEC, "SHEX::_DestroyHiddenDDEWindow on hwnd = 0x%X", hwnd); DestroyWindow(hwnd); } } BOOL CShellExecute::_PostDDEExecute(HWND hwndOurs, HWND hwndTheirs, HGLOBAL hDDECommand, HANDLE hWait) { TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute(0x%X, 0x%X) entered", hwndTheirs, hwndOurs); DWORD dwProcessID = 0; GetWindowThreadProcessId(hwndTheirs, &dwProcessID); if (dwProcessID) { AllowSetForegroundWindow(dwProcessID); } if (PostMessage(hwndTheirs, WM_DDE_EXECUTE, (WPARAM)hwndOurs, (LPARAM)PackDDElParam(WM_DDE_EXECUTE, 0,(UINT_PTR)hDDECommand))) { _ReportHinst(Window_GetInstance(hwndTheirs)); TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() connected"); // everything's going fine so far, so return to the application // with the instance handle of the guy, and hope he can execute our string if (hWait) { // We can't return from this call until the DDE conversation terminates. // Otherwise the thread may go away, nuking our hwndConv window, // messing up the DDE conversation, and Word drops funky error messages on us. TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() waiting for termination"); SetProp(hwndOurs, SZDDEEVENT, hWait); SHProcessMessagesUntilEvent(NULL, hWait, INFINITE); // it is removed during WM_DESTROY (before signaling) } else if (IsWindow(hwndOurs)) { // set a timer to tidy up the window incase we never get a ACK.... TraceMsg(TF_SHELLEXEC, "SHEX::_PostDDEExecute() setting DEATH timer"); SetTimer(hwndOurs, DDE_DEATH_TIMER_ID, DDE_DEATH_TIMEOUT, NULL); } return TRUE; } return FALSE; } #define DDE_TIMEOUT 30000 // 30 seconds. #define DDE_TIMEOUT_LOW_MEM 80000 // 80 seconds - Excel takes 77.87 on 486.33 with 8mb typedef struct { WORD aName; HWND hwndDDE; LONG lAppTopic; UINT timeout; } INITDDECONV; HWND CShellExecute::_GetConversationWindow(HWND hwndDDE) { ULONG_PTR dwResult; //unused HWND hwnd = NULL; INITDDECONV idc = { NULL, hwndDDE, MAKELONG(_aApplication, _aTopic), SHIsLowMemoryMachine(ILMM_IE4) ? DDE_TIMEOUT_LOW_MEM : DDE_TIMEOUT }; // if we didnt find him, then we better default to the old way... if (!hwnd) { // we found somebody who used to like us... // Send the initiate message. // NB This doesn't need packing. SendMessageTimeout((HWND) -1, WM_DDE_INITIATE, (WPARAM)hwndDDE, idc.lAppTopic, SMTO_ABORTIFHUNG, idc.timeout, &dwResult); hwnd = (HWND) GetProp(hwndDDE, SZCONV); } TraceMsg(TF_SHELLEXEC, "SHEX::GetConvWnd returns [%X]", hwnd); return hwnd; } BOOL CShellExecute::_DDEExecute( BOOL fWillRetry, HWND hwndParent, int nShowCmd, BOOL fWaitForDDE ) { LONG err = ERROR_OUTOFMEMORY; BOOL fReportErr = TRUE; // Get the actual command string. // NB We'll assume the guy we're going to talk to is LFN aware. If we're wrong // we'll rebuild the command string a bit later on. HGLOBAL hDDECommand = _CreateDDECommand(nShowCmd, TRUE, TRUE); if (hDDECommand) { // we have a DDE command to try if (_TryDDEShortCircuit(hwndParent, hDDECommand, nShowCmd)) { // the shortcut tried and now we have an error reported fReportErr = FALSE; } else { HANDLE hWait = fWaitForDDE ? CreateEvent(NULL, FALSE, FALSE, NULL) : NULL; if (hWait || !fWaitForDDE) { // Create a hidden window for the conversation HWND hwndDDE = _CreateHiddenDDEWindow(hwndParent); if (hwndDDE) { HWND hwndConv = _GetConversationWindow(hwndDDE); if (hwndConv) { // somebody answered us. // This doesn't work if the other guy is using ddeml. if (_fActivateHandler) ActivateHandler(hwndConv, (DWORD_PTR) _startup.hStdInput); // Can the guy we're talking to handle LFNs? BOOL fLFNAware = Window_IsLFNAware(hwndConv); BOOL fNative = IsWindowUnicode(hwndConv); if (!fLFNAware || !fNative) { // we need to redo the command string. // Nope - App isn't LFN aware - redo the command string. GlobalFree(hDDECommand); // we may need a new _pidlGlobal too. if (_pidlGlobal) { SHFreeShared((HANDLE)_pidlGlobal,GetCurrentProcessId()); _pidlGlobal = NULL; } hDDECommand = _CreateDDECommand(nShowCmd, fLFNAware, fNative); } // Send the execute message to the application. err = ERROR_DDE_FAIL; if (_PostDDEExecute(hwndDDE, hwndConv, hDDECommand, hWait)) { fReportErr = FALSE; hDDECommand = NULL; // hwnd owns itself now if (!hWait) hwndDDE = NULL; } } else { err = (ERROR_FILE_NOT_FOUND); } // cleanup _DestroyHiddenDDEWindow(hwndDDE); } if (hWait) CloseHandle(hWait); } } // cleanup if (hDDECommand) GlobalFree(hDDECommand); } if (fReportErr) { if (fWillRetry && ERROR_FILE_NOT_FOUND == err) { // this means that we need to update the // command so that we can try DDE again after // starting the app up... // if it wasn't found, determine the correct command _QueryString(0, ASSOCSTR_DDEIFEXEC, _szDDECmd, SIZECHARS(_szDDECmd)); return FALSE; } else { _ReportWin32(err); } } return TRUE; } BOOL CShellExecute::_SetDDEInfo(void) { ASSERT(_pqa); if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDECOMMAND, _szDDECmd, SIZECHARS(_szDDECmd)))) { TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo command: %s", _szDDECmd); // Any activation info? _fActivateHandler = FAILED(_pqa->GetData(0, ASSOCDATA_NOACTIVATEHANDLER, _pszQueryVerb, NULL, NULL)); if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDEAPPLICATION, _szTemp, SIZECHARS(_szTemp)))) { TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo application: %s", _szTemp); if (_aApplication) GlobalDeleteAtom(_aApplication); _aApplication = GlobalAddAtom(_szTemp); if (SUCCEEDED(_QueryString(0, ASSOCSTR_DDETOPIC, _szTemp, SIZECHARS(_szTemp)))) { TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo topic: %s", _szTemp); if (_aTopic) GlobalDeleteAtom(_aTopic); _aTopic = GlobalAddAtom(_szTemp); _fDDEInfoSet = TRUE; } } } TraceMsg(TF_SHELLEXEC, "SHEX::SetDDEInfo returns %d", _fDDEInfoSet); return _fDDEInfoSet; } TRYRESULT CShellExecute::_TryExecDDE(void) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; TraceMsg(TF_SHELLEXEC, "SHEX::TryExecDDE entered "); if (_SetDDEInfo()) { // try the real deal here. we pass TRUE for fWillRetry because // if this fails to find the app, we will attempt to start // the app and then use DDE again. if (_DDEExecute(TRUE, _hwndParent, _nShow, _fDDEWait)) tr = TRY_STOP; } TraceMsg(TF_SHELLEXEC, "SHEX::TryDDEExec() returning %d", tr); return tr; } TRYRESULT CShellExecute::_SetDarwinCmdTemplate(BOOL fSync) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if (SUCCEEDED(_pqa->GetData(0, ASSOCDATA_MSIDESCRIPTOR, _pszQueryVerb, (void *)_wszTemp, (LPDWORD)MAKEINTRESOURCE(sizeof(_wszTemp))))) { if (fSync) { // call darwin to give us the real location of the app. // // Note: this call could possibly fault the application in thus // installing it on the users machine. HRESULT hr = ParseDarwinID(_wszTemp, _szCmdTemplate, ARRAYSIZE(_szCmdTemplate)); if (SUCCEEDED(hr)) { tr = TRY_CONTINUE; } else { _ReportWin32(hr); tr = TRY_STOP; } } else tr = TRY_RETRYASYNC; } return tr; } HRESULT CShellExecute::_QueryString(ASSOCF flags, ASSOCSTR str, LPTSTR psz, DWORD cch) { if (_pqa) { HRESULT hr = _pqa->GetString(flags, str, _pszQueryVerb, _wszTemp, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(_wszTemp))); if (SUCCEEDED(hr)) SHUnicodeToTChar(_wszTemp, psz, cch); return hr; } return E_FAIL; } BOOL CShellExecute::_SetAppRunAsCmdTemplate(void) { DWORD cb = sizeof(_szCmdTemplate); // we want to use a special command HRESULT hr = PathToAppPathKey(_szFile, _szTemp, SIZECHARS(_szTemp)); if (SUCCEEDED(hr)) { return (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, _szTemp, TEXT("RunAsCommand"), NULL, _szCmdTemplate, &cb) && *_szCmdTemplate); } else { return FALSE; } } #if DBG && defined(_X86_) #pragma optimize("", off) // work around compiler bug #endif TRYRESULT CShellExecute::_MaybeInstallApp(BOOL fSync) { // we check darwin first since it should override everything else TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if (IsDarwinEnabled()) { // if darwin is enabled, then check for the darwin ID in // the registry and set the value based on that. tr = _SetDarwinCmdTemplate(fSync); } if (TRY_CONTINUE_UNHANDLED == tr) { // no darwin information in the registry // so now we have to check to see if the NT5 class store will populate our registry // with some helpful information (darwin or otherwise) tr = _ShouldRetryWithNewClassKey(fSync); } return tr; } TRYRESULT CShellExecute::_SetCmdTemplate(BOOL fSync) { TRYRESULT tr = _MaybeInstallApp(fSync); if (tr == TRY_CONTINUE_UNHANDLED) { // // both darwin and the class store were unsuccessful, so fall back to // the good ole' default command value. // // but if we the caller requested NOUI and we // decided to use Unknown as the class // then we should fail here so that // we dont popup the OpenWith dialog box. // HRESULT hr = E_FAIL; if (!_fClassStoreOnly) { if ((_cpt != CPT_NORMAL) || !PathIsExe(_szFile) || !_SetAppRunAsCmdTemplate()) { hr = _QueryString(0, ASSOCSTR_COMMAND, _szCmdTemplate, SIZECHARS(_szCmdTemplate)); } } if (SUCCEEDED(hr)) { tr = TRY_CONTINUE; } else { _ReportWin32(ERROR_NO_ASSOCIATION); tr = TRY_STOP; } } TraceMsg(TF_SHELLEXEC, "SHEX::SetCmdTemplate() value = %s", _szCmdTemplate); return tr; } #if DBG && defined(_X86_) #pragma optimize("", on) // return to previous optimization level #endif TRYRESULT CShellExecute::_TryWowShellExec(void) { // WOWShellExecute sets this global variable // The cb is only valid when we are being called from wow // If valid use it if (g_pfnWowShellExecCB) { SHSTRA strCmd; SHSTRA strDir; HINSTANCE hinst = (HINSTANCE)SE_ERR_OOM; if (SUCCEEDED(strCmd.SetStr(_szCommand)) && SUCCEEDED(strDir.SetStr(_szWorkingDir))) { hinst = IntToHinst((*(LPFNWOWSHELLEXECCB)g_pfnWowShellExecCB)(strCmd.GetInplaceStr(), _startup.wShowWindow, strDir.GetInplaceStr())); } if (!_ReportHinst(hinst)) { // SUCCESS! // // If we were doing DDE, then retry now that the app has been // exec'd. Note we don't keep HINSTANCE returned from _DDEExecute // because it will be constant 33 instead of the valid WOW HINSTANCE // returned from *g_pfnWowShellExecCB above. // if (_fDDEInfoSet) { _DDEExecute(NULL, _hwndParent, _nShow, _fDDEWait); } } TraceMsg(TF_SHELLEXEC, "SHEX::TryWowShellExec() used Wow"); return TRY_STOP; } return TRY_CONTINUE_UNHANDLED; } TRYRESULT CShellExecute::_ShouldRetryWithNewClassKey(BOOL fSync) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; // If this is an app who's association is unknown, we might need to query the ClassStore if // we have not already done so. // The easiest way we can tell if the file we are going to execute is "Unknown" is by looking for // the "QueryClassStore" string value under the hkey we have. DllInstall in shell32 writes this key // so that we know when we are dealing with HKCR\Unknown (or any other progid that always wants to // do a classtore lookup) if (!_fAlreadyQueriedClassStore && !_fNoQueryClassStore && SUCCEEDED(_pqa->GetData(0, ASSOCDATA_QUERYCLASSSTORE, NULL, NULL, NULL))) { if (fSync) { // go hit the NT5 Directory Services class store if (_szFile[0]) { INSTALLDATA id; LPTSTR pszExtPart; WCHAR szFileExt[MAX_PATH]; // all we have is a filename so whatever PathFindExtension // finds, we will use pszExtPart = PathFindExtension(_szFile); StrCpyN(szFileExt, pszExtPart, ARRAYSIZE(szFileExt)); // Need to zero init id (can't do a = {0} when we declated it, because it has a non-zero enum type) ZeroMemory(&id, sizeof(INSTALLDATA)); id.Type = FILEEXT; id.Spec.FileExt = szFileExt; // call the DS to lookup the file type in the class store if (ERROR_SUCCESS == InstallApplication(&id)) { // Since InstallApplication succeeded, it could have possibly installed and app // or munged the registry so that we now have the necesssary reg info to // launch the app. So basically re-read the class association to see if there is any // new darwin info or new normal info, and jump back up and retry to execute. LPITEMIDLIST pidlUnkFile = ILCreateFromPath(_szFile); if (pidlUnkFile) { IQueryAssociations *pqa; if (SUCCEEDED(SHGetAssociations(pidlUnkFile, (void **)&pqa))) { _pqa->Release(); _pqa = pqa; if (_pszQueryVerb && (lstrcmpi(_pszQueryVerb, TEXT("openas")) == 0)) { // Since we just sucessfully queried the class store, if our verb was "openas" (meaning // that we used the Unknown key to do the execute) we always reset the verb to the default. // If we do not do this, then we could fail the execute since "openas" is most likely not a // supported verb of the application _pszQueryVerb = NULL; } } ILFree(pidlUnkFile); _fAlreadyQueriedClassStore = TRUE; _fClassStoreOnly = FALSE; } } // CoGetClassInfo } // _szFile[0] } else tr = TRY_RETRYASYNC; } TraceMsg(TF_SHELLEXEC, "SHEX::ShouldRWNCK() returning %d", tr); return tr; } TRYRESULT CShellExecute::_TryHooks(LPSHELLEXECUTEINFO pei) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if (_UseHooks(pei->fMask)) { // REARCHITECT: the only client of this are URLs. // if we change psfInternet to return IID_IQueryAssociations, // then we can kill the urlexechook (our only client) if (S_FALSE != TryShellExecuteHooks(pei)) { // either way we always exit. should get TryShellhook to use SetLastError() _ReportHinst(pei->hInstApp); tr = TRY_STOP;; } } return tr; } void _PathStripTrailingDots(LPTSTR psz) { // don't strip "." or ".." if (!PathIsDotOrDotDot(psz)) { // remove any trailing dots TCHAR *pchLast = psz + lstrlen(psz) - 1; while (*pchLast == TEXT('.')) { *pchLast = 0; pchLast = CharPrev(psz, pchLast); } } } #define STR_PARSE_REQUIRE_REAL_NETWORK L"Parse Require Real Network" #define STR_PARSE_INTERNET_DONT_ESCAPE_SPACES L"Parse Internet Dont Escape Spaces" IBindCtx *CShellExecute::_PerfBindCtx() { // // 180557 - make sure that we prefer the EXE to the folder - ZekeL - 9-SEP-2000 // this so that if both "D:\Setup" and "D:\Setup.exe" exist // and the user types "D:\Setup" we will prefer to use "D:\Setup.exe" // we also have to be careful not to send URLs down to SimpleIDList // because of the weird results we get with the DavRedir // // 360353 - dont do resolve if we are passed the class key - ZekeL - 9-APR-2001 // if the caller passes us a key or class name then we must assume that // the item is already fully qualified. specifically this can result in // a double resolve when doing an Open With.... // // 206795 - dont use simple if the path is a root - ZekeL - 12-APR-2001 // specifically \\server\share needs this for printer shares with '.' to work. // (eg \\printsvr\printer.first) this fails since a simple share will // be interpreted as SFGAO_FILESYSTEM always which will cause us to avoid // the SHValidateUNC() which is what forces us to use the pidl for print shares. // i think there are some similar issues with other shares that are not on the // default provider for the server (ie DAV shares). // IBindCtx *pbc = NULL; if (_fIsUrl) { // 403781 - avoid escaping spaces in URLs from ShellExec() - ZekeL - 25-May-2001 // this is because of removing the ShellExec hooks as the mechanism // for invoking URLs and switching to just using Parse/Invoke(). // however, the old code evidently avoided doing the UrlEscapeSpaces() // which the InternetNamespace typically does on parse. // force xlate even though we are doing simple parse static BINDCTX_PARAM rgUrlParams[] = { { STR_PARSE_TRANSLATE_ALIASES, NULL}, { STR_DONT_PARSE_RELATIVE, NULL}, { STR_PARSE_INTERNET_DONT_ESCAPE_SPACES, NULL}, }; BindCtx_RegisterObjectParams(NULL, rgUrlParams, ARRAYSIZE(rgUrlParams), &pbc); } else if (!_fUseClass && !PathIsRoot(_szFile)) { DWORD dwAttribs; if (PathFileExistsDefExtAndAttributes(_szFile, PFOPEX_DEFAULT | PFOPEX_OPTIONAL, &dwAttribs)) { // we found this with the extension. // avoid hitting the disk again to do the parse WIN32_FIND_DATA wfd = {0}; wfd.dwFileAttributes = dwAttribs; _PathStripTrailingDots(_szFile); IBindCtx *pbcFile; if (SUCCEEDED(SHCreateFileSysBindCtx(&wfd, &pbcFile))) { // force xlate even though we are doing simple parse static BINDCTX_PARAM rgSimpleParams[] = { { STR_PARSE_TRANSLATE_ALIASES, NULL}, { STR_DONT_PARSE_RELATIVE, NULL}, //{ STR_PARSE_REQUIRE_REAL_NETWORK, NULL}, }; BindCtx_RegisterObjectParams(pbcFile, rgSimpleParams, ARRAYSIZE(rgSimpleParams), &pbc); pbcFile->Release(); } } else { static BINDCTX_PARAM rgDefaultParams[] = { { STR_DONT_PARSE_RELATIVE, NULL}, }; BindCtx_RegisterObjectParams(NULL, rgDefaultParams, ARRAYSIZE(rgDefaultParams), &pbc); } } return pbc; } TRYRESULT CShellExecute::_PerfPidl(LPCITEMIDLIST *ppidl) { *ppidl = _lpID; if (!_lpID) { IBindCtx *pbc = _PerfBindCtx(); HRESULT hr = SHParseDisplayName(_szFile, pbc, &_pidlFree, SFGAO_STORAGECAPMASK, &_sfgaoID); if (pbc) pbc->Release(); if (FAILED(hr) && !pbc && UrlIs(_szFile, URLIS_FILEURL)) { DWORD err = (HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : ERROR_FILE_NOT_FOUND; _ReportWin32(err); return TRY_STOP; } *ppidl = _lpID = _pidlFree; } else { _sfgaoID = SFGAO_STORAGECAPMASK; HRESULT hrT; if (*_szFile) { hrT = SHGetNameAndFlags(_lpID, 0, NULL, 0, &_sfgaoID); } else { hrT = SHGetNameAndFlags(_lpID, SHGDN_FORPARSING, _szFile, ARRAYSIZE(_szFile), &_sfgaoID); // NTBUG#629947 - dont replace the path for the remote printers folder - ZekeL - 30-MAY-2002 // the remote printers pidl comes back with a path that looks like // \\server\::{GUID Remote Printers}, which tanks in _TryValidateUNC(). // so, lets ditch the name if it is not a stream, since // we may need the path for zone checking files // if (SUCCEEDED(hrT) && !(_sfgaoID & SFGAO_STREAM)) *_szFile = 0; } if (FAILED(hrT)) _sfgaoID = 0; } return TRY_CONTINUE; } DWORD CShellExecute::_InvokeAppThreadProc() { _fDDEWait = TRUE; _TryInvokeApplication(TRUE); Release(); return 0; } DWORD WINAPI CShellExecute::s_InvokeAppThreadProc(void *pv) { return ((CShellExecute *)pv)->_InvokeAppThreadProc(); } TRYRESULT CShellExecute::_RetryAsync() { if (_lpID && !_pidlFree) _lpID = _pidlFree = ILClone(_lpID); if (_lpParameters) _lpParameters = _pszAllocParams = StrDup(_lpParameters); if (_lpTitle) _lpTitle = _startup.lpTitle = _pszAllocTitle = StrDup(_lpTitle); _fAsync = TRUE; AddRef(); if (!SHCreateThread(s_InvokeAppThreadProc, this, CTF_FREELIBANDEXIT | CTF_COINIT, NULL)) { _ReportWin32(GetLastError()); Release(); return TRY_STOP; } return TRY_RETRYASYNC; } TRYRESULT CShellExecute::_TryInvokeApplication(BOOL fSync) { TRYRESULT tr = TRY_CONTINUE_UNHANDLED; if (fSync) tr = _SetCmdTemplate(fSync); if (KEEPTRYING(tr)) { // check for both the CacheFilename and URL being passed to us, // if this is the case, we need to check to see which one the App // wants us to pass to it. _SetFileAndUrl(); tr = _TryExecDDE(); // check to see if darwin is enabled on the machine if (KEEPTRYING(tr)) { if (!fSync) tr = _SetCmdTemplate(fSync); if (KEEPTRYING(tr)) { // At this point, the _szFile should have been determined one way // or another. ASSERT(_szFile[0] || _szCmdTemplate[0]); // do we have the necessary RegDB info to do an exec? _DoExecCommand(); tr = TRY_STOP; } } } if (tr == TRY_RETRYASYNC) { // install this on another thread tr = _RetryAsync(); } return tr; } void CShellExecute::ExecuteNormal(LPSHELLEXECUTEINFO pei) { SetAppStartingCursor(pei->hwnd, TRUE); _Init(pei); // // Copy the specified directory in _szWorkingDir if the working // directory is specified; otherwise, get the current directory there. // _SetWorkingDir(pei->lpDirectory); // // Copy the file name to _szFile, if it is specified. Then, // perform environment substitution. // _SetFile(pei->lpFile, pei->fMask & SEE_MASK_FILEANDURL); LPCITEMIDLIST pidl; if (STOPTRYING(_PerfPidl(&pidl))) goto Quit; // If the specified filename is a UNC path, validate it now. if (STOPTRYING(_TryValidateUNC(_szFile, pei, pidl))) goto Quit; if (STOPTRYING(_TryHooks(pei))) goto Quit; if (STOPTRYING(_TryExecPidl(pei, pidl))) goto Quit; if (STOPTRYING(_VerifyExecTrust(pei))) goto Quit; // Is the class key provided? if (STOPTRYING(_InitAssociations(pei, pidl))) goto Quit; _TryInvokeApplication(_fDDEWait || (pei->fMask & SEE_MASK_NOCLOSEPROCESS)); Quit: // // we should only see this if the registry is corrupted. // but we still want to be able to open EXE's if (_err == ERROR_SUCCESS && UEMIsLoaded()) { // skip the call if stuff isn't there yet. // the load is expensive (forces ole32.dll and browseui.dll in // and then pins browseui). // however we ran the app (exec, dde, etc.), we succeeded. do our // best to guess the association etc. and log it. int i = GetUEMAssoc(_szFile, _szApplication, _lpID); TraceMsg(DM_MISC, "cse.e: GetUEMAssoc()=%d", i); UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_RUNASSOC, (LPARAM)i); } SetAppStartingCursor(pei->hwnd, FALSE); } DWORD CShellExecute::_FinalMapError(HINSTANCE UNALIGNED64 *phinst) { if (_err != ERROR_SUCCESS) { // REVIEW: if errWin32 == ERROR_CANCELLED, we may want to // set hInstApp to 42 so people who don't check the return // code properly won't put up bogus messages. We should still // return FALSE. But this won't help everything and we should // really evangelize the proper use of ShellExecuteEx. In fact, // if we do want to do this, we should do it in ShellExecute // only. (This will force new people to do it right.) // Map FNF for drives to something slightly more sensible. if (_err == ERROR_FILE_NOT_FOUND && PathIsRoot(_szFile) && !PathIsUNC(_szFile)) { // NB CD-Rom drives with disk missing will hit this. if ((DriveType(DRIVEID(_szFile)) == DRIVE_CDROM) || (DriveType(DRIVEID(_szFile)) == DRIVE_REMOVABLE)) _err = ERROR_NOT_READY; else _err = ERROR_BAD_UNIT; } SetLastError(_err); if (phinst) *phinst = _MapWin32ErrToHINST(_err); } else if (phinst) { if (!_hInstance) { *phinst = (HINSTANCE) 42; } else *phinst = _hInstance; ASSERT(ISSHELLEXECSUCCEEDED(*phinst)); } TraceMsg(TF_SHELLEXEC, "SHEX::FinalMapError() returning err = %d, hinst = %d", _err, _hInstance); return _err; } DWORD CShellExecute::Finalize(LPSHELLEXECUTEINFO pei) { ASSERT(!_fAsync || !(pei->fMask & SEE_MASK_NOCLOSEPROCESS)); if (!_fAsync && _pi.hProcess && _err == ERROR_SUCCESS && (pei->fMask & SEE_MASK_NOCLOSEPROCESS)) { // // change from win95 behavior - zekel 3-APR-98 // in win95 we would close the proces but return a handle. // the handle was invalid of course, but some crazy app could be // using this value to test for success. i am assuming that they // are using one of the other three ways to determine success, // and we can follow the spec and return NULL if we close it. // // PEIOUT - set the hProcess if they are going to use it. pei->hProcess = _pi.hProcess; _pi.hProcess = NULL; } // // NOTE: _FinalMapError() actually calls SetLastError() with our best error // if any win32 apis are called after this, they can reset LastError!! // return _FinalMapError(&(pei->hInstApp)); } // // Both the Reports return back TRUE if there was an error // or FALSE if it was a Success. // BOOL CShellExecute::_ReportWin32(DWORD err) { ASSERT(!_err); TraceMsg(TF_SHELLEXEC, "SHEX::ReportWin32 reporting err = %d", err); _err = err; return (err != ERROR_SUCCESS); } BOOL CShellExecute::_ReportHinst(HINSTANCE hinst) { ASSERT(!_hInstance); TraceMsg(TF_SHELLEXEC, "SHEX::ReportHinst reporting hinst = %d", hinst); if (ISSHELLEXECSUCCEEDED(hinst) || !hinst) { _hInstance = hinst; return FALSE; } else return _ReportWin32(_MapHINSTToWin32Err(hinst)); } typedef struct { DWORD errWin32; UINT se_err; } SHEXERR; // one to one errs // ERROR_FILE_NOT_FOUND SE_ERR_FNF 2 // file not found // ERROR_PATH_NOT_FOUND SE_ERR_PNF 3 // path not found // ERROR_ACCESS_DENIED SE_ERR_ACCESSDENIED 5 // access denied // ERROR_NOT_ENOUGH_MEMORY SE_ERR_OOM 8 // out of memory #define ISONE2ONE(e) (e == SE_ERR_FNF || e == SE_ERR_PNF || e == SE_ERR_ACCESSDENIED || e == SE_ERR_OOM) // no win32 mapping SE_ERR_DDETIMEOUT 28 // no win32 mapping SE_ERR_DDEBUSY 30 // but i dont see any places where this is returned. // before they became the win32 equivalent...ERROR_OUT_OF_PAPER or ERROR_READ_FAULT // now they become ERROR_DDE_FAIL. // but we wont preserve these errors in the pei->hInstApp #define ISUNMAPPEDHINST(e) (e == 28 || e == 30) // **WARNING** . ORDER is IMPORTANT. // if there is more than one mapping for an error, // (like SE_ERR_PNF) then the first const SHEXERR c_rgShexErrs[] = { {ERROR_SHARING_VIOLATION, SE_ERR_SHARE}, {ERROR_OUTOFMEMORY, SE_ERR_OOM}, {ERROR_BAD_PATHNAME,SE_ERR_PNF}, {ERROR_BAD_NETPATH,SE_ERR_PNF}, {ERROR_PATH_BUSY,SE_ERR_PNF}, {ERROR_NO_NET_OR_BAD_PATH,SE_ERR_PNF}, {ERROR_OLD_WIN_VERSION,10}, {ERROR_APP_WRONG_OS,12}, {ERROR_RMODE_APP,15}, {ERROR_SINGLE_INSTANCE_APP,16}, {ERROR_INVALID_DLL,20}, {ERROR_NO_ASSOCIATION,SE_ERR_NOASSOC}, {ERROR_DDE_FAIL,SE_ERR_DDEFAIL}, {ERROR_DDE_FAIL,SE_ERR_DDEBUSY}, {ERROR_DDE_FAIL,SE_ERR_DDETIMEOUT}, {ERROR_DLL_NOT_FOUND,SE_ERR_DLLNOTFOUND} }; DWORD CShellExecute::_MapHINSTToWin32Err(HINSTANCE hinst) { DWORD errWin32 = 0; UINT_PTR se_err = (UINT_PTR) hinst; ASSERT(se_err); ASSERT(!ISSHELLEXECSUCCEEDED(se_err)); // i actually handle these, but it used to be that these // became mutant win32s. now they will be lost // i dont think these occur anymore AssertMsg(!ISUNMAPPEDHINST(se_err), TEXT("SHEX::COMPATIBILITY SE_ERR = %d, Get ZekeL!!!"), se_err); if (ISONE2ONE(se_err)) { errWin32 = (DWORD) se_err; } else for (int i = 0; i < ARRAYSIZE(c_rgShexErrs) ; i++) { if (se_err == c_rgShexErrs[i].se_err) { errWin32= c_rgShexErrs[i].errWin32; break; } } ASSERT(errWin32); return errWin32; } HINSTANCE CShellExecute::_MapWin32ErrToHINST(UINT errWin32) { ASSERT(errWin32); UINT se_err = 0; if (ISONE2ONE(errWin32)) { se_err = errWin32; } else for (int i = 0; i < ARRAYSIZE(c_rgShexErrs) ; i++) { if (errWin32 == c_rgShexErrs[i].errWin32) { se_err = c_rgShexErrs[i].se_err; break; } } if (!se_err) { // NOTE legacy error handling - zekel - 20-NOV-97 // for any unhandled win32 errors, we default to ACCESS_DENIED se_err = SE_ERR_ACCESSDENIED; } return IntToHinst(se_err); } DWORD ShellExecuteNormal(LPSHELLEXECUTEINFO pei) { DWORD err; TraceMsg(TF_SHELLEXEC, "ShellExecuteNormal Using CShellExecute"); // WARNING Dont use up Stack Space // we allocate because of win16 stack issues // and the shex is a big object CShellExecute *shex = new CShellExecute(); if (shex) { shex->ExecuteNormal(pei); err = shex->Finalize(pei); shex->Release(); } else { pei->hInstApp = (HINSTANCE)SE_ERR_OOM; err = ERROR_OUTOFMEMORY; } TraceMsg(TF_SHELLEXEC, "ShellExecuteNormal returning win32 = %d, hinst = %d", err, pei->hInstApp); return err; } BOOL CShellExecute::Init(PSHCREATEPROCESSINFO pscpi) { TraceMsg(TF_SHELLEXEC, "SHEX::Init(pscpi)"); _SetMask(pscpi->fMask); _lpParameters= pscpi->pszParameters; // we always do "runas" _pszQueryVerb = _wszVerb; _cpt = pscpi->hUserToken ? CPT_ASUSER : CPT_WITHLOGON; if (pscpi->lpStartupInfo) { _nShow = pscpi->lpStartupInfo->wShowWindow; _startup = *(pscpi->lpStartupInfo); } else // require startupinfo return !(_ReportWin32(ERROR_INVALID_PARAMETER)); // // Copy the specified directory in _szWorkingDir if the working // directory is specified; otherwise, get the current directory there. // _SetWorkingDir(pscpi->pszCurrentDirectory); // // Copy the file name to _szFile, if it is specified. Then, // perform environment substitution. // _SetFile(pscpi->pszFile, FALSE); _pProcAttrs = pscpi->lpProcessAttributes; _pThreadAttrs = pscpi->lpThreadAttributes; _fInheritHandles = pscpi->bInheritHandles; _hUserToken = pscpi->hUserToken; // createflags already inited by _SetMask() just // add the users in. _dwCreateFlags |= pscpi->dwCreationFlags; _hwndParent = pscpi->hwnd; return TRUE; } void CShellExecute::ExecuteProcess(void) { SetAppStartingCursor(_hwndParent, TRUE); // // If the specified filename is a UNC path, validate it now. // if (STOPTRYING(_TryValidateUNC(_szFile, NULL, NULL))) goto Quit; LPCITEMIDLIST pidl; if (STOPTRYING(_Resolve(&pidl))) goto Quit; if (STOPTRYING(_InitAssociations(NULL, NULL))) goto Quit; // check to see if darwin is enabled on the machine if (STOPTRYING(_SetCmdTemplate(TRUE))) goto Quit; // At this point, the _szFile should have been determined one way // or another. ASSERT(_szFile[0] || _szCmdTemplate[0]); // do we have the necessary RegDB info to do an exec? _DoExecCommand(); Quit: if (_err == ERROR_SUCCESS && UEMIsLoaded()) { int i; // skip the call if stuff isn't there yet. // the load is expensive (forces ole32.dll and browseui.dll in // and then pins browseui). // however we ran the app (exec, dde, etc.), we succeeded. do our // best to guess the association etc. and log it. i = GetUEMAssoc(_szFile, _szApplication, NULL); TraceMsg(DM_MISC, "cse.e: GetUEMAssoc()=%d", i); UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_RUNASSOC, (LPARAM)i); } SetAppStartingCursor(_hwndParent, FALSE); } DWORD CShellExecute::Finalize(PSHCREATEPROCESSINFO pscpi) { if (!_fAsync && _pi.hProcess) { if (!(pscpi->fMask & SEE_MASK_NOCLOSEPROCESS)) { CloseHandle(_pi.hProcess); _pi.hProcess = NULL; CloseHandle(_pi.hThread); _pi.hThread = NULL; } if (_err == ERROR_SUCCESS && pscpi->lpProcessInformation) { *(pscpi->lpProcessInformation) = _pi; } } else if (pscpi->lpProcessInformation) ZeroMemory(pscpi->lpProcessInformation, sizeof(_pi)); // // NOTE: _FinalMapError() actually calls SetLastError() with our best error // if any win32 apis are called after this, they can reset LastError!! // return _FinalMapError(NULL); } SHSTDAPI_(BOOL) SHCreateProcessAsUserW(PSHCREATEPROCESSINFOW pscpi) { DWORD err; TraceMsg(TF_SHELLEXEC, "SHCreateProcess using CShellExecute"); // WARNING Don't use up Stack Space // we allocate because of win16 stack issues // and the shex is a big object CShellExecute *pshex = new CShellExecute(); if (pshex) { if (pshex->Init(pscpi)) pshex->ExecuteProcess(); err = pshex->Finalize(pscpi); pshex->Release(); } else err = ERROR_OUTOFMEMORY; TraceMsg(TF_SHELLEXEC, "SHCreateProcess returning win32 = %d", err); if (err != ERROR_SUCCESS) { _DisplayShellExecError(pscpi->fMask, pscpi->hwnd, pscpi->pszFile, NULL, err); SetLastError(err); } return err == ERROR_SUCCESS; } HINSTANCE APIENTRY WOWShellExecute( HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd, void *lpfnCBWinExec) { g_pfnWowShellExecCB = lpfnCBWinExec; if (!lpParameters) lpParameters = ""; HINSTANCE hinstRet = RealShellExecuteExA(hwnd, lpOperation, lpFile, lpParameters, lpDirectory, NULL, "", NULL, (WORD)nShowCmd, NULL, 0); g_pfnWowShellExecCB = NULL; return hinstRet; } void _ShellExec_RunDLL(HWND hwnd, HINSTANCE hAppInstance, LPCTSTR pszCmdLine, int nCmdShow) { TCHAR szQuotedCmdLine[MAX_PATH * 2]; SHELLEXECUTEINFO ei = {0}; ULONG fMask = SEE_MASK_FLAG_DDEWAIT; LPTSTR pszArgs; // Don't let empty strings through, they will endup doing something dumb // like opening a command prompt or the like if (!pszCmdLine || !*pszCmdLine) return; // // the flags are prepended to the command line like: // "?0x00000001?" "cmd line" // if (pszCmdLine[0] == TEXT('?')) { // these are the fMask flags int i; if (StrToIntEx(++pszCmdLine, STIF_SUPPORT_HEX, &i)) { fMask |= i; } pszCmdLine = StrChr(pszCmdLine, TEXT('?')); if (!pszCmdLine) return; pszCmdLine++; } // Gross, but if the process command fails, copy the command line to let // shell execute report the errors if (PathProcessCommand(pszCmdLine, szQuotedCmdLine, ARRAYSIZE(szQuotedCmdLine), PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1) StrCpyN(szQuotedCmdLine, pszCmdLine, SIZECHARS(szQuotedCmdLine)); pszArgs = PathGetArgs(szQuotedCmdLine); if (*pszArgs) *(pszArgs - 1) = 0; // Strip args PathUnquoteSpaces(szQuotedCmdLine); ei.cbSize = sizeof(SHELLEXECUTEINFO); ei.hwnd = hwnd; ei.lpFile = szQuotedCmdLine; ei.lpParameters = pszArgs; ei.nShow = nCmdShow; ei.fMask = fMask; // if shellexec() fails we want to pass back the error. if (!ShellExecuteEx(&ei)) { DWORD err = GetLastError(); if (InRunDllProcess()) ExitProcess(err); } } STDAPI_(void) ShellExec_RunDLLA(HWND hwnd, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow) { SHSTR str; if (SUCCEEDED(str.SetStr(pszCmdLine))) _ShellExec_RunDLL(hwnd, hAppInstance, str, nCmdShow); } STDAPI_(void) ShellExec_RunDLLW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR pszCmdLine, int nCmdShow) { SHSTR str; if (SUCCEEDED(str.SetStr(pszCmdLine))) _ShellExec_RunDLL(hwnd, hAppInstance, str, nCmdShow); }