windows-nt/Source/XPSP1/NT/shell/shell32/execute.cpp
2020-09-26 16:20:57 +08:00

4045 lines
129 KiB
C++

#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()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
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 _SetCommand(void);
void _SetStartup(LPSHELLEXECUTEINFO pei);
void _SetImageName(void);
IBindCtx *_PerfBindCtx();
TRYRESULT _PerfPidl(LPCITEMIDLIST *ppidl);
// utility methods
HRESULT _QueryString(ASSOCF flags, ASSOCSTR str, LPTSTR psz, DWORD cch);
BOOL _CheckForRegisteredProgram(void);
TRYRESULT _ProcessErrorShouldTryExecCommand(DWORD err, HWND hwnd, BOOL fCreateProcessFailed);
HRESULT _BuildEnvironmentForNewProcess(LPCTSTR pszNewEnvString);
void _FixActivationStealingApps(HWND hwndOldActive, int nShow);
DWORD _GetCreateFlags(ULONG fMask);
BOOL _Resolve(void);
// 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 _TryOpenExe(void);
void _TryExecCommand(void);
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 _szImageName[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 _fTryOpenExe;
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
lstrcpy(_szUrl, pszUrlPart);
}
}
}
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
lstrcpy(_szFile, _szUrl);
}
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(void)
{
TCHAR szTemp[MAX_PATH];
TraceMsg(TF_SHELLEXEC, "SHEX::CFRP entered");
// Only supported for files with no paths specified
if (PathFindFileName(_szFile) != _szFile)
return FALSE;
if (PathToAppPath(_szFile, szTemp))
{
TraceMsg(TF_SHELLEXEC, "SHEX::CFRP Set szFile = %s", szTemp);
StrCpy(_szFile, szTemp);
return TRUE;
}
return FALSE;
}
BOOL CShellExecute::_Resolve(void)
{
// 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.
//
if (!_fNoResolve && !_fIsUrl && !_fIsNamespaceObject &&
!PathResolve(_szFile, rgszDirs, uFlags))
{
// _CheckForRegisteredProgram() changes _szFile if
// there is a registered program in the registry
// so we recheck to see if it exists.
if (!_CheckForRegisteredProgram() ||
!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;
}
}
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())
{
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");
}
if (KEEPTRYING(tr))
{
tr = _VerifyExecTrust(pei);
}
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))
_fTryOpenExe = TRUE;
else
_ReportWin32(ERROR_NO_ASSOCIATION);
}
return SUCCEEDED(hr) ? TRY_CONTINUE : TRY_STOP;
}
void CShellExecute::_TryOpenExe(void)
{
//
// this is the last chance that a file will have
// we shouldnt even be here in any case
// unless the registry has been thrashed, and
// the exe classes are all deleted from HKCR
//
ASSERT(PathIsExe(_szFile));
// even with no association, we know how to open an executable
if ((!_pszQueryVerb || !StrCmpIW(_pszQueryVerb, L"open")))
{
// _SetCommand() by hand here...
// NB WinExec can handle long names so there's no need to convert it.
StrCpy(_szCommand, _szFile);
//
// We need to append the parameter
//
if (_lpParameters && *_lpParameters)
{
StrCatBuff(_szCommand, c_szSpace, ARRAYSIZE(_szCommand));
StrCatBuff(_szCommand, _lpParameters, ARRAYSIZE(_szCommand));
}
TraceMsg(TF_SHELLEXEC, "SHEX::TryOpenExe() command = %s", _szCommand);
// _TryExecCommand() sets the fSucceeded if appropriate
_DoExecCommand();
}
else
{
TraceMsg(TF_SHELLEXEC, "SHEX::TryOpenExe() wrong verb");
_ReportWin32(ERROR_INVALID_PARAMETER);
}
}
TRYRESULT CShellExecute::_ProcessErrorShouldTryExecCommand(DWORD err, HWND hwnd, BOOL fCreateProcessFailed)
{
TRYRESULT tr = TRY_STOP;
// insure that we dont lose this error.
BOOL fNeedToReport = TRUE;
TraceMsg(TF_SHELLEXEC, "SHEX::PESTEC() enter : err = %d", err);
// special case some error returns
switch (err)
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_BAD_PATHNAME:
case ERROR_INVALID_NAME:
if ((_szCmdTemplate[0] != TEXT('%')) && fCreateProcessFailed)
{
UINT uAppType = LOWORD(GetExeType(_szImageName));
if ((uAppType == NEMAGIC))
{
}
else if (uAppType != PEMAGIC && !_fNoUI) // ie, it was not found
{
HKEY hk;
if (_pqa)
_pqa->GetKey(0, ASSOCKEY_CLASS, NULL, &hk);
else
hk = NULL;
//
// have user help us find missing exe
//
int iret = FindAssociatedExe(hwnd, _szCommand, ARRAYSIZE(_szCommand), _szFile, hk);
if (hk)
RegCloseKey(hk);
//
// We infinitely retry until either the user cancel it
// or we find it.
//
if (iret == -1)
{
tr = TRY_CONTINUE;
TraceMsg(TF_SHELLEXEC, "SHEX::PESTEC() found new exe");
}
else
_ReportWin32(ERROR_CANCELLED);
// either way we dont need to report this error
fNeedToReport = FALSE;
}
}
break;
} // switch (errWin32)
if (fNeedToReport)
_ReportWin32(err);
TraceMsg(TF_SHELLEXEC, "SHEX::PESTEC() return %d", tr);
return tr;
}
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 = S_FALSE;
_envblock.SetToken(_hUserToken);
// Use the _szTemp to build key to the programs specific
// key in the registry as well as other things...
PathToAppPathKey(_szImageName, _szTemp, SIZECHARS(_szTemp));
// 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)
{
lstrcatn(szFullName, TEXT("\\"), ARRAYSIZE(szFullName));
lstrcatn(szFullName, szName, ARRAYSIZE(szFullName));
}
else
{
// use just the username if we cannot get a domain name
lstrcpyn(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);
PathToAppPathKey(pszImageName, szAppPathKeyName, ARRAYSIZE(szAppPathKeyName));
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, LPCTSTR pszCommandLine, 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())
{
TCHAR szExePath[MAX_PATH];
lstrcpyn(szExePath, pszCommandLine, ARRAYSIZE(szExePath));
PathRemoveArgs(szExePath);
PathUnquoteSpaces(szExePath);
// ...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(szExePath))
{
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))
{
TCHAR szFullPathToApp[MAX_PATH];
// we want to disable runas on unc and net shares for now since the Administrative account might not
// have privlidges to the network path
lstrcpyn(szFullPathToApp, pszCommandLine, ARRAYSIZE(szFullPathToApp));
PathRemoveArgs(szFullPathToApp);
PathUnquoteSpaces(szFullPathToApp);
if (PathIsUNC(szFullPathToApp) || IsNetDrive(PathGetDriveNumber(szFullPathToApp)))
{
TraceMsg(TF_SHELLEXEC, "_SHCreateProcess: not prompting for runas install on unc/network path %s", szFullPathToApp);
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 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, lpCommandLine, &li);
}
if ((cpt == CPT_WITHLOGON || cpt == CPT_WITHLOGONADMIN) && lpApplicationName)
{
AssocQueryString(ASSOCF_VERIFY | ASSOCF_INIT_BYEXENAME, ASSOCSTR_FRIENDLYAPPNAME,
lpApplicationName, NULL, li.szAppName, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(li.szAppName)));
RetryUserLogon:
cpt = _LogonUser(hwnd, cpt, &li);
}
BOOL fRet = FALSE;
DWORD err = NOERROR;
switch(cpt)
{
case CPT_NORMAL:
{
// DEFAULT use CreateProcess
fRet = CreateProcess(NULL, 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, NULL, 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, NULL, 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(NULL, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles,
dwCreationFlags | CREATE_PRESERVE_CODE_AUTHZ_LEVEL,
lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
}
}
break;
case CPT_INSTALLTS:
{
HRESULT hr = InstallOnTerminalServerWithUIDD(hwnd,
NULL,
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, NULL, lpCommandLine,
dwCreationFlags, lpEnvironment, 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());
}
return fRet;
}
__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)
{
if (_szCmdTemplate[0])
{
// parse arguments into command line
DWORD se_err = ReplaceParameters(_szCommand, ARRAYSIZE(_szCommand),
_szFile, _szCmdTemplate, _lpParameters,
_nShow, NULL, FALSE, _lpID, &_pidlGlobal);
if (se_err)
_ReportHinst(IntToHinst(se_err));
else
{
return TRUE;
}
}
else if (PathIsExe(_szFile))
{
_fTryOpenExe = TRUE;
}
else
_ReportWin32(ERROR_NO_ASSOCIATION);
return FALSE;
}
void CShellExecute::_TryExecCommand(void)
{
TraceMsg(TF_SHELLEXEC, "SHEX::TryExecCommand() entered CmdTemplate = %s", _szCmdTemplate);
if (!_SetCommand())
return;
_DoExecCommand();
}
void CShellExecute::_SetImageName(void)
{
if (SUCCEEDED(_QueryString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, _szImageName, SIZECHARS(_szImageName))))
{
if (0 == lstrcmp(_szImageName, TEXT("%1")))
StrCpyN(_szImageName, _szFile, SIZECHARS(_szImageName));
}
else if (PathIsExe(_szFile))
{
StrCpyN(_szImageName, _szFile, SIZECHARS(_szImageName));
}
if (!_fInheritHandles && SHRestricted(REST_INHERITCONSOLEHANDLES))
{
_fInheritHandles = IsCurrentProcessConsole() && IsConsoleApp(_szImageName);
}
}
//
// 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)
{
BOOL fCreateProcessFailed;
TraceMsg(TF_SHELLEXEC, "SHEX::DoExecCommand() entered szCommand = %s", _szCommand);
do
{
HWND hwndOld = GetForegroundWindow();
LPTSTR pszEnv = NULL;
LPCTSTR pszNewEnvString = NULL;
fCreateProcessFailed = FALSE;
_SetImageName();
// Check exec restrictions.
if (SHRestricted(REST_RESTRICTRUN) && RestrictedApp(_szImageName))
{
_ReportWin32(ERROR_RESTRICTED_APP);
break;
}
if (SHRestricted(REST_DISALLOWRUN) && DisallowedApp(_szImageName))
{
_ReportWin32(ERROR_RESTRICTED_APP);
break;
}
// Check if app is incompatible in some fashion...
if (!CheckAppCompatibility(_szImageName, &pszNewEnvString, _fNoUI, _hwndParent))
{
_ReportWin32(ERROR_CANCELLED);
break;
}
// 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.
if (STOPTRYING(_TryValidateUNC(_szImageName, NULL, NULL)))
{
// returns TRUE if it failed or handled the operation
// Note that SHValidateUNC calls SetLastError
// this continue will test based on GetLastError()
continue;
}
//
// WOWShellExecute sets a global variable
// The cb is only valid when we are being called from wow
// If valid use it
//
if (STOPTRYING(_TryWowShellExec()))
break;
// 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,
_szImageName,
_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();
}
break; // out of retry loop
}
else
{
fCreateProcessFailed = TRUE;
}
// clean up the loop
if (pszEnv)
LocalFree(pszEnv);
// **WARNING** this assumes that SetLastError() has been called - zekel - 20-NOV-97
// right now we only reach here after CreateProcess() fails or
// SHValidateUNC() fails. both of these do SetLastError()
}
while (KEEPTRYING(_ProcessErrorShouldTryExecCommand(GetLastError(), _hwndParent, fCreateProcessFailed)));
// (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, _szImageName, 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);
lstrcpyA(psz, stra.GetStr());
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);
lstrcpy(psz, strTemp.GetStr());
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
PathToAppPathKey(_szFile, _szTemp, SIZECHARS(_szTemp));
return (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, _szTemp, TEXT("RunAsCommand"), NULL, _szCmdTemplate, &cb) && *_szCmdTemplate);
}
#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);
lstrcpy(szFileExt, pszExtPart);
// 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_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_PARSE_REQUIRE_REAL_NETWORK, NULL},
};
BindCtx_RegisterObjectParams(pbcFile, rgSimpleParams, ARRAYSIZE(rgSimpleParams), &pbc);
pbcFile->Release();
}
}
}
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;
if (FAILED(SHGetNameAndFlags(_lpID, 0, NULL, 0, &_sfgaoID)))
_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?
_TryExecCommand();
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;
// 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
#ifdef DEBUG
if (_fTryOpenExe)
TraceMsg(TF_WARNING, "SHEX - trying EXE with no Associations - %s", _szFile);
#endif // DEBUG
if (_fTryOpenExe)
_TryOpenExe();
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, _szImageName, _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;
if (STOPTRYING(_Resolve()))
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?
_TryExecCommand();
Quit:
//
// we should only see this if the registry is corrupted.
// but we still want to be able to open EXE's
RIP(!_fTryOpenExe);
if (_fTryOpenExe)
_TryOpenExe();
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, _szImageName, 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);
}