execute.cpp
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 126k
Category:

Windows Kernel

Development Platform:

Visual C++

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