2020-09-30 17:12:32 +02:00

1982 lines
64 KiB
C

// Init the Cabinet (ie the top level browser).
// Includes...
#include "cabinet.h"
#include "rcids.h"
#include "cabwnd.h"
#include <regstr.h>
#include "startmnu.h"
#include <shdguid.h> // for IID_IShellService
#include <shlguid.h>
#include "..\shdocvw\winlist.h" // BUGBUG: get rid of this
#include <desktray.h>
#include <wininet.h>
#include <dbgmem.h>
#define DM_SHUTDOWN DM_TRACE // shutdown
// copied from desktop.cpp
#define PEEK_NORMAL 0
#define PEEK_QUIT 1
// on NT5 we need this event to be global so that it is shared between hydra
// sessions
#define SZ_SCMCREATEDEVENT_NT5 TEXT("Global\\ScmCreatedEvent")
#define SZ_SCMCREATEDEVENT TEXT("ScmCreatedEvent")
// exports from shdocvw.dll
STDAPI_(void) RunInstallUninstallStubs(void);
// from win32\kernel\utctime.c (private)
DWORD APIENTRY RefreshDaylightInformation(BOOL fChangeTime);
// shell32.dll exports, shelldll\binder.c
STDAPI_(void) SHFreeUnusedLibraries();
int ExplorerWinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPTSTR pszCmdLine, int nCmdShow);
//Do not change this stock5.lib use this as a BOOL not a bit.
BOOL g_bMirroredOS = FALSE;
HINSTANCE hinstCabinet = 0;
CRITICAL_SECTION g_csDll = { 0 };
HKEY g_hkeyExplorer = NULL;
BOOL g_fLogonCycle = FALSE;
BOOL g_fCleanShutdown = TRUE;
BOOL g_fExitExplorer = TRUE; // set to FALSE on WM_ENDSESSION shutdown case
BOOL g_fEndSession = FALSE; // set to TRUE if we rx a WM_ENDSESSION during RunOnce etc
BOOL g_fFakeShutdown = FALSE; // set to TRUE if we do Ctrl+Alt+Shift+Cancel shutdown
BOOL g_fRuningOnTerminalServer = FALSE; // Assume we are not running on Hydra
BOOL Cabinet_IsExplorerWindow(HWND hwnd)
{
TCHAR szClass[32];
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
return lstrcmpi(szClass, TEXT("ExploreWClass")) == 0;
}
// BUGBUG: does not account for "Browser" windows
BOOL Cabinet_IsFolderWindow(HWND hwnd)
{
TCHAR szClass[32];
GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
return lstrcmpi(szClass, TEXT("CabinetWClass")) == 0;
}
typedef enum {
RRA_DEFAULT = 0x0000,
RRA_DELETE = 0x0001, // delete each reg value when we're done with it
RRA_WAIT = 0x0002, // Wait for current item to finish before launching next item
RRA_SHELLSERVICEOBJECTS = 0x0004, // treat as a shell service object instead of a command sting
RRA_NOUI = 0x0008, // prevents ShellExecuteEx from displaying error dialogs
RRA_RUNSUBKEYS = 0x0010, // Run items in sub keys in alphabetical order
} RRA_FLAGS;
// The following handles running an application and optionally waiting for it
// to terminate.
void ShellExecuteRegApp(LPTSTR szCmdLine, RRA_FLAGS fFlags)
{
TCHAR szQuotedCmdLine[MAX_PATH+2];
SHELLEXECUTEINFO ei;
LPTSTR pszArgs;
// We used to call CreateProcess( NULL, szCmdLine, ...) here,
// but thats not useful for people with apppaths stuff.
// Don't let empty strings through, they will endup doing something dumb
// like opening a command prompt or the like
if (!szCmdLine || !*szCmdLine)
return;
// Gross, but if the process command fails, copy the command line to let
// shell execute report the errors
if (PathProcessCommand(szCmdLine, szQuotedCmdLine, ARRAYSIZE(szQuotedCmdLine), PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1)
lstrcpy(szQuotedCmdLine, szCmdLine);
pszArgs = PathGetArgs(szQuotedCmdLine);
if (*pszArgs)
*(pszArgs - 1) = 0; // Strip args
PathUnquoteSpaces(szQuotedCmdLine);
ei.cbSize = sizeof(SHELLEXECUTEINFO);
ei.hwnd = NULL;
ei.lpVerb = NULL;
ei.lpFile = szQuotedCmdLine;
ei.lpParameters = pszArgs;
ei.lpDirectory = NULL;
ei.nShow = SW_SHOWNORMAL;
ei.fMask = (fFlags & RRA_NOUI)?
(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI) : SEE_MASK_NOCLOSEPROCESS;
// Hydra comment:
// If we were sure that this func would not be called for anything but handling entries in RunOnce, then
// I would have out the system in in stall-mode and back to execute-mode right here.
// However, although I do not see this func being used by anything other than entries in RunOnce,
// there is no guaranty.
if (ShellExecuteEx(&ei))
{
if ( NULL != ei.hProcess)
{
if (fFlags & RRA_WAIT)
{
MsgWaitForMultipleObjectsLoop(ei.hProcess, INFINITE);
}
CloseHandle(ei.hProcess);
}
}
}
// The following code manages shell service objects. We load inproc dlls
// from the registry key and QI them for IOleCommandTarget. Note that all
// Shell Service Objects are loaded on the desktop thread.
// CGID_ShellServiceObject notifications are sent to these objects letting
// them know about shell status.
HDSA g_hdsaShellServiceObjects=NULL;
void LoadShellServiceObject(LPCTSTR szValueName, LPCTSTR szCmdLine, RRA_FLAGS fFlags)
{
SHELLSERVICEOBJECT sso = {0};
DebugMsg(DM_TRACE, TEXT("%s %s"), szValueName, szCmdLine);
if (!g_hdsaShellServiceObjects && !(g_hdsaShellServiceObjects = DSA_Create(sizeof(SHELLSERVICEOBJECT), 2)))
{
// Fail
return;
}
if (SHCLSIDFromString(szCmdLine, &sso.clsid) == S_OK)
{
if (SUCCEEDED(CoCreateInstance(&sso.clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, &IID_IOleCommandTarget, (void **)&sso.pct)))
{
lstrcpyn(sso.szName, szValueName, ARRAYSIZE(sso.szName));
if (DSA_AppendItem(g_hdsaShellServiceObjects, &sso) == -1)
{
DebugMsg( DM_ERROR, TEXT("Cannot add to dsa <%s>"), szValueName );
sso.pct->lpVtbl->Release(sso.pct);
}
}
}
}
// While we are doing our RunOnce processing, we want to change the
// system cursor. We can't use SetClassLong on the desktop class
// because that's illegal under Win32. And we can't use SetSystemCursor
// because on NT, CopyImage on a system cursor doesn't actually copy
// the cursor. It merely returns the original cursor handle back,
// which leaves us in a bit of a fix because the original cursor is
// about to be obliterated by SetSystemCursor!
// So instead, we create a full-virtual-screen window that always ducks
// to the bottom of the Z-order. We can't use SetShellWindow and make
// USER do the ducking for us, because RunOnce applets might get confused
// if they see a GetShellWindow before the shell is initialized.
// We can't use a shlwapi worker window because we need to make the
// class background brush COLOR_DESKTOP to ensure that we don't get
// ugly white flashes if the user quickly drags a window around our
// fake desktop.
// You'd think this would be easy, but there are a lot of subtleties.
LRESULT RegAppsWaitWndProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp)
{
switch (wm) {
case WM_WINDOWPOSCHANGING:
((LPWINDOWPOS)lp)->hwndInsertAfter = HWND_BOTTOM; // force to bottom
break; // proceed with default action
// Subtlety: Must erase background with PaintDesktop so user's
// wallpaper shows through.
// Double subtlety: Don't paint directly through the HDC that comes
// in via the wParam, because that HDC is >not clipped<. Consequently,
// you get horrible flickering since we repaint the entire desktop.
// Triple subtlety: You have to do this in the WM_ERASEBKGND handler,
// not the WM_PAINT handler, because MsgWaitForMultipleObjectsLoop
// waits only for QS_SENDMESSAGE and not QS_PAINT.
case WM_ERASEBKGND:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
PaintDesktop(ps.hdc);
EndPaint(hwnd, &ps);
}
return 0;
case WM_ENDSESSION:
g_fEndSession = (BOOL)wp;
break;
}
return DefWindowProc(hwnd, wm, wp, lp);
}
#define REGAPPSWAIT_CLASS TEXT("RegAppsWait")
HWND CreateRegAppsWaitWindow(void)
{
WNDCLASS wc;
// Should never try to do this if the main desktop is already up
ASSERT(!GetShellWindow());
wc.style = 0;
wc.lpfnWndProc = RegAppsWaitWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstCabinet;
wc.hIcon = 0;
// Doesn't matter what cursor we choose because NT USER will say
// "Stupid app not waiting for QS_POSTMESSAGE so I will always display
// an hourglass cursor." But do the right thing in case NT USER
// decides actually to pay attention to our class cursor.
wc.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
// Subtlety: Don't use (HBRUSH)(COLOR_DESKTOP+1). If the user
// drags a window across the desktop, USER will first paint the
// window with the class background brush and only later will
// actually send the WM_ERASEBKGND message. If the user's
// desktop wallpaper has a different color from COLOR_DESKTOP,
// you will see ugly flashing as the exposed bits are first
// painted in COLOR_DESKTOP, and then later with the wallpaper.
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = REGAPPSWAIT_CLASS;
RegisterClass(&wc);
return CreateWindow(
REGAPPSWAIT_CLASS, /* Class Name */
NULL, /* Title */
WS_POPUP | WS_CLIPCHILDREN | WS_VISIBLE,/* Style */
GetSystemMetrics(SM_XVIRTUALSCREEN), /* Position */
GetSystemMetrics(SM_YVIRTUALSCREEN), /* Position */
GetSystemMetrics(SM_CXVIRTUALSCREEN), /* Size */
GetSystemMetrics(SM_CYVIRTUALSCREEN), /* Size */
NULL, /* Parent */
NULL, /* Menu */
hinstCabinet, /* Instance */
0); /* Special parameters */
}
void DestroyRegAppsWaitWindow(HWND hwnd)
{
DestroyWindow(hwnd);
UnregisterClass(REGAPPSWAIT_CLASS, hinstCabinet);
}
BOOL RunRegAppsAndObjects(HKEY hkeyParent, LPCTSTR szSubkey, RRA_FLAGS fFlags)
{
HKEY hkey;
BOOL fShellInit = FALSE;
if (RegOpenKey(hkeyParent, szSubkey, &hkey) == ERROR_SUCCESS)
{
DWORD cbData, cbValue, dwType, i;
TCHAR szValueName[80], szCmdLine[MAX_PATH];
// BUGBUG: should we do this in retail too or is it too scary :)
#ifdef DEBUG
// we only support named values so explicitly purge default values
cbData = SIZEOF(szCmdLine);
if (RegQueryValue(hkey, NULL, szCmdLine, &cbData) == ERROR_SUCCESS)
{
AssertMsg((cbData <= 2), TEXT("BOGUS default entry in <%s> '%s'"), szSubkey, szCmdLine);
RegDeleteValue(hkey, NULL);
}
#endif
if ( fFlags & RRA_RUNSUBKEYS )
{
// run the contents of each sub key. Keys must be run in alphabetical order. The sub keys
// are used to prioritize the launching of items. We only run one level of subkey so we need
// to turn off the subkey flag.
RRA_FLAGS fSubkeyFlags = fFlags & ~(RRA_RUNSUBKEYS);
for (i=0; !g_fEndSession ; i++)
{
LONG lEnum;
cbValue = ARRAYSIZE(szValueName);
// BUGBUG: This is a dirty assumption. This assumes several unsafe things:
// 1.) none of the programs being run will changes this section of the registry.
// 2.) NT enumerates registry items in alphabetic order.
// Since it isn't safe to make assumption #1 this needs to be made more robust.
lEnum = RegEnumKey( hkey, i, szValueName, cbValue );
if( ERROR_MORE_DATA == lEnum )
{
// ERROR_MORE_DATA means the value name or data was too large.
// Skip to the next item.
DebugMsg( DM_ERROR, TEXT("Explorer: RunRegAppsAndObjects cannot run oversize entry in <%s> '%s'"), szSubkey, szValueName );
continue;
}
else if ( ERROR_SUCCESS != lEnum )
{
// could be ERROR_NO_MORE_ENTRIES, or some kind of failure
// we can't recover from any other registry problem, anyway
break;
}
RunRegAppsAndObjects( hkey, szValueName, fSubkeyFlags );
if ( fFlags & RRA_DELETE )
{
// delete the sub key.
SHDeleteKey( hkey, szValueName );
i--;
}
}
}
else
{
// now enumerate all of the values.
for (i = 0; !g_fEndSession ; i++)
{
LONG lEnum;
cbValue = ARRAYSIZE(szValueName);
cbData = SIZEOF(szCmdLine);
lEnum = RegEnumValue( hkey, i, szValueName, &cbValue, NULL, &dwType, (LPBYTE)szCmdLine, &cbData );
if( ERROR_MORE_DATA == lEnum )
{
// ERROR_MORE_DATA means the value name or data was too large
// skip to the next item
DebugMsg( DM_ERROR, TEXT("Cannot run oversize entry in <%s>"), szSubkey );
continue;
}
else if ( lEnum != ERROR_SUCCESS )
{
// could be ERROR_NO_MORE_ENTRIES, or some kind of failure
// we can't recover from any other registry problem, anyway
break;
}
if (dwType == REG_SZ)
{
DebugMsg(DM_TRACE, TEXT("%s %s"), szSubkey, szCmdLine);
// only run things marked with a "*" in clean boot
if (g_fCleanBoot && (szValueName[0] != TEXT('*')))
continue;
// NB Things marked with a '!' mean delete after
// the CreateProcess not before. This is to allow
// certain apps (runonce.exe) to be allowed to rerun
// to if the machine goes down in the middle of execing
// them. Be very afraid of this switch.
if ((fFlags & RRA_DELETE) && (szValueName[0] != TEXT('!')))
{
// This delete can fail if the user doesn't have the privilege
if (RegDeleteValue(hkey, szValueName) == ERROR_SUCCESS)
{
// adjust for shift in value index only if delete succeeded
i--;
}
}
if (fFlags & RRA_SHELLSERVICEOBJECTS)
LoadShellServiceObject(szValueName, szCmdLine, fFlags);
else
{
#ifdef WINNT
// Hydra-Specific stuff
BOOL hydraInAppInstallMode = FALSE;
// In here, We only put the Hydra server in app-install-mode if RunOnce entries are
// being processed
if (!lstrcmpi(szSubkey, REGSTR_PATH_RUNONCE))
{
// See if we are on NT5, and if the terminal-services is enabled
if (g_fRuningOnTerminalServer)
{
if (hydraInAppInstallMode = SetTermsrvAppInstallMode(TRUE))
{
fFlags |= RRA_WAIT; // Changing timing blows up IE 4.0, but IE5 is ok!
}
}
}
#endif // WINNT
ShellExecuteRegApp(szCmdLine, fFlags);
#ifdef WINNT
// Hydra-Specific stuff
if (hydraInAppInstallMode)
{
SetTermsrvAppInstallMode(FALSE);
}
#endif // WINNT
}
// Post delete '!' things.
if ((fFlags & RRA_DELETE) && (szValueName[0] == TEXT('!'))) {
// This delete can fail if the user doesn't have the privilege
if (RegDeleteValue(hkey, szValueName) == ERROR_SUCCESS)
{
// adjust for shift in value index only if delete succeeded
i--; // adjust for shift in value index
}
}
}
}
}
RegCloseKey(hkey);
}
// if we rx'd a WM_ENDSESSION whilst running any of these keys we must exit the
// process.
if ( g_fEndSession )
ExitProcess(0);
return fShellInit;
}
BOOL LoadShellServiceObjects(HKEY hkeyParent, LPCTSTR szSubkey)
{
return RunRegAppsAndObjects(hkeyParent, szSubkey, RRA_SHELLSERVICEOBJECTS);
}
// clsid - NULL, send to everyone
// else restrict cmd to given class.
void CTExecShellServiceObjects(const CLSID *pclsid, DWORD nCmdID, DWORD nCmdexecopt, DWORD flags)
{
int iCount;
int i;
int iEnd, iStart;
HDSA hdsaShellServiceObjects;
if (!g_hdsaShellServiceObjects)
{
return;
}
// We use a temp variable to protect agains re-entancy (eg netshell calls PeekMessage during the
// Exec callback). Basically this is single threaded since the tray thread is the only guy who ever
// calls this function, but we have to be careful that we dont re-enter ourself.
hdsaShellServiceObjects = g_hdsaShellServiceObjects;
g_hdsaShellServiceObjects = NULL;
iCount = DSA_GetItemCount(hdsaShellServiceObjects);
if (iCount)
{
// Loop through all shell service objects and send the command target the
// command id.
if (flags & CTEXECSSOF_REVERSE)
{
iStart = iCount-1;
iEnd = 0-1;
}
else
{
iStart = 0;
iEnd = iCount;
}
for (i=iStart; i != iEnd; (flags & CTEXECSSOF_REVERSE ? i-- : i++))
{
PSHELLSERVICEOBJECT psso = (PSHELLSERVICEOBJECT)DSA_GetItemPtr(hdsaShellServiceObjects, i);
if (!pclsid || IsEqualGUID(&psso->clsid, pclsid))
{
psso->pct->lpVtbl->Exec(psso->pct, &CGID_ShellServiceObject, nCmdID, nCmdexecopt, NULL, NULL);
if (nCmdID==SSOCMDID_CLOSE)
psso->pct->lpVtbl->Release(psso->pct);
}
}
}
g_hdsaShellServiceObjects = hdsaShellServiceObjects;
}
void CreateShellDirectories()
{
TCHAR szPath[MAX_PATH];
// Create the shell directories if they don't exist
SHGetSpecialFolderPath(NULL, szPath, CSIDL_DESKTOPDIRECTORY, TRUE);
SHGetSpecialFolderPath(NULL, szPath, CSIDL_PROGRAMS, TRUE);
SHGetSpecialFolderPath(NULL, szPath, CSIDL_STARTMENU, TRUE);
SHGetSpecialFolderPath(NULL, szPath, CSIDL_STARTUP, TRUE);
SHGetSpecialFolderPath(NULL, szPath, CSIDL_RECENT, TRUE);
SHGetSpecialFolderPath(NULL, szPath, CSIDL_FAVORITES, TRUE);
}
// returns:
// TRUE if the user wants to abort the startup sequence
// FALSE keep going
// note: this is a switch, once on it will return TRUE to all
// calls so these keys don't need to be pressed the whole time
BOOL AbortStartup()
{
static BOOL bAborted = FALSE; // static so it sticks!
// DebugMsg(DM_TRACE, "Abort Startup?");
if (bAborted)
return TRUE; // don't do funky startup stuff
else {
bAborted = (g_fCleanBoot || ((GetAsyncKeyState(VK_CONTROL) < 0) || (GetAsyncKeyState(VK_SHIFT) < 0)));
return bAborted;
}
}
// BUGBUG: hwndOwner is no longer used (NULL is passed in) remove the hwndOwner code
// once we are certain that this isn't needed
BOOL EnumFolder_Startup(IShellFolder * psf, HWND hwndOwner, LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
LPCONTEXTMENU pcm;
HRESULT hres;
// MSG msg;
hres = psf->lpVtbl->GetUIObjectOf(psf, hwndOwner, 1, &pidlItem, & IID_IContextMenu, NULL, &pcm);
if (SUCCEEDED(hres))
{
HMENU hmenu = CreatePopupMenu();
if (hmenu)
{
#define CMD_ID_FIRST 1
#define CMD_ID_LAST 0x7fff
INT idCmd;
pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0, CMD_ID_FIRST, CMD_ID_LAST, CMF_DEFAULTONLY);
idCmd = GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0);
if (idCmd)
{
CMINVOKECOMMANDINFOEX ici;
ZeroMemory(&ici, SIZEOF(ici));
ici.cbSize = SIZEOF(ici);
ici.hwnd = hwndOwner;
ici.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - 1);
ici.nShow = SW_NORMAL;
pcm->lpVtbl->InvokeCommand(pcm, (LPCMINVOKECOMMANDINFO)&ici);
}
DestroyMenu(hmenu);
}
pcm->lpVtbl->Release(pcm);
}
if (AbortStartup())
return FALSE;
return TRUE;
}
// BUGBUG: hwndOwner is no longer used (NULL is passed in) remove the hwndOwner code
// once we are certain that this isn't needed
void EnumFolder(HWND hwndOwner, LPITEMIDLIST pidlFolder, DWORD grfFlags, PFNENUMFOLDERCALLBACK pfn)
{
IShellFolder *psf = BindToFolder(pidlFolder);
if (psf)
{
LPENUMIDLIST penum;
HRESULT hres = psf->lpVtbl->EnumObjects(psf, hwndOwner, grfFlags, &penum);
if (SUCCEEDED(hres))
{
LPITEMIDLIST pidl;
UINT celt;
while (penum->lpVtbl->Next(penum, 1, &pidl, &celt)==NOERROR && celt==1)
{
if (!pfn(psf, hwndOwner, pidlFolder, pidl))
{
SHFree(pidl);
break;
}
SHFree(pidl);
}
penum->lpVtbl->Release(penum);
}
psf->lpVtbl->Release(psf);
}
}
// BUGBUG: hwndOwner is no longer used (NULL is passed in) remove the hwndOwner code
// once we are certain that this isn't needed
void _ExecuteStartupPrograms(HWND hwndOwner)
{
LPITEMIDLIST pidlStartup;
if (AbortStartup())
return;
pidlStartup = SHCloneSpecialIDList(NULL, CSIDL_COMMON_STARTUP, TRUE);
if (pidlStartup)
{
EnumFolder(hwndOwner, pidlStartup, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, EnumFolder_Startup);
ILFree(pidlStartup);
}
// Execute non-localized "Common StartUp" group if exists.
pidlStartup = SHCloneSpecialIDList(NULL, CSIDL_COMMON_ALTSTARTUP, FALSE);
if (pidlStartup)
{
EnumFolder(hwndOwner, pidlStartup, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, EnumFolder_Startup);
ILFree(pidlStartup);
}
pidlStartup = SHCloneSpecialIDList(NULL, CSIDL_STARTUP, TRUE);
if (pidlStartup)
{
EnumFolder(hwndOwner, pidlStartup, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, EnumFolder_Startup);
ILFree(pidlStartup);
}
// Execute non-localized "StartUp" group if exists.
pidlStartup = SHCloneSpecialIDList(NULL, CSIDL_ALTSTARTUP, FALSE);
if (pidlStartup)
{
EnumFolder(hwndOwner, pidlStartup, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, EnumFolder_Startup);
ILFree(pidlStartup);
}
}
// BUGBUG:: A bunch of this code can get reduced down... ie boiled
const LPTSTR c_aszForceCheckWinIni[] = {TEXT("GROUPS.B$$"), NULL};
void CheckWinIniForAssocs(void);
void BoilThatDustSpec(LPTSTR pStart, int nCmdShow)
{
BOOL bFinished;
bFinished = FALSE;
while (!bFinished && !AbortStartup())
{
SHELLEXECUTEINFO ei;
LPTSTR pEnd;
pEnd = pStart;
while ((*pEnd) && (*pEnd != TEXT(' ')) && (*pEnd != TEXT(',')))
pEnd = (LPTSTR)OFFSETOF(CharNext(pEnd));
if (*pEnd == 0)
bFinished = TRUE;
else
*pEnd = 0;
if (lstrlen(pStart) != 0)
{
LPTSTR pszFile;
TCHAR szFile[MAX_PATH];
const LPTSTR *ppszForce;
// Load and Run lines are done relative to windows directory.
GetWindowsDirectory(szFile, ARRAYSIZE(szFile));
SetCurrentDirectory(szFile);
pszFile = PathFindFileName(pStart);
lstrcpy(szFile, pszFile);
PathRemoveFileSpec(pStart);
// App hacks to get borlands Setup program to work
for (ppszForce = c_aszForceCheckWinIni; *ppszForce; ppszForce++)
{
if (lstrcmpi(szFile, *ppszForce) == 0)
{
DebugMsg(DM_TRACE, TEXT("c.boil: Apphack %s force winini scan"), szFile);
CheckWinIniForAssocs();
break;
}
}
ei.cbSize = sizeof(SHELLEXECUTEINFO);
ei.hwnd = NULL;
ei.lpVerb = NULL;
ei.lpFile = szFile;
ei.lpParameters = NULL;
ei.lpDirectory = pStart;
ei.nShow = nCmdShow;
ei.fMask = 0;
if (!ShellExecuteEx(&ei))
{
ShellMessageBox(hinstCabinet, NULL, MAKEINTRESOURCE(IDS_WINININORUN), MAKEINTRESOURCE(IDS_DESKTOP), MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL, (LPTSTR)szFile);
}
}
pStart = pEnd+1;
}
}
void _DoRunEquals()
{
TCHAR szBuffer[255]; // max size of load= run= lines...
if (g_fCleanBoot)
return;
/* "Load" apps before "Run"ning any. */
GetProfileString(TEXT("windows"), TEXT("Load"), TEXT(""), szBuffer, ARRAYSIZE(szBuffer));
if (*szBuffer)
BoilThatDustSpec(szBuffer, SW_SHOWMINNOACTIVE);
GetProfileString(TEXT("windows"), TEXT("Run"), TEXT(""), szBuffer, ARRAYSIZE(szBuffer));
if (*szBuffer)
BoilThatDustSpec(szBuffer, SW_SHOWNORMAL);
}
// Use IERnonce.dll to process RunOnceEx key
typedef void (WINAPI *RUNONCEEXPROCESS)(HWND, HINSTANCE, LPSTR, int);
void ProcessRunOnceEx()
{
HKEY hkey;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCEEX, 0, KEY_QUERY_VALUE, &hkey))
{
DWORD dwNumSubKeys = 0;
RegQueryInfoKey(hkey, NULL, NULL, NULL, &dwNumSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
RegCloseKey(hkey);
if (dwNumSubKeys)
{
HANDLE hLib;
TCHAR szPath[MAX_PATH];
GetSystemDirectory(szPath, ARRAYSIZE(szPath));
PathAppend(szPath, TEXT("iernonce.dll"));
hLib = LoadLibrary(szPath);
if (hLib)
{
RUNONCEEXPROCESS pfnRunOnceExProcess;
#ifdef WINNT // Hydra-Specific
BOOL hydraInAppInstallMode = FALSE;
// See if we are on NT5, and if the terminal-services is enabled
if (g_fRuningOnTerminalServer)
{
hydraInAppInstallMode = SetTermsrvAppInstallMode(TRUE);
}
#endif // WINNT
pfnRunOnceExProcess = (RUNONCEEXPROCESS)GetProcAddress(hLib, "RunOnceExProcess");
if (pfnRunOnceExProcess)
{
// the four param in the function is due to the function cab be called
// from RunDLL which will path in those params. But RunOnceExProcess ignore all
// of them. Therefore, I don't pass any meaningful thing here.
pfnRunOnceExProcess(NULL, NULL, NULL, 0);
}
FreeLibrary(hLib);
#ifdef WINNT // Hydra-Specific
if (hydraInAppInstallMode)
{
SetTermsrvAppInstallMode(FALSE) ;
}
#endif // WINNT
}
}
}
}
#define REGTIPS REGSTR_PATH_EXPLORER TEXT("\\Tips")
#define SZ_REGKEY_RUNSRVWIZ TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\Welcome")
// BUGBUG: This can be removed as soon as the policy editor and setup are updated to
// place welcome under one of the prioritized HKCU\Run subkeys.
UINT _RunWelcome()
{
HKEY hkey;
BOOL fShow = FALSE;
UINT uPeek = PEEK_NORMAL;
if (RegOpenKey(HKEY_CURRENT_USER, REGTIPS, &hkey) == ERROR_SUCCESS)
{
DWORD cbData = SIZEOF(fShow);
RegQueryValueEx(hkey, TEXT("Show"), NULL, NULL, (LPBYTE)&fShow, &cbData);
RegCloseKey(hkey);
}
if (fShow)
{
DWORD dwType;
DWORD dwData;
DWORD cbSize = sizeof(dwData);
TCHAR szCmdLine[MAX_PATH * 2];
PROCESS_INFORMATION pi;
STARTUPINFO startup = {0};;
startup.cb = SIZEOF(startup);
startup.wShowWindow = SW_SHOWNORMAL;
if ( IsOS(OS_WIN2000PRO) || IsOS(OS_WINDOWS) )
{
// Only run welcome.exe if we are on Professional (or win9x)
GetWindowsDirectory(szCmdLine, ARRAYSIZE(szCmdLine));
PathAppend(szCmdLine, TEXT("Welcome.exe"));
}
else if ( (IsOS(OS_WIN2000SERVER) || IsOS(OS_WIN2000ADVSERVER)) && IsUserAnAdmin() &&
((ERROR_SUCCESS != SHGetValue(HKEY_CURRENT_USER, SZ_REGKEY_RUNSRVWIZ, TEXT("SrvWiz"), &dwType, (LPBYTE)&dwData, &cbSize)) || (dwData != 0)))
{
// launch Configure Your Server for system administrators on Win2000 Server and Advanced Server
GetSystemDirectory(szCmdLine, ARRAYSIZE(szCmdLine));
PathAppend(szCmdLine, TEXT("mshta.exe res://srvwiz.dll/default.hta"));
}
else
{
// If neither or the above are true don't try to run anything.
return 0;
}
if (CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &pi))
{
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
return uPeek;
}
#ifdef WINNT
// On NT, run the TASKMAN= line from the registry
void _AutoRunTaskMan(void)
{
HKEY hkeyWinLogon;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"), 0, KEY_READ, &hkeyWinLogon) == ERROR_SUCCESS)
{
TCHAR szBuffer[MAX_PATH];
DWORD cbBuffer = SIZEOF(szBuffer);
if (RegQueryValueEx(hkeyWinLogon, TEXT("Taskman"), 0, NULL, (LPBYTE)szBuffer, &cbBuffer) == ERROR_SUCCESS)
{
if (szBuffer[0])
{
PROCESS_INFORMATION pi;
STARTUPINFO startup = {0};
startup.cb = SIZEOF(startup);
startup.wShowWindow = SW_SHOWNORMAL;
if (CreateProcess(NULL, szBuffer, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &pi))
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
}
RegCloseKey(hkeyWinLogon);
}
}
#else
#define _AutoRunTaskMan() // nothing on Win95
#endif
#ifdef DEBUG
// Copy the exception info so we can get debug info for Raised exceptions
// which don't go through the debugger.
void _CopyExceptionInfo(LPEXCEPTION_POINTERS pep)
{
PEXCEPTION_RECORD per;
per = pep->ExceptionRecord;
DebugMsg(DM_ERROR, TEXT("Exception %x at %#08x."), per->ExceptionCode, per->ExceptionAddress);
if (per->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// If the first param is 1 then this was a write.
// If the first param is 0 then this was a read.
if (per->ExceptionInformation[0])
{
DebugMsg(DM_ERROR, TEXT("Invalid write to %#08x."), per->ExceptionInformation[1]);
}
else
{
DebugMsg(DM_ERROR, TEXT("Invalid read of %#08x."), per->ExceptionInformation[1]);
}
}
}
#else
#define _CopyExceptionInfo(x) TRUE
#endif
// try to create this by sending a wm_command directly to
// the desktop.
BOOL MyCreateFromDesktop(HINSTANCE hInst, LPCTSTR pszCmdLine, int nCmdShow)
{
NEWFOLDERINFO fi = {0};
BOOL bRet = FALSE;
fi.nShow = nCmdShow;
// since we have browseui fill out the fi,
// SHExplorerParseCmdLine() does a GetCommandLine()
if (SHExplorerParseCmdLine(&fi))
bRet = SHCreateFromDesktop(&fi);
// should we also have it cleanup after itself??
// SHExplorerParseCmdLine() can allocate this buffer...
if (fi.uFlags & COF_PARSEPATH)
LocalFree(fi.pszPath);
ILFree(fi.pidl);
ILFree(fi.pidlRoot);
return bRet;
}
BOOL g_fDragFullWindows=FALSE;
int g_cxEdge=0;
int g_cyEdge=0;
int g_cySize=0;
int g_cxTabSpace=0;
int g_cyTabSpace=0;
int g_cxBorder=0;
int g_cyBorder=0;
int g_cxPrimaryDisplay=0;
int g_cyPrimaryDisplay=0;
int g_cxDlgFrame=0;
int g_cyDlgFrame=0;
int g_cxFrame=0;
int g_cyFrame=0;
int g_cxMinimized=0;
int g_fCleanBoot=0;
int g_cxVScroll=0;
int g_cyHScroll=0;
void Cabinet_InitGlobalMetrics(WPARAM wParam, LPTSTR lpszSection)
{
BOOL fForce = (!lpszSection || !*lpszSection);
if (fForce || wParam == SPI_SETDRAGFULLWINDOWS) {
SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &g_fDragFullWindows, 0);
}
if (fForce || !lstrcmpi(lpszSection, TEXT("WindowMetrics")) || wParam == SPI_SETNONCLIENTMETRICS) {
// REVIEW, before it's all over, make sure all these vars are used somewhere.
g_cxEdge = GetSystemMetrics(SM_CXEDGE);
g_cyEdge = GetSystemMetrics(SM_CYEDGE);
#ifdef NASHFLAT_TASKBAR
g_cxTabSpace = (g_cxEdge * 5);
#else
g_cxTabSpace = (g_cxEdge * 3) / 2;
#endif
g_cyTabSpace = (g_cyEdge * 3) / 2; // cause the graphic designers really really want 3.
g_cySize = GetSystemMetrics(SM_CYSIZE);
g_cxBorder = GetSystemMetrics(SM_CXBORDER);
g_cyBorder = GetSystemMetrics(SM_CYBORDER);
g_cxVScroll = GetSystemMetrics(SM_CXVSCROLL);
g_cyHScroll = GetSystemMetrics(SM_CYHSCROLL);
g_cxDlgFrame = GetSystemMetrics(SM_CXDLGFRAME);
g_cyDlgFrame = GetSystemMetrics(SM_CYDLGFRAME);
g_cxFrame = GetSystemMetrics(SM_CXFRAME);
g_cyFrame = GetSystemMetrics(SM_CYFRAME);
g_cxMinimized = GetSystemMetrics(SM_CXMINIMIZED);
g_cxPrimaryDisplay = GetSystemMetrics(SM_CXSCREEN);
g_cyPrimaryDisplay = GetSystemMetrics(SM_CYSCREEN);
}
}
void _CreateAppGlobals()
{
Cabinet_InitGlobalMetrics(0, NULL);
#ifdef WINNT
g_bRunOnNT5 = BOOLIFY(IsOS(OS_NT5));
#else
g_bRunOnMemphis = BOOLIFY(IsOS(OS_MEMPHIS));
#endif
// Check if the mirroring APIs exist on the current
// platform.
g_bMirroredOS = IS_MIRRORING_ENABLED();
// make sure we got our #defines right...
ASSERT(BOOLIFY(g_bRunOnNT) == BOOLIFY(IsOS(OS_NT)));
ASSERT(BOOLIFY(g_bRunOnNT5) == BOOLIFY(IsOS(OS_NT5)));
ASSERT(BOOLIFY(g_bRunOnMemphis) == BOOLIFY(IsOS(OS_MEMPHIS)));
}
// This function checks if any of the shell windows is already created by
// another instance of explorer and returns TRUE if so.
BOOL IsAnyShellWindowAlreadyPresent()
{
return GetShellWindow() || FindWindow(TEXT("Proxy Desktop"), NULL);
}
// See if the Shell= line indicates that we are the shell
BOOL ExplorerIsShell()
{
TCHAR *pszPathName, szPath[MAX_PATH];
TCHAR *pszModuleName, szModulePath[MAX_PATH];
ASSERT(!IsAnyShellWindowAlreadyPresent());
GetModuleFileName(NULL, szModulePath, ARRAYSIZE(szModulePath));
pszModuleName = PathFindFileName(szModulePath);
GetPrivateProfileString(TEXT("boot"), TEXT("shell"), pszModuleName, szPath, ARRAYSIZE(szPath), TEXT("system.ini"));
PathRemoveArgs(szPath);
PathRemoveBlanks(szPath);
pszPathName = PathFindFileName(szPath);
// NB Special case shell=install.exe - assume we are the shell.
// Symantec un-installers temporarily set shell=installer.exe so
// we think we're not the shell when we are. They fail to clean up
// a bunch of links if we don't do this.
return StrCmpNI(pszPathName, pszModuleName, lstrlen(pszModuleName)) == 0 ||
lstrcmpi(pszPathName, TEXT("install.exe")) == 0;
}
// Returns TRUE of this is the first time the explorer is run
BOOL ShouldStartDesktopAndTray()
{
// We need to be careful on which window we look for. If we look for
// our desktop window class and Progman is running we will find the
// progman window. So Instead we should ask user for the shell window.
// We can not depend on any values being set here as this is the
// start of a new process. This wont be called when we start new
// threads.
return !IsAnyShellWindowAlreadyPresent() && ExplorerIsShell();
}
void DisplayCleanBootMsg()
{
TCHAR szMsg[1024];
TCHAR szTitle[80];
int ids;
int cb;
LPTSTR pszMsg = szMsg;
szMsg[0] = TEXT('\0');
for (ids=IDS_CLEANBOOTMSG1; ids <= IDS_CLEANBOOTMSG4 ; ids++)
{
cb = LoadString(hinstCabinet, ids, pszMsg,
ARRAYSIZE(szMsg) - (int)(pszMsg - szMsg));
if (cb == 0)
break;
pszMsg += cb;
}
// Make sure it is NULL terminated
*pszMsg = TEXT('\0');
LoadString(hinstCabinet, IDS_DESKTOP, szTitle, ARRAYSIZE(szTitle));
// Now display the message.
MessageBox(NULL, szMsg, szTitle, MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
}
const CHAR c_szTimeChangedRunDLL[] = "rundll32 shell32.dll,Control_RunDLL timedate.cpl,,/m";
void DoDaylightCheck(BOOL fStartupInit)
{
DWORD changed;
DebugMsg(DM_TRACE, TEXT("c.ddc(%d): calling k32.rdi"), fStartupInit);
#ifdef WINNT
changed = FALSE;
#else
// Win95 base does not automatically handle timezone cutover
// we have poke it every so often...
changed = RefreshDaylightInformation(TRUE);
#endif
if (changed > 0)
{
DebugMsg(DM_TRACE, TEXT("c.ddc(%d): rdi changed - %lu"), fStartupInit, changed);
// something actually changed, tell everbody
if (!fStartupInit)
{
SendMessage((HWND)-1, WM_TIMECHANGE, 0, 0);
// if the local time changed tell the user
if (changed > 1)
WinExec(c_szTimeChangedRunDLL, SW_SHOWNORMAL);
}
else
{
// there should only be "server" processes around anyway
PostMessage((HWND)-1, WM_TIMECHANGE, 0, 0);
// if the local time changed queue a runonce to tell the user
if (changed > 1)
{
HKEY runonce;
if (RegCreateKey(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE, &runonce) == ERROR_SUCCESS)
{
RegSetValueEx(runonce, TEXT("WarnTimeChanged"), 0, REG_SZ, (LPBYTE)c_szTimeChangedRunDLL, SIZEOF(c_szTimeChangedRunDLL));
RegCloseKey(runonce);
}
}
}
}
}
BOOL IsExecCmd(LPCTSTR pszCmd)
{
return *pszCmd && !StrStrI(pszCmd, TEXT("-embedding"));
}
// run the cmd line passed up from win.com
void _RunWinComCmdLine(LPCTSTR pszCmdLine, UINT nCmdShow)
{
if (IsExecCmd(pszCmdLine))
{
SHELLEXECUTEINFO ei = { SIZEOF(ei), 0, NULL, NULL, pszCmdLine, NULL, NULL, nCmdShow};
ei.lpParameters = PathGetArgs(pszCmdLine);
if (*ei.lpParameters)
*((LPTSTR)ei.lpParameters - 1) = 0; // const -> non const
ShellExecuteEx(&ei);
}
}
// stolen from the CRT, used to shirink our code
#ifdef DEBUG // For leak detection
BOOL g_fInitTable = FALSE;
LEAKDETECTFUNCS LeakDetFunctionTable;
#endif
// ccover uses runtime libs, that in turn require main()
#ifdef CCOVER
STDAPI_(int) ModuleEntry(void);
void __cdecl main() {
ModuleEntry();
};
#endif
STDAPI_(int) ModuleEntry(void)
{
int i;
STARTUPINFOA si;
LPTSTR pszCmdLine;
#ifdef DEBUG
// leak detection
if(!g_fInitTable)
{
if(GetLeakDetectionFunctionTable(&LeakDetFunctionTable))
g_fInitTable = TRUE;
}
if(g_fInitTable)
LeakDetFunctionTable.pfnDebugMemLeak(DML_TYPE_THREAD | DML_BEGIN, TEXT(__FILE__), __LINE__);
#endif
#ifdef WINNT
// Hydra-Specific
g_fRuningOnTerminalServer = IsTerminalServicesEnabled();
#endif
pszCmdLine = GetCommandLine();
// We don't want the "No disk in drive X:" requesters, so we set
// the critical error mask such that calls will just silently fail
SetErrorMode(SEM_FAILCRITICALERRORS);
if ( *pszCmdLine == TEXT('\"') ) {
/*
* Scan, and skip over, subsequent characters until
* another double-quote or a null is encountered.
*/
while ( *++pszCmdLine && (*pszCmdLine
!= TEXT('\"')) );
/*
* If we stopped on a double-quote (usual case), skip
* over it.
*/
if ( *pszCmdLine == TEXT('\"') )
pszCmdLine++;
}
else {
while (*pszCmdLine > TEXT(' '))
pszCmdLine++;
}
/*
* Skip past any white space preceeding the second token.
*/
while (*pszCmdLine && (*pszCmdLine <= TEXT(' '))) {
pszCmdLine++;
}
si.dwFlags = 0;
GetStartupInfoA(&si);
i = ExplorerWinMain(GetModuleHandle(NULL), NULL, pszCmdLine, si.dwFlags & STARTF_USESHOWWINDOW ? si.wShowWindow : SW_SHOWDEFAULT);
// Since we now have a way for an extension to tell us when it is finished,
// we will terminate all processes when the main thread goes away.
#ifdef DEBUG
// we stop leak detection on main shell thread here
// we need to change this
if (g_fInitTable)
LeakDetFunctionTable.pfnDebugMemLeak(DML_TYPE_THREAD | DML_END, TEXT(__FILE__), __LINE__);
#endif
if (g_fExitExplorer) // desktop told us not to exit
{
TraceMsg(DM_SHUTDOWN, "c.me: call ExitProcess");
#ifdef CCOVER
cov_write();
#endif
ExitProcess(i);
}
DebugMsg(DM_TRACE, TEXT("c.me: Cabinet main thread exiting without ExitProcess."));
return i;
}
void _InitComctl32()
{
INITCOMMONCONTROLSEX icce;
// init the controls
icce.dwICC = ICC_COOL_CLASSES | ICC_WIN95_CLASSES | ICC_PAGESCROLLER_CLASS;
icce.dwSize = sizeof(icce);
InitCommonControlsEx(&icce);
}
// cached "handle" to the desktop
HANDLE g_hDesktop = NULL;
extern IDeskTray* const c_pdtray;
BOOL CreateDesktopAndTray()
{
BOOL fRet = TRUE;
if (g_dwProfileCAP & 0x00008000)
StartCAPAll();
if (!v_hwndTray)
{
InitTrayClass(hinstCabinet);
if (!InitTray(hinstCabinet))
return FALSE;
}
ASSERT(v_hwndTray);
if (!v_hwndDesktop)
{
ASSERT(!g_hDesktop);
// cache the handle to the desktop...
g_hDesktop = SHCreateDesktop(c_pdtray);
if (g_hDesktop == NULL)
fRet = FALSE;
}
if (g_dwProfileCAP & 0x80000000)
StopCAPAll();
return fRet;
}
// The "Session key" is a volatile registry key unique to this session.
// A session is a single continuous logon. If Explorer crashes and is
// auto-restarted, the two Explorers share the same session. But if you
// log off and back on, that new Explorer is a new session.
// Note that Win9x doesn't support volatile registry keys, so we have to
// fake it. IsFirstInstanceAfterLogon() answers the question *and*
// initializes the session key.
// The s_SessionKeyName is the name of the session key relative to
// REGSTR_PATH_EXPLORER\SessionInfo. On NT, this is normally the
// Authentication ID, but we pre-initialize it to something safe so
// we don't fault if for some reason we can't get to it. Since
// Win95 supports only one session at a time, it just stays at the
// default value.
// Sometimes we want to talk about the full path (SessionInfo\BlahBlah)
// and sometimes just the partial path (BlahBlah) so we wrap it inside
// this goofy structure.
union SESSIONKEYNAME {
TCHAR szPath[12+16+1];
struct {
TCHAR szSessionInfo[12]; // strlen("SessionInfo\\")
TCHAR szName[16+1]; // 16 = two DWORDs converted to hex
};
} s_SessionKeyName = {
{ TEXT("SessionInfo\\.Default") }
};
HKEY GetSessionKey(REGSAM samDesired)
{
HKEY hkExp;
HKEY hkSession = NULL;
DWORD dwDisposition;
LONG lRes;
// Must create this key in multiple steps, because we want
// SessionInfo to be volatile, but REGSTR_PATH_EXPLORER to be
// nonvolatile.
lRes = RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, &hkExp, &dwDisposition);
if (lRes == ERROR_SUCCESS)
{
lRes = RegCreateKeyEx(hkExp, s_SessionKeyName.szPath, 0, NULL, REG_OPTION_VOLATILE, samDesired, NULL, &hkSession, &dwDisposition );
RegCloseKey(hkExp);
}
return hkSession;
}
// Removes the session key from the registry.
void NukeSessionKey(void)
{
HKEY hkExp;
LONG lRes;
lRes = RegOpenKey(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, &hkExp);
if (lRes == ERROR_SUCCESS)
{
SHDeleteKey(hkExp, s_SessionKeyName.szPath);
RegCloseKey(hkExp);
}
}
BOOL IsFirstInstanceAfterLogon()
{
BOOL fResult = FALSE;
#ifdef WINNT
LONG lRes;
HANDLE hToken;
// Build the name of the session key. We use the authentication ID
// which is guaranteed to be unique forever. We can't use the
// Hydra session ID since that can be recycled.
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
{
TOKEN_STATISTICS stats;
DWORD cbOut;
if (GetTokenInformation(hToken, TokenStatistics, &stats, sizeof(stats), &cbOut))
{
HKEY hkSession;
wsprintf(s_SessionKeyName.szName, TEXT("%08x%08x"), stats.AuthenticationId.HighPart, stats.AuthenticationId.LowPart);
hkSession = GetSessionKey(KEY_WRITE);
if (hkSession) {
HKEY hkStartup;
DWORD dwDisposition;
lRes = RegCreateKeyEx(hkSession, TEXT("StartupHasBeenRun"), 0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hkStartup, &dwDisposition );
if (lRes == ERROR_SUCCESS)
{
RegCloseKey(hkStartup);
if (dwDisposition == REG_CREATED_NEW_KEY)
fResult = TRUE;
}
RegCloseKey(hkSession);
}
}
CloseHandle(hToken);
}
#else
// on win95, we use the overloaded RegisterShellHook to thunk to the 16 bit side to do
// the work for us. this gets fixed when the tray shuts down properly...
fResult = RegisterShellHook(NULL,(BOOL) 4 );
if (fResult) {
// Clean out the "volatile" session info since Win9x doesn't support
// volatile regkeys.
NukeSessionKey();
}
#endif
return fResult;
}
// dwValue is FALSE if this is startup, TRUE if this is shutdown,
void WriteCleanShutdown(DWORD dwValue)
{
RegSetValueEx(g_hkeyExplorer, TEXT("CleanShutdown"), 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue));
// If we are shutting down for real (i.e., not fake), then clean up the
// session key so we don't leak a bazillion volatile keys into the
// registry on a Hydra system when people log on and off and on and off...
if (dwValue && !g_fFakeShutdown) {
NukeSessionKey();
}
}
BOOL ReadCleanShutdown()
{
DWORD dwValue = 1; // default: it was clean
DWORD dwSize = sizeof(dwValue);
RegQueryValueEx(g_hkeyExplorer, TEXT("CleanShutdown"), NULL, NULL, (LPBYTE)&dwValue, &dwSize);
return (BOOL)dwValue;
}
// APP HACK APP HACK
// installing SP3 over ie4 on NT4 trashes the RSA key.
// same code is in mainloop.c of iexplore.exe
#ifdef WINNT
#define RSA_PATH_TO_KEY TEXT("Software\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Base Cryptographic Provider v1.0")
#define CSD_REG_PATH TEXT("System\\CurrentControlSet\\Control\\Windows")
#define CSD_REG_VALUE TEXT("CSDVersion")
// the signatures we are looking for in the regsitry so that we can patch up
#ifdef _M_IX86
static BYTE SP3Sig[] = {0xbd, 0x9f, 0x13, 0xc5, 0x92, 0x12, 0x2b, 0x72,
0x4a, 0xba, 0xb6, 0x2a, 0xf9, 0xfc, 0x54, 0x46,
0x6f, 0xa1, 0xb4, 0xbb, 0x43, 0xa8, 0xfe, 0xf8,
0xa8, 0x23, 0x7d, 0xd1, 0x85, 0x84, 0x22, 0x6e,
0xb4, 0x58, 0x00, 0x3e, 0x0b, 0x19, 0x83, 0x88,
0x6a, 0x8d, 0x64, 0x02, 0xdf, 0x5f, 0x65, 0x7e,
0x3b, 0x4d, 0xd4, 0x10, 0x44, 0xb9, 0x46, 0x34,
0xf3, 0x40, 0xf4, 0xbc, 0x9f, 0x4b, 0x82, 0x1e,
0xcc, 0xa7, 0xd0, 0x2d, 0x22, 0xd7, 0xb1, 0xf0,
0x2e, 0xcd, 0x0e, 0x21, 0x52, 0xbc, 0x3e, 0x81,
0xb1, 0x1a, 0x86, 0x52, 0x4d, 0x3f, 0xfb, 0xa2,
0x9d, 0xae, 0xc6, 0x3d, 0xaa, 0x13, 0x4d, 0x18,
0x7c, 0xd2, 0x28, 0xce, 0x72, 0xb1, 0x26, 0x3f,
0xba, 0xf8, 0xa6, 0x4b, 0x01, 0xb9, 0xa4, 0x5c,
0x43, 0x68, 0xd3, 0x46, 0x81, 0x00, 0x7f, 0x6a,
0xd7, 0xd1, 0x69, 0x51, 0x47, 0x25, 0x14, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#else // other than _M_IX86
static BYTE SP3Sig[] = {0x8a, 0x06, 0x01, 0x6d, 0xc2, 0xb5, 0xa2, 0x66,
0x12, 0x1b, 0x9c, 0xe4, 0x58, 0xb1, 0xf8, 0x7d,
0xad, 0x17, 0xc1, 0xf9, 0x3f, 0x87, 0xe3, 0x9c,
0xdd, 0xeb, 0xcc, 0xa8, 0x6b, 0x62, 0xd0, 0x72,
0xe7, 0xf2, 0xec, 0xd6, 0xd6, 0x36, 0xab, 0x2d,
0x28, 0xea, 0x74, 0x07, 0x0e, 0x6c, 0x6d, 0xe1,
0xf8, 0x17, 0x97, 0x13, 0x8d, 0xb1, 0x8b, 0x0b,
0x33, 0x97, 0xc5, 0x46, 0x66, 0x96, 0xb4, 0xf7,
0x03, 0xc5, 0x03, 0x98, 0xf7, 0x91, 0xae, 0x9d,
0x00, 0x1a, 0xc6, 0x86, 0x30, 0x5c, 0xc8, 0xc7,
0x05, 0x47, 0xed, 0x2d, 0xc2, 0x0b, 0x61, 0x4b,
0xce, 0xe5, 0xb7, 0xd7, 0x27, 0x0c, 0x9e, 0x2f,
0xc5, 0x25, 0xe3, 0x81, 0x13, 0x9d, 0xa2, 0x67,
0xb2, 0x26, 0xfc, 0x99, 0x9d, 0xce, 0x0e, 0xaf,
0x30, 0xf3, 0x30, 0xec, 0xa3, 0x0a, 0xfe, 0x16,
0xb6, 0xda, 0x16, 0x90, 0x9a, 0x9a, 0x74, 0x7a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#endif // _M_IX86
void CheckForSP3RSAOverwrite( void )
{
// check for them having installed NTSP3 over the top of IE4, it nukes
// the RSABASE reg stuff, so we have to re-do it. (our default platform is NT + SP3, but this
// problem doesn't occur on NT5, so ignore it.
OSVERSIONINFO osVer;
ZeroMemory(&osVer, sizeof(osVer));
osVer.dwOSVersionInfoSize = sizeof(osVer);
if( GetVersionEx((OSVERSIONINFO *)&osVer) && (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
&& (osVer.dwMajorVersion == 4))
{
// now check to see we are on SP3 ...
DWORD dwValue = 0;
DWORD dwSize = sizeof( dwValue );
if ( ERROR_SUCCESS == SHGetValue( HKEY_LOCAL_MACHINE, CSD_REG_PATH, CSD_REG_VALUE, NULL, &dwValue, &dwSize) && LOWORD( dwValue ) == 0x300 )
{
BYTE rgbSig[136];
dwSize = sizeof(rgbSig);
if (ERROR_SUCCESS == SHGetValue ( HKEY_LOCAL_MACHINE, RSA_PATH_TO_KEY, TEXT("Signature"), NULL, rgbSig, &dwSize))
{
if ((dwSize == sizeof(SP3Sig)) && (0 == memcmp(SP3Sig, rgbSig, sizeof(SP3Sig))))
{
// need to do a DLLRegisterServer on RSABase
HINSTANCE hInst = LoadLibrary(TEXT("rsabase.dll"));
if ( hInst )
{
FARPROC pfnDllReg = GetProcAddress( hInst, "DllRegisterServer");
if ( pfnDllReg )
{
__try
{
pfnDllReg();
}
__except( EXCEPTION_EXECUTE_HANDLER)
{
}
}
FreeLibrary( hInst );
}
}
}
}
}
}
#else
#define CheckForSP3RSAOverwrite()
#endif
#ifdef WINNT
// Synopsis: Waits for the OLE SCM process to finish its initialization.
// This is called before the first call to OleInitialize since
// the SHELL runs early in the boot process.
// Arguments: None.
// Returns: S_OK - SCM is running. OK to call OleInitialize.
// CO_E_INIT_SCM_EXEC_FAILURE - timed out waiting for SCM
// other - create event failed
// History: 26-Oct-95 Rickhi Extracted from CheckAndStartSCM so
// that only the SHELL need call it.
HRESULT WaitForSCMToInitialize()
{
static BOOL s_fScmStarted = FALSE;
HANDLE hEvent;
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES *psa;
if (s_fScmStarted)
return S_OK;
psa = CreateAllAccessSecurityAttributes(&sa, &sd);
if (g_bRunOnNT5)
{
// on NT5 we need a global event that is shared between hydra sessions
hEvent = CreateEvent(psa, TRUE, FALSE, SZ_SCMCREATEDEVENT_NT5);
}
else
{
hEvent = CreateEvent(psa, TRUE, FALSE, SZ_SCMCREATEDEVENT);
}
if (hEvent)
{
// wait for the SCM to signal the event, then close the handle
// and return a code based on the WaitEvent result.
int rc = WaitForSingleObject(hEvent, 60000);
CloseHandle(hEvent);
if (rc == WAIT_OBJECT_0)
{
s_fScmStarted = TRUE;
return S_OK;
}
else if (rc == WAIT_TIMEOUT)
{
return CO_E_INIT_SCM_EXEC_FAILURE;
}
}
return HRESULT_FROM_WIN32(GetLastError()); // event creation failed or WFSO failed.
}
#endif // WINNT
// OleInitialize()
STDAPI OleInitializeWaitForSCM()
{
#ifdef WINNT
HRESULT hres = WaitForSCMToInitialize();
if (FAILED(hres))
return hres;
#endif
return OleInitialize(NULL);
}
// we need to figure out the fFirstShellBoot on a per-user
// basis rather than once per machine. We want the welcome
// splash screen to come up for every new user.
BOOL IsFirstShellBoot()
{
DWORD dwDisp;
HKEY hkey;
BOOL fFirstShellBoot = TRUE; // default value
if (RegCreateKeyEx(HKEY_CURRENT_USER, REGTIPS, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hkey, &dwDisp) == ERROR_SUCCESS)
{
DWORD dwSize = sizeof(fFirstShellBoot);
RegQueryValueEx(hkey, TEXT("DisplayInitialTipWindow"), NULL, NULL, (LPBYTE)&fFirstShellBoot, &dwSize);
if (fFirstShellBoot)
{
// Turn off the initial tip window for future shell starts.
BOOL bTemp = FALSE;
RegSetValueEx(hkey, TEXT("DisplayInitialTipWindow"), 0, REG_DWORD, (LPBYTE) &bTemp, sizeof(bTemp));
}
RegCloseKey(hkey);
}
return fFirstShellBoot;
}
// Post a message to the MTTF window if it is present
// The MTTF tool tracks this process being up
#define STR_MTTF_WINDOW_CLASS TEXT("HARVEYCAT")
#define MTTF_STARTUP 40006
#define MTTF_SHUTDOWN 40007
void PostMTTFMessage(WPARAM mttfMsg)
{
HWND hwndVerFind = FindWindow(STR_MTTF_WINDOW_CLASS, NULL);
if (hwndVerFind)
{
PostMessage(hwndVerFind, WM_COMMAND, (WPARAM) mttfMsg, (LPARAM) GetCurrentProcessId());
}
}
int ExplorerWinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPTSTR pszCmdLine, int nCmdShow)
{
DWORD dwShellStartTime; // used for perf times
CcshellGetDebugFlags();
if (g_dwProfileCAP & 0x00000001)
StartCAP();
hinstCabinet = hInstance;
g_fCleanBoot = GetSystemMetrics(SM_CLEANBOOT); // also known as "Safe Mode"
// Run IEAK via Wininet initialization if the autoconfig url is present.
// No need to unload wininet in this case. Also only do this first time
// Explorer loads (GetShellWindow() returns NULL).
if (!GetShellWindow() && !g_fCleanBoot && SHRegGetUSValue(TEXT("Software\\Microsoft\\Windows\\Internet Settings"),
TEXT("AutoConfigURL"),
NULL, NULL, NULL, FALSE, NULL, 0) == ERROR_SUCCESS)
{
LoadLibrary(TEXT("WININET.DLL"));
}
// Very Important: Make sure to init dde prior to any Get/Peek/Wait().
InitializeCriticalSection(&g_csDll);
_InitComctl32();
if (g_dwPrototype & 0x80000000)
{
// Turn off GDI batching so that paints are performed immediately
GdiSetBatchLimit(1);
}
RegCreateKey(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, &g_hkeyExplorer);
ASSERT(g_hkeyExplorer); // Really really bad.. unable to create reg explorer key
if (!ShouldStartDesktopAndTray())
{
MyCreateFromDesktop(hInstance, pszCmdLine, nCmdShow);
}
else
{
MSG msg;
/* In case shell32 was kept in memory by a service process, tell him to
* refresh all restrictions, before we call SHRestricted or any other
* shell API that might depend on restrictions (SHGetSpecialFolderPath,
* for example). Otherwise, the current user will inherit some of the
* previous user's restrictions.
*/
SHSettingsChanged(0, 0);
/*
* BUGBUG - Win9x - we need to nuke the drives list so the incoming
* user doesn't inherit drive icons from the previous user
*/
dwShellStartTime = GetTickCount(); // Compute shell startup time for perf automation
ShellDDEInit(TRUE); // use shdocvw shell DDE code.
// Specify the shutdown order of the shell process. 2 means
// the explorer should shutdown after everything but ntsd/windbg
// (level 0). (Taskman used to use 1, but is no more.)
SetProcessShutdownParameters(2, 0);
_AutoRunTaskMan();
// NB Make this the primary thread by calling peek message
// for a message we know we're not going to get.
// If we don't do it really soon, the notify thread can sometimes
// become the primary thread by accident. There's a bunch of
// special code in user to implement DDE hacks by assuming that
// the primary thread is handling DDE.
// Also, the PeekMsg() will cause us to set the WaitForInputIdle()
// event so we better be ready to do all dde.
PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_NOREMOVE);
// Make sure we are the first one to call the FileIconInit...
FileIconInit(TRUE); // Tell the shell we want to play with a full deck
g_fLogonCycle = IsFirstInstanceAfterLogon();
g_fCleanShutdown = ReadCleanShutdown();
if (g_fLogonCycle)
{
HWND hwndWait;
// force kernel32 to update the timezone before running any apps
DoDaylightCheck(TRUE);
ProcessRunOnceEx();
// We are about to do something that might take a long time so we want to display the
// wait cursor, however we haven't created the desktop window yet. To get around this
// we create a temporary window which we use to display the wait cursor.
// REVIEW: Maybe we should move this up to show the wait cursor sooner and longer?
hwndWait = CreateRegAppsWaitWindow();
RunRegAppsAndObjects(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE, RRA_DELETE | RRA_WAIT);
DestroyRegAppsWaitWindow(hwndWait);
}
_CreateAppGlobals();
CheckForSP3RSAOverwrite();
if (g_fCleanBoot)
DisplayCleanBootMsg(); // let users know we are in safe mode
CreateShellDirectories(); // Create the other special folders.
// Run install stubs for the current user, mostly to propagate
// shortcuts to apps installed by another user.
if (!g_fCleanBoot)
RunInstallUninstallStubs();
OleInitializeWaitForSCM();
if (!g_fCleanShutdown)
{
HWND hwndVerFind;
IActiveDesktopP *piadp;
// Post a message to the MTTF window if it is present
// BUGBUG Get LOR to use RegisterWindowMessage()
hwndVerFind = FindWindow(TEXT("BFG2000"), NULL);
if (hwndVerFind)
{
PostMessage(hwndVerFind, WM_COMMAND, 40005, 0);
}
// Put the active desktop in safe mode if we faulted previously and this is a subsequent instance
if (SUCCEEDED(CoCreateInstance(&CLSID_ActiveDesktop, NULL, CLSCTX_INPROC, &IID_IActiveDesktopP, (void **)&piadp)))
{
piadp->lpVtbl->SetSafeMode(piadp, SSM_SET | SSM_UPDATE);
piadp->lpVtbl->Release(piadp);
}
}
PostMTTFMessage(MTTF_STARTUP);
WriteCleanShutdown(FALSE); // assume we will have a bad shutdown
WinList_Init();
// If any of the shellwindows are already present, then we want to bail out.
// NOTE: Compaq shell changes the "shell=" line during RunOnce time and
// that will make ShouldStartDesktopAndTray() return FALSE
if (!IsAnyShellWindowAlreadyPresent() && CreateDesktopAndTray())
{
_RunWinComCmdLine(pszCmdLine, nCmdShow);
if (StopWatchMode())
{
// We used to save these off into global vars, and then write them at
// WM_ENDSESSION, but that seems too unreliable
DWORD dwShellStopTime = GetTickCount();
StopWatch_StartTimed(SWID_STARTUP, TEXT("Shell Startup: Start"), SPMODE_SHELL | SPMODE_DEBUGOUT, dwShellStartTime);
StopWatch_StopTimed(SWID_STARTUP, TEXT("Shell Startup: Stop"), SPMODE_SHELL | SPMODE_DEBUGOUT, dwShellStopTime);
}
if (g_dwProfileCAP & 0x00010000)
StopCAP();
// this must be whomever is the window on this thread
SHDesktopMessageLoop(g_hDesktop);
WriteCleanShutdown(TRUE); // we made it out ok, record that fact
PostMTTFMessage(MTTF_SHUTDOWN);
}
WinList_Terminate(); // Turn off our window list processing
OleUninitialize();
ShellDDEInit(FALSE); // use shdocvw shell DDE code
}
DebugMsg(DM_TRACE, TEXT("c.App Exit."));
return TRUE;
}
DWORD WINAPI RunStartupAppsThread(void *pvVoid)
{
// Some of the items we launch during startup assume that com is initialized. Make this
// assumption true.
CoInitialize(0);
// These global flags are set once long before our thread starts and are then only
// read so we don't need to worry about timing issues.
if (g_fLogonCycle && !g_fCleanBoot)
{
// We only run these startup items if g_fLogonCycle is TRUE. This prevents
// them from running again if the shell crashes and restarts.
_DoRunEquals(); // Process the Load= and Run= lines...
RunRegAppsAndObjects(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUN, RRA_NOUI);
RunRegAppsAndObjects(HKEY_CURRENT_USER, REGSTR_PATH_RUN, RRA_NOUI);
_ExecuteStartupPrograms(NULL);
}
// As a best guess, the CU\RunOnce key is executed regardless of the g_fLogonCycle
// becuase it was once hoped that we could install newer versions of IE without
// requiring a reboot. They would place something in the CU\RunOnce key and then
// shutdown and restart the shell to continue their setup process. I believe this
// idea was later abandoned but the code change is still here. Since that could
// some day be a useful feature I'm leaving it the same.
RunRegAppsAndObjects(HKEY_CURRENT_USER, REGSTR_PATH_RUNONCE, RRA_DELETE|RRA_NOUI);
// we need to run all the non-blocking items first. Then we spend the rest of this threads life
// runing the synchronized objects one after another.
if (g_fLogonCycle && !g_fCleanBoot)
{
_RunWelcome();
RunRegAppsAndObjects(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE, RRA_NOUI|RRA_RUNSUBKEYS|RRA_WAIT|RRA_DELETE);
RunRegAppsAndObjects(HKEY_CURRENT_USER, REGSTR_PATH_RUNONCE, RRA_NOUI|RRA_RUNSUBKEYS|RRA_WAIT|RRA_DELETE);
RunRegAppsAndObjects(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUN, RRA_NOUI|RRA_RUNSUBKEYS|RRA_WAIT);
RunRegAppsAndObjects(HKEY_CURRENT_USER, REGSTR_PATH_RUN, RRA_NOUI|RRA_RUNSUBKEYS|RRA_WAIT);
}
CoUninitialize();
return TRUE;
}
void RunStartupApps()
{
DWORD dwThreadID;
HANDLE handle;
handle = CreateThread( NULL, 0, RunStartupAppsThread, 0, 0, &dwThreadID );
if ( handle )
{
CloseHandle(handle);
}
else
{
// we couldn't create the thread so just call the thread proc directly.
// The RunStartupAppsThread function takes a long time to complete and
// the UI thread will be non-responsive. We call this from the tray.
// REVIEW: Is this really safe?
RunStartupAppsThread(0);
}
}