//+--------------------------------------------------------------------------- // // 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 #include // 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(""); 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; }