|
|
#include "shellprv.h"
#include "shlexec.h"
#include <newexe.h>
#include <appmgmt.h>
#include "ids.h"
#include <shstr.h>
#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 <wincrui.h> // for CredUIInitControls
#include <winsafer.h> // for ComputeAccessTokenFromCodeAuthzLevel, etc
#include <winsaferp.h> // for Saferi APIs
#include <softpub.h> // for WinVerifyTrust constants
#include <lmcons.h> // 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<HMONITOR>(_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); }
|