windows-nt/Source/XPSP1/NT/sdktools/mtscript/exe/mtscript.cxx
2020-09-26 16:20:57 +08:00

1920 lines
48 KiB
C++

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1998
//
// File: mtscript.cxx
//
// Contents: Implementation of the MTScript class
//
// Written by Lyle Corbin
//
//----------------------------------------------------------------------------
#include "headers.hxx"
#include <shellapi.h>
#include <advpub.h> // for RegInstall
#include "StatusDialog.h"
#include "RegSettingsIO.h"
HINSTANCE g_hInstDll;
HINSTANCE g_hinstAdvPack = NULL;
REGINSTALL g_pfnRegInstall = NULL;
const TCHAR *g_szWindowName = _T("MTScript");
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
//
// Global variables
//
HINSTANCE g_hInstance = NULL;
EXTERN_C HANDLE g_hProcessHeap = NULL;
DWORD g_dwFALSE = 0;
DeclareTagOther(tagDebugger, "MTScript", "Register with script debugger (MUST RESTART)");
DeclareTagOther(tagIMSpy, "!Memory", "Register IMallocSpy (MUST RESTART)");
//+---------------------------------------------------------------------------
//
// Function: ErrorPopup
//
// Synopsis: Displays a message to the user.
//
//----------------------------------------------------------------------------
#define ERRPOPUP_BUFSIZE 300
void
ErrorPopup(LPWSTR szMessage)
{
WCHAR achBuf[ERRPOPUP_BUFSIZE];
_snwprintf(achBuf, ERRPOPUP_BUFSIZE, L"%s: (%d)", szMessage, GetLastError());
achBuf[ERRPOPUP_BUFSIZE - 1] = L'\0';
MessageBox(NULL, achBuf, L"MTScript", MB_OK | MB_SETFOREGROUND);
}
int PrintfMessageBox(HWND hwnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType, ...)
{
TCHAR chBuffer[256];
va_list args;
va_start(args, uType);
_vsnwprintf(chBuffer, 256, lpText, args);
va_end(args);
return MessageBox(hwnd, chBuffer, lpCaption, uType);
}
//+---------------------------------------------------------------------------
//
// Function: LoadAdvPack
//
// Synopsis: Loads AdvPack.dll for DLL registration.
//
//----------------------------------------------------------------------------
HRESULT
LoadAdvPack()
{
HRESULT hr = S_OK;
g_hinstAdvPack = LoadLibrary(_T("ADVPACK.DLL"));
if (!g_hinstAdvPack)
goto Error;
g_pfnRegInstall = (REGINSTALL)GetProcAddress(g_hinstAdvPack, achREGINSTALL);
if (!g_pfnRegInstall)
goto Error;
Cleanup:
return hr;
Error:
hr = HRESULT_FROM_WIN32(GetLastError());
if (g_hinstAdvPack)
{
FreeLibrary(g_hinstAdvPack);
}
goto Cleanup;
}
//+---------------------------------------------------------------------------
//
// Function: Register
//
// Synopsis: Register the various important information needed by this
// executable.
//
// Notes: Uses AdvPack.dll and an INF file to do the registration
//
//----------------------------------------------------------------------------
HRESULT
Register()
{
HRESULT hr;
OLECHAR strClsid[40];
TCHAR keyName [256];
STRTABLE stReg = { 0, NULL };
if (!g_hinstAdvPack)
{
hr = LoadAdvPack();
if (hr)
goto Cleanup;
}
hr = g_pfnRegInstall(GetModuleHandle(NULL), "Register", &stReg);
if (!hr)
{
DWORD dwRet;
HKEY hKey;
BOOL fSetACL = FALSE;
// If the access key already exists, then don't modify it in case
// someone changed it from the defaults.
StringFromGUID2(CLSID_RemoteMTScript, strClsid, 40);
wsprintf (keyName, TEXT("APPID\\%s"), strClsid);
dwRet = RegOpenKeyEx (HKEY_CLASSES_ROOT,
keyName,
0,
KEY_ALL_ACCESS,
&hKey);
if (dwRet == ERROR_SUCCESS)
{
dwRet = RegQueryValueEx (hKey,
TEXT("AccessPermission"),
NULL,
NULL,
NULL,
NULL);
if (dwRet != ERROR_SUCCESS)
{
fSetACL = TRUE;
}
RegCloseKey (hKey);
}
else
{
fSetACL = TRUE;
}
if (fSetACL)
{
// Give everyone access rights
hr = ChangeAppIDACL(CLSID_RemoteMTScript, _T("EVERYONE"), TRUE, TRUE, TRUE);
if (!hr)
{
// Deny everyone launch rights
hr = ChangeAppIDACL(CLSID_RemoteMTScript, _T("EVERYONE"), FALSE, TRUE, TRUE);
}
}
}
Cleanup:
RegFlushKey(HKEY_CLASSES_ROOT);
return hr;
}
//+------------------------------------------------------------------------
//
// Function: Unregister
//
// Synopsis: Undo the actions of Register.
//
//-------------------------------------------------------------------------
HRESULT
Unregister()
{
HRESULT hr;
HKEY hKey;
DWORD dwRet;
OLECHAR strClsid[40];
TCHAR keyName [256];
STRTABLE stReg = { 0, NULL };
if (!g_hinstAdvPack)
{
hr = LoadAdvPack();
if (hr)
goto Cleanup;
}
//
// Remove the security keys that we created while registering
//
StringFromGUID2(CLSID_RemoteMTScript, strClsid, 40);
wsprintf (keyName, TEXT("APPID\\%s"), strClsid);
dwRet = RegOpenKeyEx (HKEY_CLASSES_ROOT,
keyName,
0,
KEY_ALL_ACCESS,
&hKey);
if (dwRet == ERROR_SUCCESS)
{
RegDeleteValue (hKey, TEXT("AccessPermission"));
RegDeleteValue (hKey, TEXT("LaunchPermission"));
RegCloseKey (hKey);
}
hr = g_pfnRegInstall(GetModuleHandle(NULL), "Unregister", &stReg);
Cleanup:
RegFlushKey(HKEY_CLASSES_ROOT);
return hr;
}
//+------------------------------------------------------------------------
//
// Function: IAmTheOnlyMTScript, private
//
// Synopsis: Guarantees that only 1 MTScript gets to run
//
// Arguments:
//
// Returns: True is there is not already a running MTScript.
//
// Note: This function "leaks" a Mutex handle intentionally.
// The system frees this handle on exit - so we know
// for sure that we have finished all other cleanup
// before it OK for another instance of MTScript to run.
//
//-------------------------------------------------------------------------
static bool IAmTheOnlyMTScript()
{
HANDLE hMutex = CreateMutex(0, FALSE, g_szWindowName);
if (!hMutex)
{
ErrorPopup(_T("Cannot create MTScript mutex!"));
return false;
}
if( GetLastError() == ERROR_ALREADY_EXISTS)
{
ErrorPopup(_T("Cannot run more than one mtscript.exe!"));
return false;
}
return true;
}
//+------------------------------------------------------------------------
//
// Function: WinMain, public
//
// Synopsis: Entry routine called by Windows upon startup.
//
// Arguments: [hInstance] -- handle to the program's instance
// [hPrevInstance] -- Always NULL
// [lpCmdLine] -- Command line arguments
// [nCmdShow] -- Value to be passed to ShowWindow when the
// main window is initialized.
//
// Returns: FALSE on error or the value passed from PostQuitMessage on exit
//
//-------------------------------------------------------------------------
EXTERN_C int PASCAL
WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
OSVERSIONINFO ovi;
CMTScript * pMT = NULL;
int iRet = 0;
#if DBG == 1
IMallocSpy * pSpy = NULL;
#endif
#ifdef USE_STACK_SPEW
InitChkStk(0xCCCCCCCC);
#endif
//
// Initialize data structures.
//
g_hProcessHeap = GetProcessHeap();
g_hInstance = hInstance;
#if DBG == 1
DbgExRestoreDefaultDebugState();
#endif
//
// Get system information
//
ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&ovi);
if (ovi.dwPlatformId != VER_PLATFORM_WIN32_NT)
{
// Win95 doesn't implement MessageBoxW
MessageBoxA(NULL,
"MTScript",
"This program can only be run on Windows NT",
MB_OK | MB_SETFOREGROUND);
goto Error;
}
if (lpCmdLine && _stricmp(&lpCmdLine[1], "register") == 0)
{
HRESULT hr;
hr = Register();
if (FAILED(hr))
goto Error;
return 0;
}
else if (lpCmdLine && _stricmp(&lpCmdLine[1], "unregister") == 0)
{
HRESULT hr;
hr = Unregister();
if (FAILED(hr))
goto Error;
return 0;
}
if (!IAmTheOnlyMTScript() )
{
return 1;
}
#if DBG == 1
if (DbgExIsTagEnabled(tagIMSpy))
{
pSpy = (IMallocSpy *)DbgExGetMallocSpy();
if (pSpy)
{
CoRegisterMallocSpy(pSpy);
}
}
#endif
//
// Can't put it on the stack because it depends on having zero'd memory
//
pMT = new CMTScript();
if (!pMT)
goto Error;
if (!pMT->Init())
goto Error;
//
// Start doing real stuff
//
iRet = pMT->ThreadMain();
Cleanup:
if (pMT)
pMT->Release();
#if DBG == 1
if (pSpy)
{
// Note, due to the fact that we do not have control over DLL unload
// ordering, the IMallocSpy implementation may report false leaks.
// (lylec) The only way to fix this is to explicitely load all
// dependent DLLs and unload them in their proper order (mshtmdbg.dll
// last).
CoRevokeMallocSpy();
}
#endif
return iRet;
Error:
iRet = 1;
goto Cleanup;
}
//+---------------------------------------------------------------------------
//
// CMTScript::OPTIONSETTINGS class
//
// Handles user-configurable options
//
//----------------------------------------------------------------------------
CMTScript::OPTIONSETTINGS::OPTIONSETTINGS()
: cstrScriptPath(CSTR_NOINIT),
cstrInitScript(CSTR_NOINIT)
{
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::OPTIONSETTINGS::GetModulePath, public
//
// Synopsis: Returns the path of this executable. Used for finding other
// related files.
//
// Arguments: [pstr] -- Place to put path.
//
// Notes: Any existing string in pstr will be cleared.
//
//----------------------------------------------------------------------------
void
CMTScript::OPTIONSETTINGS::GetModulePath(CStr *pstr)
{
WCHAR *pch;
WCHAR achBuf[MAX_PATH];
pstr->Free();
if (!GetModuleFileName(NULL, achBuf, sizeof(achBuf)))
return;
pch = wcsrchr(achBuf, L'\\');
if (!pch)
return;
*pch = L'\0';
pstr->Set(achBuf);
}
void
CMTScript::OPTIONSETTINGS::GetScriptPath(CStr *cstrScript)
{
LOCK_LOCALS(this);
if (cstrScriptPath.Length() == 0)
{
// TCHAR achBuf[MAX_PATH];
// TCHAR *psz;
GetModulePath(cstrScript);
/*
cstrScript->Append(L"\\..\\..\\scripts");
GetFullPathName(*cstrScript, MAX_PATH, achBuf, &psz);
cstrScript->Set(achBuf);
*/
}
else
{
cstrScript->Set(cstrScriptPath);
}
}
void
CMTScript::OPTIONSETTINGS::GetInitScript(CStr *cstr)
{
static WCHAR * pszInitScript = L"mtscript.js";
LOCK_LOCALS(this);
if (cstrInitScript.Length() == 0)
{
cstr->Set(pszInitScript);
}
else
{
cstr->Set(cstrInitScript);
}
}
//+---------------------------------------------------------------------------
//
// CMTScript class
//
// Handles the main UI thread
//
//----------------------------------------------------------------------------
CMTScript::CMTScript()
{
Assert(_fInDestructor == FALSE);
Assert(_pGIT == NULL);
Assert(_dwPublicDataCookie == 0);
Assert(_dwPrivateDataCookie == 0);
Assert(_dwPublicSerialNum == 0);
Assert(_dwPrivateSerialNum == 0);
VariantInit(&_vPublicData);
VariantInit(&_vPrivateData);
_ulRefs = 1;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::~CMTScript, public
//
// Synopsis: destructor
//
// Notes: Anything done in the Init() call must be undone here. This
// method must also be able to handle a partial initialization,
// in case something inside Init() failed halfway through.
//
//----------------------------------------------------------------------------
CMTScript::~CMTScript()
{
int i;
VERIFY_THREAD();
// Anything done in the Init() call must be undone here
UnregisterClassObjects();
_fInDestructor = TRUE;
//
// Process any pending messages such as PROCESSTHREADTERMINATE now.
//
HandleThreadMessage();
// Cleanup any running processes.
for (i = 0; i < _aryProcesses.Size(); i++)
{
Shutdown(_aryProcesses[i]);
}
_aryProcesses.ReleaseAll();
//
// This must be done in reverse order because the primary script (element 0)
// must be shutdown last.
//
for (i = _aryScripts.Size() - 1; i >= 0; i--)
{
Shutdown(_aryScripts[i]);
}
_aryScripts.ReleaseAll();
if (_pMachine)
{
Shutdown(_pMachine);
ReleaseInterface(_pMachine);
}
if (_dwPublicDataCookie != 0)
{
_pGIT->RevokeInterfaceFromGlobal(_dwPublicDataCookie);
}
if (_dwPrivateDataCookie != 0)
{
_pGIT->RevokeInterfaceFromGlobal(_dwPrivateDataCookie);
}
ReleaseInterface(_pTIMachine);
ReleaseInterface(_pTypeLibEXE);
ReleaseInterface(_pGIT);
ReleaseInterface(_pJScriptFactory);
VariantClear(&_vPublicData);
VariantClear(&_vPrivateData);
DeInitScriptDebugger();
CoUninitialize();
CleanupUI();
// This delete call must be done after we've destroyed our window, which
// in turn will destroy the status window.
delete _pStatusDialog;
_pStatusDialog = NULL;
}
HRESULT
CMTScript::QueryInterface(REFIID iid, void **ppvObj)
{
if (iid == IID_IUnknown)
{
*ppvObj = (IUnknown *)this;
}
else
{
*ppvObj = NULL;
return E_NOINTERFACE;
}
((IUnknown *)*ppvObj)->AddRef();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::Init, public
//
// Synopsis: Initialization method that can fail.
//
//----------------------------------------------------------------------------
BOOL
CMTScript::Init()
{
HRESULT hr;
_dwThreadId = GetCurrentThreadId();
if (!CThreadComm::Init())
return FALSE;
//
// Load our default configuration
//
UpdateOptionSettings(FALSE);
if (FAILED(CoInitializeEx(NULL,
COINIT_MULTITHREADED |
COINIT_DISABLE_OLE1DDE |
COINIT_SPEED_OVER_MEMORY)))
{
goto Error;
}
// This code may be needed if we want to do our own custom security.
// However, it is easier to let DCOM do it for us.
// The following code removes all security always. It cannot be overridden
// using the registry.
if (FAILED(CoInitializeSecurity(NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL)))
{
goto Error;
}
hr = LoadTypeLibraries();
if (hr)
goto Error;
InitScriptDebugger();
if (FAILED(CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&_pGIT)))
{
goto Error;
}
//
// Create our hidden window and put an icon on the taskbar
//
if (!ConfigureUI())
goto Error;
//
// Run the initial script
//
if (FAILED(RunScript(NULL, NULL)))
goto Error;
if (RegisterClassObjects(this) != S_OK)
goto Error;
#if DBG == 1
OpenStatusDialog();
#endif
return TRUE;
Error:
return FALSE;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::ThreadMain, public
//
// Synopsis: Main UI message loop.
//
// Arguments: [hWnd] -- Modeless Dialog HWND
//
//----------------------------------------------------------------------------
DWORD
CMTScript::ThreadMain()
{
DWORD dwRet;
HANDLE ahEvents[3];
int cEvents = 2;
DWORD dwReturn = 0;
VERIFY_THREAD();
SetName("CMTScript");
// Don't need to call ThreadStarted() because StartThread() was not used
// to start the main thread!
ahEvents[0] = _hCommEvent;
ahEvents[1] = GetPrimaryScript()->hThread();
while (TRUE)
{
MSG msg;
//
// Purge out all window messages.
//
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
goto Cleanup;
}
if (_pStatusDialog)
{
if (_pStatusDialog->IsDialogMessage(&msg))
continue;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//
// Wait for anything we need to deal with.
//
dwRet = MsgWaitForMultipleObjects(cEvents,
ahEvents,
FALSE,
INFINITE,
QS_ALLINPUT);
if (dwRet == WAIT_OBJECT_0)
{
//
// Another thread is sending us a message.
//
HandleThreadMessage();
}
else if (dwRet == WAIT_OBJECT_0 + 1)
{
// The Primary Script Thread terminated due to a problem loading
// the initial script. Bring up the configuration dialog.
{
LOCK_LOCALS(this);
_aryScripts.ReleaseAll();
}
if (!_fRestarting)
{
int iRet = MessageBox(_hwnd, _T("An error occurred loading the default script.\n\nDo you wish to edit the default configuration?"),
_T("MTScript Error"),
MB_YESNO | MB_SETFOREGROUND | MB_ICONERROR);
if (iRet == IDNO)
{
goto Cleanup;
}
_fRestarting = TRUE; // Preventthe config dialog from doing a restart in this case.
CConfig * pConfig = new CConfig(this);
if (pConfig)
{
pConfig->StartThread(NULL);
WaitForSingleObject(pConfig->hThread(), INFINITE);
pConfig->Release();
}
_fRestarting = FALSE;
}
else
{
_fRestarting = FALSE;
}
//
// Try re-loading the initial script
//
if (FAILED(RunScript(NULL, NULL)))
goto Error;
ahEvents[1] = GetPrimaryScript()->hThread();
if (_pStatusDialog)
_pStatusDialog->Restart();
}
else if (dwRet == WAIT_TIMEOUT)
{
// Make sure our message queue is empty first.
HandleThreadMessage();
// Right now we never fall in this loop.
}
else if (dwRet == WAIT_FAILED)
{
TraceTag((tagError, "MsgWaitForMultipleObjects failure (%d)", GetLastError()));
AssertSz(FALSE, "MsgWaitForMultipleObjects failure");
goto Cleanup;
}
}
Cleanup:
return dwReturn;
Error:
dwReturn = 1;
goto Cleanup;
}
void
CMTScript::InitScriptDebugger()
{
HRESULT hr;
if (!IsTagEnabled(tagDebugger))
{
return;
}
hr = CoCreateInstance(CLSID_ProcessDebugManager,
NULL,
CLSCTX_INPROC_SERVER |
CLSCTX_INPROC_HANDLER |
CLSCTX_LOCAL_SERVER,
IID_IProcessDebugManager,
(LPVOID*)&_pPDM);
if (hr)
{
TraceTag((tagError, "Could not create ProcessDebugManager: %x", hr));
return;
}
hr = THR(_pPDM->CreateApplication(&_pDA));
if (hr)
goto Error;
_pDA->SetName(L"MTScript");
hr = THR(_pPDM->AddApplication(_pDA, &_dwAppCookie));
if (hr)
goto Error;
return;
Error:
ClearInterface(&_pDA);
ClearInterface(&_pPDM);
return;
}
void
CMTScript::DeInitScriptDebugger()
{
_try
{
if (_pPDM)
{
_pPDM->RemoveApplication(_dwAppCookie);
_pDA->Close();
ReleaseInterface(_pPDM);
ReleaseInterface(_pDA);
}
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
//$ BUGBUG -- Figure out what's wrong here!
// Ignore the crash caused by the Script Debugger
}
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::HackCreateInstance, public
//
// Synopsis: Loads a private jscript.dll since the one that shipped with
// Win2K is broken for what we need it for.
//
// Arguments: [clsid] -- Same parameters as CoCreateInstance.
// [pUnk] --
// [clsctx] --
// [riid] --
// [ppv] --
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT
CMTScript::HackCreateInstance(REFCLSID clsid,
IUnknown *pUnk,
DWORD clsctx,
REFIID riid,
LPVOID *ppv)
{
TCHAR achDllPath[MAX_PATH];
HINSTANCE hInstDll;
DWORD iRet;
TCHAR *pch;
LPFNGETCLASSOBJECT pfnGCO = NULL;
HRESULT hr;
DWORD dwJunk;
BYTE *pBuf = NULL;
VS_FIXEDFILEINFO *pFI = NULL;
UINT iLen;
if (!_pJScriptFactory && _fHackVersionChecked)
{
return S_FALSE;
}
if (!_pJScriptFactory)
{
LOCK_LOCALS(this);
// Make sure another thread didn't take care of this while we were
// waiting for the lock.
if (!_fHackVersionChecked)
{
// Remember we've done this check so we won't do it again.
_fHackVersionChecked = TRUE;
// First, check the version number on the system DLL
iRet = GetSystemDirectory(achDllPath, MAX_PATH);
if (iRet == 0)
goto Win32Error;
_tcscat(achDllPath, _T("\\jscript.dll"));
iRet = GetFileVersionInfoSize(achDllPath, &dwJunk);
if (iRet == 0)
goto Win32Error;
pBuf = new BYTE[iRet];
iRet = GetFileVersionInfo(achDllPath, NULL, iRet, pBuf);
if (iRet == 0)
goto Win32Error;
if (!VerQueryValue(pBuf, _T("\\"), (LPVOID*)&pFI, &iLen))
goto Win32Error;
//
// Is the system DLL a version that has our needed fix?
//
// Version 5.1.0.4702 has the fix but isn't approved for Win2K.
// The first official version that has the fix is 5.5.0.4703.
//
if ( (pFI->dwProductVersionMS == 0x00050001 && pFI->dwProductVersionLS >= 4702)
|| (pFI->dwProductVersionMS >= 0x00050005 && pFI->dwProductVersionLS >= 4703))
{
hr = S_FALSE;
goto Cleanup;
}
iRet = GetModuleFileName(NULL, achDllPath, MAX_PATH);
if (iRet == 0)
goto Win32Error;
pch = _tcsrchr(achDllPath, _T('\\'));
if (pch)
{
*pch = _T('\0');
}
_tcscat(achDllPath, _T("\\jscript.dll"));
hInstDll = CoLoadLibrary(achDllPath, TRUE);
if (!hInstDll)
{
hr = HRESULT_FROM_WIN32(GetLastError());
ErrorPopup(_T("Your copy of JSCRIPT.DLL contains a problem which may prevent you from using this tool.\n")
_T("Please update that DLL to version 5.1.0.4702 or later.\n")
_T("You may put the new DLL in the same directory as mtscript.exe to avoid upgrading the system DLL."));
// ErrorPopup clears the GetLastError() status.
goto Cleanup;
}
pfnGCO = (LPFNGETCLASSOBJECT)GetProcAddress(hInstDll, "DllGetClassObject");
if (!pfnGCO)
goto Win32Error;
hr = (*pfnGCO)(clsid, IID_IClassFactory, (LPVOID*)&_pJScriptFactory);
if (hr)
goto Cleanup;
}
}
if (_pJScriptFactory)
hr = _pJScriptFactory->CreateInstance(pUnk, riid, ppv);
else
hr = S_FALSE;
Cleanup:
delete [] pBuf;
return hr;
Win32Error:
hr = HRESULT_FROM_WIN32(GetLastError());
goto Cleanup;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::ConfigureUI, public
//
// Synopsis: Creates our hidden window and puts an icon on the taskbar
//
//----------------------------------------------------------------------------
BOOL
CMTScript::ConfigureUI()
{
VERIFY_THREAD();
WNDCLASS wc = { 0 };
NOTIFYICONDATA ni = { 0 };
ATOM aWin;
BOOL fRet;
// The window will be a hidden window so we don't set many of the attributes
wc.lpfnWndProc = MainWndProc;
wc.hInstance = g_hInstance;
wc.lpszClassName = SZ_WNDCLASS;
aWin = RegisterClass(&wc);
if (aWin == 0)
{
return FALSE;
}
_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW,
(LPTSTR)aWin,
g_szWindowName,
WS_OVERLAPPED,
10, // Coordinates don't matter - it will never
10, // be visible.
10,
10,
NULL,
NULL,
g_hInstance,
(LPVOID)this);
if (_hwnd == NULL)
{
return FALSE;
}
ni.cbSize = sizeof(NOTIFYICONDATA);
ni.hWnd = _hwnd;
ni.uID = 1;
ni.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
ni.uCallbackMessage = WM_USER;
ni.hIcon = LoadIcon(g_hInstance, L"myicon_small");
wcscpy(ni.szTip, L"Remote Script Engine");
fRet = Shell_NotifyIcon(NIM_ADD, &ni);
DestroyIcon(ni.hIcon);
return fRet;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::CleanupUI, public
//
// Synopsis: Cleans up UI related things.
//
//----------------------------------------------------------------------------
void
CMTScript::CleanupUI()
{
VERIFY_THREAD();
NOTIFYICONDATA ni = { 0 };
if (_hwnd != NULL)
{
ni.cbSize = sizeof(NOTIFYICONDATA);
ni.hWnd = _hwnd;
ni.uID = 1;
Shell_NotifyIcon(NIM_DELETE, &ni);
DestroyWindow(_hwnd);
}
}
HRESULT
CMTScript::LoadTypeLibraries()
{
VERIFY_THREAD();
HRESULT hr = S_OK;
_pTIMachine = NULL;
if (!_pTypeLibEXE)
{
hr = THR(LoadRegTypeLib(LIBID_MTScriptEngine, 1, 0, 0, &_pTypeLibEXE));
if (hr)
goto Cleanup;
}
if (!_pTIMachine)
{
TYPEATTR *pTypeAttr;
UINT mb = IDYES;
hr = THR(_pTypeLibEXE->GetTypeInfoOfGuid(IID_IConnectedMachine, &_pTIMachine));
if (hr)
goto Cleanup;
hr = THR(_pTIMachine->GetTypeAttr(&pTypeAttr));
if (hr)
goto Cleanup;
if (pTypeAttr->wMajorVerNum != IConnectedMachine_lVersionMajor || pTypeAttr->wMinorVerNum != IConnectedMachine_lVersionMinor)
{
mb = PrintfMessageBox(NULL,
L"Mtscript.exe version (%d.%d) does not match mtlocal.dll (%d.%d).\n"
L"You may experience undefined behavior if you continue.\n"
L"Do you wish to ignore this error and continue?",
L"Version mismatch error",
MB_YESNO | MB_ICONWARNING | MB_SETFOREGROUND | MB_DEFBUTTON2,
IConnectedMachine_lVersionMajor, IConnectedMachine_lVersionMinor,
pTypeAttr->wMajorVerNum, pTypeAttr->wMinorVerNum);
}
_pTIMachine->ReleaseTypeAttr(pTypeAttr);
if (mb != IDYES)
return E_FAIL;
}
Cleanup:
if (hr)
{
PrintfMessageBox(NULL,
_T("FATAL: Could not load type library (%x).\nIs mtlocal.dll registered?"),
_T("MTScript"),
MB_OK | MB_ICONERROR | MB_SETFOREGROUND,
hr);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::ShowMenu, public
//
// Synopsis: Displays a menu when the user right-clicks on the tray icon.
//
// Arguments: [x] -- x location
// [y] -- y location
//
//----------------------------------------------------------------------------
void
CMTScript::ShowMenu(int x, int y)
{
VERIFY_THREAD();
ULONG ulItem;
HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MAINMENU));
if (x == -1 && y == -1)
{
POINT pt;
GetCursorPos(&pt);
x = pt.x;
y = pt.y;
}
SetForegroundWindow(_hwnd);
ulItem = TrackPopupMenuEx(GetSubMenu(hMenu, 0),
TPM_RETURNCMD |
TPM_NONOTIFY |
TPM_RIGHTBUTTON |
TPM_LEFTALIGN,
x, y,
_hwnd,
NULL);
switch (ulItem)
{
case IDM_EXIT:
UpdateOptionSettings(true);
PostQuitMessage(0);
break;
case IDM_CONFIGURE:
{
CConfig * pConfig = new CConfig(this);
if (pConfig)
{
pConfig->StartThread(NULL);
pConfig->Release();
}
}
break;
case IDM_RESTART:
Restart();
break;
case IDM_STATUS:
if (!_pStatusDialog)
OpenStatusDialog();
if (_pStatusDialog)
_pStatusDialog->Show();
break;
#if DBG == 1
case IDM_TRACETAG:
DbgExDoTracePointsDialog(FALSE);
break;
case IDM_MEMORYMON:
DbgExOpenMemoryMonitor();
break;
#endif
}
DestroyMenu(hMenu);
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::HandleThreadMessage, public
//
// Synopsis: Another thread has sent us a cross-thread message that we need
// to handle.
//
//----------------------------------------------------------------------------
void
CMTScript::HandleThreadMessage()
{
VERIFY_THREAD();
THREADMSG tm;
BYTE bData[MSGDATABUFSIZE];
DWORD cbData;
while (GetNextMsg(&tm, (void *)bData, &cbData))
{
switch (tm)
{
case MD_SECONDARYSCRIPTTERMINATE:
{
//
// A secondary script ended. Find it in our list, remove it,
// and delete it.
//
CScriptHost *pbs = *(CScriptHost**)bData;
LOCK_LOCALS(this);
Verify(_aryScripts.DeleteByValue(pbs));
pbs->Release();
}
break;
case MD_MACHINECONNECT:
PostToThread(GetPrimaryScript(), MD_MACHINECONNECT);
break;
case MD_SENDTOPROCESS:
{
MACHPROC_EVENT_DATA *pmed = *(MACHPROC_EVENT_DATA**)bData;
CProcessThread *pProc = FindProcess(pmed->dwProcId);
if (pProc && pProc->GetProcComm())
{
pProc->GetProcComm()->SendToProcess(pmed);
}
else
{
V_VT(pmed->pvReturn) = VT_I4;
V_I4(pmed->pvReturn) = -1;
SetEvent(pmed->hEvent);
}
}
break;
case MD_REBOOT:
Reboot();
break;
case MD_RESTART:
Restart();
break;
case MD_PLEASEEXIT:
PostQuitMessage(0);
break;
case MD_OUTPUTDEBUGSTRING:
if (_pStatusDialog)
{
_pStatusDialog->OUTPUTDEBUGSTRING( (LPWSTR) bData);
}
break;
default:
AssertSz(FALSE, "CMTScript got a message it couldn't handle!");
break;
}
}
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::RunScript, public
//
// Synopsis: Creates a scripting thread and runs a given script
// Can be called from any thread.
//
// Arguments: [pszPath] -- If NULL, we're starting the primary thread.
// Otherwise, it's the name of a file in the
// scripts directory.
//
//----------------------------------------------------------------------------
HRESULT
CMTScript::RunScript(LPWSTR pszPath, VARIANT *pvarParam)
{
HRESULT hr;
CScriptHost * pScript = NULL;
CStr cstrScript;
SCRIPT_PARAMS scrParams;
if (!pszPath)
_options.GetInitScript(&cstrScript);
else
cstrScript.Set(pszPath);
AssertSz(cstrScript.Length() > 0, "CRASH: Bogus script path");
pScript = new CScriptHost(this,
(pszPath) ? FALSE : TRUE,
FALSE);
if (!pScript)
{
hr = E_OUTOFMEMORY;
goto Error;
}
scrParams.pszPath = cstrScript;
scrParams.pvarParams = pvarParam;
// Race: We can successfully start a thread.
// That thread can run to completion, and exit.
// CScriptHost would then post MD_SECONDARYSCRIPTTERMINATE
// in an attempt to remove the script from the list and free
// it.
// Thus, we must add it to the list first, then remove it
// if the script fails to start.
{
LOCK_LOCALS(this);
_aryScripts.Append(pScript);
}
hr = pScript->StartThread(&scrParams);
if (FAILED(hr) && pszPath) // DO NOT REMOVE THE PRIMARY SCRIPT! Instead, return SUCCESS.
{ // The main thread makes a special check for the primary script
LOCK_LOCALS(this);
Verify(_aryScripts.DeleteByValue(pScript));
pScript->Release();
goto Error;
}
Assert(pszPath || _aryScripts.Size() == 1);
return S_OK;
Error:
ReleaseInterface(pScript);
if (pszPath == 0)
ErrorPopup(L"An error occurred running the initial script");
return hr;
}
HRESULT
CMTScript::UpdateOptionSettings(BOOL fSave)
{
LOCK_LOCALS(&_options); // Makes this method thread safe
static REGKEYINFORMATION aKeyValuesOptions[] =
{
{ _T("File Paths"), RKI_KEY, 0 },
{ _T("Script Path"), RKI_EXPANDSZ, offsetof(OPTIONSETTINGS, cstrScriptPath) },
{ _T("Initial Script"), RKI_STRING, offsetof(OPTIONSETTINGS, cstrInitScript) },
};
HRESULT hr;
hr = RegSettingsIO(g_szRegistry, fSave, aKeyValuesOptions, ARRAY_SIZE(aKeyValuesOptions), (BYTE *)&_options);
return hr;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::Restart, public
//
// Synopsis: Restarts like we were just starting
//
// Notes: All currently running scripts are stopped and destroyed.
// Public information is freed and then everything is restarted.
//
//----------------------------------------------------------------------------
void
CMTScript::Restart()
{
int i;
// Make sure the status dialog doesn't try to do anything while we're
// restarting.
if (_pStatusDialog)
_pStatusDialog->Pause();
// Kill all running scripts and start over.
for (i = _aryScripts.Size() - 1; i >= 0; i--)
{
Shutdown(_aryScripts[i]);
// Scripts will be released when they notify us of their being
// shutdown.
}
// Kill all running processes
for (i = _aryProcesses.Size() - 1; i >= 0; i--)
{
Shutdown(_aryProcesses[i]);
}
_aryProcesses.ReleaseAll();
if (_dwPublicDataCookie != 0)
{
_pGIT->RevokeInterfaceFromGlobal(_dwPublicDataCookie);
_dwPublicDataCookie = 0;
}
if (_dwPrivateDataCookie != 0)
{
_pGIT->RevokeInterfaceFromGlobal(_dwPrivateDataCookie);
_dwPrivateDataCookie = 0;
}
VariantClear(&_vPublicData);
VariantClear(&_vPrivateData);
// Reset the statusvalues to 0
memset(_rgnStatusValues, 0, sizeof(_rgnStatusValues));
// The above call to shutdown will terminate the primary script thread,
// which will trigger the restart code in ThreadMain().
_fRestarting = TRUE;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::OpenStatusDialog, public
//
// Synopsis: Opens the status modeless dialog
//
//----------------------------------------------------------------------------
void
CMTScript::OpenStatusDialog()
{
if (!_pStatusDialog)
_pStatusDialog = new CStatusDialog(_hwnd, this);
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::Reboot, public
//
// Synopsis: Reboots the local machine. The user must have appropriate
// rights to do so.
//
//----------------------------------------------------------------------------
void
CMTScript::Reboot()
{
TOKEN_PRIVILEGES tp;
LUID luid;
HANDLE hToken;
//
// Try to make sure we get shutdown last
//
SetProcessShutdownParameters(0x101, 0);
//
// Setup shutdown priviledges
//
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hToken))
{
goto Error;
}
if (!LookupPrivilegeValue(NULL,
SE_SHUTDOWN_NAME,
&luid))
{
goto Error;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL);
if (GetLastError() != ERROR_SUCCESS)
goto Error;
PostQuitMessage(0);
// BUGBUG -- This call is Windows2000 specific.
ExitWindowsEx(EWX_REBOOT | EWX_FORCEIFHUNG, 0xFFFF);
return;
Error:
TraceTag((tagError, "Failed to get security to reboot."));
return;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::AddProcess, public
//
// Synopsis: Adds a process to our process thread list. Any old ones
// hanging around are cleaned up in the meantime.
//
// Arguments: [pProc] -- New process object to add.
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT
CMTScript::AddProcess(CProcessThread *pProc)
{
LOCK_LOCALS(this);
CleanupOldProcesses();
return _aryProcesses.Append(pProc);
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::FindProcess, public
//
// Synopsis: Returns a process object for the given process ID.
//
// Arguments: [dwProcID] -- Process ID to find
//
//----------------------------------------------------------------------------
CProcessThread *
CMTScript::FindProcess(DWORD dwProcID)
{
CProcessThread **ppProc;
int cProcs;
LOCK_LOCALS(this);
CleanupOldProcesses();
for (ppProc = _aryProcesses, cProcs = _aryProcesses.Size();
cProcs;
ppProc++, cProcs--)
{
if ((*ppProc)->ProcId() == dwProcID)
{
break;
}
}
if (cProcs == 0)
{
return NULL;
}
return *ppProc;
}
BOOL CMTScript::SetScriptPath(const TCHAR *pszScriptPath, const TCHAR *pszInitScript)
{
LOCK_LOCALS(&_options);
// If there is any change then prompt the user, then force a restart.
//
// NOTE: The CStr "class" does not protect itself, so we must test it
// first before using it!
//
if ( (_options.cstrScriptPath == 0 || _tcscmp(pszScriptPath, _options.cstrScriptPath) != 0) ||
(_options.cstrInitScript == 0 || _tcscmp(pszInitScript, _options.cstrInitScript) != 0))
{
if (!_fRestarting)
{
UINT mb = MessageBox(NULL, L"This will require a restart. Continue?", L"Changing script path or starting script", MB_OKCANCEL | MB_ICONWARNING | MB_SETFOREGROUND);
if (mb == IDCANCEL)
return FALSE;
}
_options.cstrScriptPath.Set(pszScriptPath);
_options.cstrInitScript.Set(pszInitScript);
// Write it out to the registry.
UpdateOptionSettings(TRUE);
if (!_fRestarting)
PostToThread(this, MD_RESTART);
}
return TRUE;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::CleanupOldProcesses, public
//
// Synopsis: Walks the array of process objects and looks for ones that
// have been dead for more than a specified amount of time. For
// those that are, the objects are freed.
//
// Notes: Assumes that the caller has already locked the process array.
//
//----------------------------------------------------------------------------
const ULONG MAX_PROCESS_DEADTIME = 5 * 60 * 1000; // Cleanup after 5 minutes
void
CMTScript::CleanupOldProcesses()
{
int i;
//$ CONSIDER: Adding a max number of dead processes as well.
// We assume that the process array is already locked (via LOCK_LOCALS)!
// Iterate in reverse order since we'll be removing elements as we go.
for (i = _aryProcesses.Size() - 1; i >= 0; i--)
{
if (_aryProcesses[i]->GetDeadTime() > MAX_PROCESS_DEADTIME)
{
_aryProcesses.ReleaseAndDelete(i);
}
}
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::GetScriptNames, public
//
// Synopsis: Walks the array of scripts, copying the script names
// into the supplied buffer.
// Each name is null terminated. The list is double null terminated.
// If the buffer is not large enough, then *pcBuffer
// is set the the required size and FALSE is returned.
//
// Notes: Returns 0 if index is past end of array of scripts.
//
//----------------------------------------------------------------------------
BOOL
CMTScript::GetScriptNames(TCHAR *pchBuffer, long *pcBuffer)
{
VERIFY_THREAD();
LOCK_LOCALS(this);
long nChars = 0;
int i;
TCHAR *pch = pchBuffer;
for(i = 0; i < _aryScripts.Size(); ++i)
{
TCHAR *ptr = _T("<invalid>");
CScriptSite *site = _aryScripts[i]->GetSite();
if (site)
{
ptr = _tcsrchr((LPTSTR)site->_cstrName, _T('\\'));
if (!ptr)
ptr = (LPTSTR)site->_cstrName;
}
int n = _tcslen(ptr) + 1;
nChars += n;
if ( nChars + 1 < *pcBuffer)
{
_tcscpy(pch, ptr);
pch += n;
}
*pch = 0; // double null terminator.
}
BOOL retval = nChars + 1 < *pcBuffer;
*pcBuffer = nChars + 1; // double null termination
return retval;
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::GetPrimaryScript, public
//
// Synopsis: Returns the first script in the array
//
//----------------------------------------------------------------------------
CScriptHost *CMTScript::GetPrimaryScript()
{
LOCK_LOCALS(this);
return _aryScripts[0];
}
//+---------------------------------------------------------------------------
//
// Member: CMTScript::GetProcess, public
//
// Synopsis: Walks the array of processes
//
// Notes: Returns 0 if index is past end of array of processes.
//
//----------------------------------------------------------------------------
CProcessThread *
CMTScript::GetProcess(int index)
{
VERIFY_THREAD();
LOCK_LOCALS(this);
if (index < 0 || index >= _aryProcesses.Size())
return 0;
return _aryProcesses[index];
}
//+---------------------------------------------------------------------------
//
// Function: MainWndProc
//
// Synopsis: Main window procedure for our hidden window. Used mainly to
// handle context menu events on our tray icon.
//
//----------------------------------------------------------------------------
LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static CMTScript *s_pMT = NULL;
switch (msg)
{
case WM_CREATE:
{
CREATESTRUCT UNALIGNED *pcs = (CREATESTRUCT *)lParam;
s_pMT = (CMTScript *)pcs->lpCreateParams;
}
return 0;
case WM_USER:
switch (lParam)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_CONTEXTMENU:
if (s_pMT)
{
s_pMT->ShowMenu(-1, -1);
}
return 0;
}
return 0;
case WM_COMMAND:
return 0;
break;
case WM_LBUTTONDOWN:
return 0;
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
//+---------------------------------------------------------------------------
//
// Function: get_StatusValue
//
// Synopsis: Return the value at [nIndex] in the StatusValues array
// Currently the implementation of this property has a small
// limit to the range of "nIndex".
// This allows us to avoid any dynamic memory allocation
// and also allows us to dispense with the usual thread locking.
//
//----------------------------------------------------------------------------
HRESULT
CMTScript::get_StatusValue(long nIndex, long *pnStatus)
{
if (!pnStatus)
return E_POINTER;
if (nIndex < 0 || nIndex >= ARRAY_SIZE(_rgnStatusValues))
return E_INVALIDARG;
*pnStatus = _rgnStatusValues[nIndex];
return S_OK;
}
//+---------------------------------------------------------------------------
//
// Function: put_StatusValue
//
// Synopsis: Set the value at [nIndex] in the StatusValues array
//
//----------------------------------------------------------------------------
HRESULT
CMTScript::put_StatusValue(long nIndex, long nStatus)
{
if (nIndex < 0 || nIndex >= ARRAY_SIZE(_rgnStatusValues))
return E_INVALIDARG;
_rgnStatusValues[nIndex] = nStatus;
return S_OK;
}