/* * NMPGMGRP - Tiny program to add and remove items from the program group. * Its initial purpose is to support Windows NT common program groups, * which are not supported by GRPCONV. * * Usage: * * NMPGMGRP /add [/common] [/g:""] /n:"" * /p:"" * NMPGMGRP /delete [/common] [/g:""] /n:"" * * NMPGMGRP /i /n:"" /p:"" INSTALL NT DD * NMPGMGRP /u /n:"" UNINSTALL NT DD * * NMPGMGRP /s [/q] /n:"" /f"" SETUP * * /add is used to add a new program item. * /delete is used to remove an existing program item. * * /common indicates that this item belong in the common (as opposed to * per-user) program groups. * * is the name of the program group, expressed as a pathname * relative to the Programs group. For items in the Programs group, * this parameter should be omitted. * * is the name of the program, and is also used as the name * of the shortcut file itself. * * is the full path name of the program. * * is the name of the installation inf. * * is the text to be used for any message box title. * * Limitations: * * Because some of these strings may contain spaces, the group name, program * name, and program path MUST be enclosed in quotes. Currently we do not * support strings with quotes in them. * * Some of the system functions used in this program are Unicode * specific, so this program will require some modifications to run on * Windows 95. * * Author: * DannyGl, 23 Mar 97 */ #include "precomp.h" #include "resource.h" #include #pragma intrinsic(memset) #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) // DEBUG only -- Define debug zone #ifdef DEBUG HDBGZONE ghZone = NULL; // Node Controller Zones static PTCHAR rgZones[] = { TEXT("NMPgmGrp") }; #endif // DEBUG // PROGRAM_ITEM_INFO structure: // // Intended to be passed as input to the CreateProgramItem and // DeleteProgramItem functions. Fields are: // // pszProgramGroup - The full path of the program group in which the // item is to be stored. // pszProgramName - The name of the program item. // pszProgramPath - The full path of the program. typedef struct tagProgramItemInfo { PTSTR pszProgramGroup; PTSTR pszProgramName; PTSTR pszProgramPath; } PROGRAM_ITEM_INFO, *PPROGRAM_ITEM_INFO; // Command line option data enum tagGroupOperation { GRPOP_NONE = 0, GRPOP_ADD, GRPOP_DEL, GRPOP_NTDDINSTALL, GRPOP_NTDDUNINSTALL, GRPOP_SETUP } g_goAction; BOOL g_fCommonGroup = FALSE; PTSTR g_pszGroupName = NULL; PTSTR g_pszProgramName = NULL; PTSTR g_pszProgramPath = NULL; PTSTR g_pszFriendlyName = NULL; BOOL g_fQuietInstall = FALSE; const TCHAR g_cszSetupDll[] = TEXT("advpack.dll"); const TCHAR g_cszSetupEntry[] = TEXT("LaunchINFSection"); typedef int (CALLBACK * PFNSETUPENTRY)(HWND hwnd, HINSTANCE hinst, LPTSTR lpszCmdLine, int nCmdShow); // ProcessCommandLineArgs: // // Get the command line and parse it into individual parameters using the // above global variables. // // Return: TRUE on success, FALSE if it could not parse the command line. BOOL ProcessCommandLineArgs(void) { PTSTR pszTemp; pszTemp = GetCommandLine(); // Search for forward slashes pszTemp = (PTSTR) _StrChr(pszTemp, TEXT('/')); while (NULL != pszTemp) { PTSTR *ppszCurrentArg = NULL; switch(*++pszTemp) { case TEXT('S'): case TEXT('s'): ASSERT(GRPOP_NONE == g_goAction); // Check for duplicate parameter g_goAction = GRPOP_SETUP; break; case TEXT('I'): case TEXT('i'): // // Install NT-specific display driver stuff // ASSERT(GRPOP_NONE == g_goAction); // Check for duplicate parameter g_goAction = GRPOP_NTDDINSTALL; break; case TEXT('U'): case TEXT('u'): // // Uninstall NT-specific display driver stuff // ASSERT(GRPOP_NONE == g_goAction); // Check for duplicate parameter g_goAction = GRPOP_NTDDUNINSTALL; break; case TEXT('A'): case TEXT('a'): ASSERT(GRPOP_NONE == g_goAction); // Check for duplicate parameter g_goAction = GRPOP_ADD; break; case TEXT('D'): case TEXT('d'): ASSERT(GRPOP_NONE == g_goAction); // Check for duplicate parameter g_goAction = GRPOP_DEL; break; case TEXT('C'): case TEXT('c'): ASSERT(! g_fCommonGroup); // Check for duplicate parameter g_fCommonGroup = TRUE; break; case TEXT('Q'): case TEXT('q'): g_fQuietInstall = TRUE; break; case TEXT('G'): case TEXT('g'): if (NULL == ppszCurrentArg) { ppszCurrentArg = &g_pszGroupName; } // NO break HERE -- fall through case TEXT('N'): case TEXT('n'): if (NULL == ppszCurrentArg) { ppszCurrentArg = &g_pszProgramName; } // NO break HERE -- fall through case TEXT('P'): case TEXT('p'): if (NULL == ppszCurrentArg) { ppszCurrentArg = &g_pszProgramPath; } // NO break HERE -- fall through case TEXT('F'): case TEXT('f'): if (NULL == ppszCurrentArg) { ppszCurrentArg = &g_pszFriendlyName; } // ***** Processing for all string parameters ***** ASSERT(NULL == *ppszCurrentArg); // Check for duplicate parameter // Save the string pointer after skipping past the colon and open quote ASSERT(TEXT(':') == pszTemp[1] && TEXT('\"') == pszTemp[2]); *ppszCurrentArg = pszTemp += 3; // Find the closing quote and set it to null, then skip past it // Note that we don't handle strings with quotes in them. pszTemp = (PTSTR) _StrChr(pszTemp, TEXT('\"')); ASSERT(NULL != pszTemp); if (NULL != pszTemp) { *pszTemp++ = TEXT('\0'); } else { return FALSE; } break; default: ERROR_OUT(("Unknown parameter begins at %s", pszTemp)); return FALSE; break; } // Find the next option flag ASSERT(NULL != pszTemp); pszTemp = (PTSTR) _StrChr(pszTemp, TEXT('/')); } // Return based on minimal parameter validation: // 1) The program name must be specified. // 2) Either add or delete must be specified. // 3) If add is specified, the program path must be specified switch (g_goAction) { case GRPOP_ADD: case GRPOP_NTDDINSTALL: return((NULL != g_pszProgramName) && (NULL != g_pszProgramPath)); case GRPOP_DEL: case GRPOP_NTDDUNINSTALL: return(NULL != g_pszProgramName); case GRPOP_SETUP: return((NULL != g_pszProgramName) && (NULL != g_pszFriendlyName)); default: return(FALSE); } } // GetFolderPathname: // // Use the official shell interfaces to retrieve the full pathname of a // a programs folder. // // Input: // ptstrPath, ccPath - The pointer to a size of the buffer in // which to store the path. // nFolder - The folder to locate, expressed as a CSIDL constant. // See SHGetSpecialFolderLocation for details. // pctstrSubFolder - A specific subfolder, can be NULL if not specified. // If specified, this is appended (after a backslash) to the path. // // Returns: // An HRESULT to indicate success or failure of the Shell methods. // The path is returned in . HRESULT GetFolderPathname( PTSTR ptstrPath, UINT cchPath, int nFolder, LPCTSTR pctstrSubFolder) { HRESULT hr; LPMALLOC pMalloc = NULL; LPSHELLFOLDER pDesktopFolder = NULL; LPITEMIDLIST pidlSpecialFolder = NULL; // Get the allocator object hr = CoGetMalloc(MEMCTX_TASK, &pMalloc); // Get the desktop object if (SUCCEEDED(hr)) { hr = SHGetDesktopFolder(&pDesktopFolder); } // Get the special folder item ID if (SUCCEEDED(hr)) { hr = SHGetSpecialFolderLocation( GetDesktopWindow(), nFolder, &pidlSpecialFolder); } // Retrieve the folder name STRRET strFolder; if (SUCCEEDED(hr)) { strFolder.uType = STRRET_WSTR; hr = pDesktopFolder->GetDisplayNameOf( pidlSpecialFolder, SHGDN_FORPARSING, &strFolder); } if (SUCCEEDED(hr)) { CUSTRING custrPath; switch(strFolder.uType) { case STRRET_WSTR: custrPath.AssignString(strFolder.pOleStr); break; case STRRET_OFFSET: custrPath.AssignString(((LPSTR) pidlSpecialFolder) + strFolder.uOffset); break; case STRRET_CSTR: custrPath.AssignString(strFolder.cStr); break; } if(NULL != (PTSTR) custrPath) lstrcpyn(ptstrPath, custrPath, cchPath); else *ptstrPath = _TEXT('\0'); if (STRRET_WSTR == strFolder.uType) { pMalloc->Free(strFolder.pOleStr); } } // Append subgroup name, if it's specified if (SUCCEEDED(hr) && NULL != pctstrSubFolder) { // BUGBUG - We don't create this folder if it doesn't already exist int cchLen = lstrlen(ptstrPath); ASSERT((UINT) cchLen < cchPath); // Insert a path separator ptstrPath[cchLen++] = TEXT('\\'); // Copy the subgroup lstrcpyn(ptstrPath + cchLen, pctstrSubFolder, cchPath - cchLen); } // Release resources if (pDesktopFolder) { pDesktopFolder->Release(); } if (pMalloc) { if (pidlSpecialFolder) { pMalloc->Free(pidlSpecialFolder); } pMalloc->Release(); } return hr; } // BuildLinkFileName: // // Inline utility function to construct the full file name of a link given its // directory name and item name. inline void BuildLinkFileName( OUT LPWSTR wszOutputPath, IN LPCTSTR pcszDirectory, IN LPCTSTR pcszFile) { // The file name is of the form \.LNK #ifdef UNICODE static const WCHAR wszFileFormat[] = L"%s\\%s.LNK"; #else // UNICODE static const WCHAR wszFileFormat[] = L"%hs\\%hs.LNK"; #endif // UNICODE int cchSize; cchSize = wsprintfW( wszOutputPath, wszFileFormat, pcszDirectory, pcszFile); ASSERT(cchSize > ARRAY_ELEMENTS(wszFileFormat) - 1 && cchSize < MAX_PATH); } // CreateProgramItem: // // Use the official shell interfaces to create a shortcut to a program. // // Input: A pointer to a PROGRAM_ITEM_INFO structure, defined above. // // Returns: // An HRESULT to indicate success or failure of the Shell methods. HRESULT CreateProgramItem( PPROGRAM_ITEM_INFO ppii) { HRESULT hr; IShellLink *psl = NULL; IPersistFile *ppf = NULL; // Get the shell link object hr = CoCreateInstance( CLSID_ShellLink, NULL, CLSCTX_INPROC, IID_IShellLink, (LPVOID *) &psl); // Fill in the fields of the program group item if (SUCCEEDED(hr)) { hr = psl->SetDescription(ppii->pszProgramName); } if (SUCCEEDED(hr)) { hr = psl->SetPath(ppii->pszProgramPath); } // Save the link as a file if (SUCCEEDED(hr)) { hr = psl->QueryInterface(IID_IPersistFile, (LPVOID *) &ppf); } if (SUCCEEDED(hr)) { WCHAR wszFileName[MAX_PATH]; BuildLinkFileName( wszFileName, ppii->pszProgramGroup, ppii->pszProgramName); hr = ppf->Save(wszFileName, TRUE); } // Release the objects we used if (ppf) { ppf->Release(); } if (psl) { psl->Release(); } return hr; } // DeleteProgramItem: // // Delete a shortcut to a program. // // Input: A pointer to a PROGRAM_ITEM_INFO structure, defined above. // // Returns: // An HRESULT to indicate success or failure of the Shell methods. HRESULT DeleteProgramItem( PPROGRAM_ITEM_INFO ppii) { HRESULT hr = S_OK; WCHAR wszFileName[MAX_PATH]; BuildLinkFileName( wszFileName, ppii->pszProgramGroup, ppii->pszProgramName); if (! DeleteFileW(wszFileName)) { WARNING_OUT(("DeleteFile failed")); hr = E_FAIL; } return hr; } // // NtDDInstall() // This does NT-specific display driver install stuff, which depends on // whether it's NT4 or NT5 // // HRESULT NtDDInstall(LPTSTR pszOrigDd, LPTSTR pszNewDd) { HRESULT hr = E_FAIL; OSVERSIONINFO osvi; RegEntry re(NM_NT_DISPLAY_DRIVER_KEY, HKEY_LOCAL_MACHINE, FALSE); // // If NT4, set service key to disabled // If NT5, copy mnmdd.dll from NM dir to cur (system32) dir // osvi.dwOSVersionInfoSize = sizeof(osvi); if (!GetVersionEx(&osvi)) { ERROR_OUT(("GetVersionEx() failed")); goto AllDone; } if ((osvi.dwPlatformId == VER_PLATFORM_WIN32s) || (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)) { WARNING_OUT(("NT setup running on non-NT platform!")); goto AllDone; } if (osvi.dwMajorVersion >= 5) { // // This is NT5. Always set the service key to enabled (in case // the end user managed to munge it) and copy mnmdd.dll to the // current (system) directory. For example, if somebody had a // stand-alone version of a beta, uninstalled it, then installed // NM 3.0 proper--or same for 2.11. // re.SetValue(REGVAL_NM_NT_DISPLAY_DRIVER_ENABLED, NT_DRIVER_START_SYSTEM); if (!CopyFile(pszOrigDd, pszNewDd, FALSE)) { WARNING_OUT(("CopyFile from %s to %s failed", pszOrigDd, pszNewDd)); goto AllDone; } } else { // This is NT4. Set the disabled service key re.SetValue(REGVAL_NM_NT_DISPLAY_DRIVER_ENABLED, NT_DRIVER_START_DISABLED); } hr = S_OK; AllDone: return(hr); } // // NtDDUninstall() // This does NT-specific display driver uninstall stuff, which depends // on whether it's NT4 or NT5 // HRESULT NtDDUninstall(LPTSTR pszOrigFile) { HRESULT hr = E_FAIL; OSVERSIONINFO osvi; // // If NT4, set service key to disabled // If NT5, delete mnmdd.dll from cur (system32) dir // osvi.dwOSVersionInfoSize = sizeof(osvi); if (!GetVersionEx(&osvi)) { ERROR_OUT(("GetVersionEx() failed")); goto AllDone; } if ((osvi.dwPlatformId == VER_PLATFORM_WIN32s) || (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)) { ERROR_OUT(("NT setup running on non-NT platform!")); goto AllDone; } if (osvi.dwMajorVersion >= 5) { // This is NT5. Delete mnmdd.dll from the current (system) directory if (!DeleteFile(pszOrigFile)) { WARNING_OUT(("DeleteFile of %s failed", pszOrigFile)); goto AllDone; } } else { // This is NT4. Set the disabled service key RegEntry re(NM_NT_DISPLAY_DRIVER_KEY, HKEY_LOCAL_MACHINE, FALSE); re.SetValue(REGVAL_NM_NT_DISPLAY_DRIVER_ENABLED, NT_DRIVER_START_DISABLED); } hr = S_OK; AllDone: return(hr); } UINT _MessageBox(HINSTANCE hInst, UINT uID, LPCTSTR lpCaption, UINT uType) { TCHAR szText[512]; if (0 != LoadString(hInst, uID, szText, CCHMAX(szText))) { return MessageBox(NULL, szText, lpCaption, uType); } return IDCANCEL; } #define CONF_INIT_EVENT TEXT("CONF:Init") BOOL FIsNetMeetingRunning() { HANDLE hEvent; hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, SERVICE_STOP_EVENT); if (hEvent) { CloseHandle(hEvent); return TRUE; } hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, CONF_INIT_EVENT); if (hEvent) { CloseHandle(hEvent); return TRUE; } return FALSE; } BOOL FIsNT5() { OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); if (GetVersionEx(&osvi)) { if ((osvi.dwPlatformId != VER_PLATFORM_WIN32s) && (osvi.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS)) { if (osvi.dwMajorVersion >= 5) { return TRUE; } } } else { ERROR_OUT(("GetVersionEx() failed")); } return FALSE; } #define IE4_KEY TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Last Update\\IEXPLOREV4") BOOL FIsIE4Installed() { RegEntry re(IE4_KEY, HKEY_LOCAL_MACHINE, FALSE); if (ERROR_SUCCESS != re.GetError()) { return FALSE; } return TRUE; } #define INTEL_KEY1 TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\vphone.exe") #define INTEL_KEY2 TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\rvp.exe") #define INTEL_KEY3 TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\vp30.exe") #define INTEL_NM_VERSION_SZ TEXT("NetMeeting") #define INTEL_NM_VERSION_DW 3 #define PANTHER_KEY TEXT("CLSID\\{690968D0-418C-11D1-8E0B-00A0C95A83DA}\\Version") #define PANTHER_VERSION_VALUE TEXT("1.0") #define TRANSPORTS_KEY TEXT("SOFTWARE\\Microsoft\\Conferencing\\Transports") #define REGKEY_PSTN TEXT("PSTN") #define REGKEY_TCPIP TEXT("TCPIP") #define REGKEY_IPX TEXT("IPX") #define REGKEY_NETBIOS TEXT("NETBIOS") #define REGKEY_DIRCB TEXT("DIRCB") long GetIntelVersion(LPCTSTR pszKey) { RegEntry re(pszKey, HKEY_LOCAL_MACHINE, FALSE); if (ERROR_SUCCESS != re.GetError()) { return -1; } return re.GetNumber(INTEL_NM_VERSION_SZ, 0); } BOOL FAnyBadIntelApps() { long lVersion; lVersion = GetIntelVersion(INTEL_KEY1); if (0 > lVersion) { lVersion = GetIntelVersion(INTEL_KEY2); if (0 > lVersion) { lVersion = GetIntelVersion(INTEL_KEY3); } } return ((0 <= lVersion) && (3 > lVersion)); } BOOL FAnyBadPantherApps() { RegEntry re(PANTHER_KEY, HKEY_CLASSES_ROOT, FALSE); if (ERROR_SUCCESS != re.GetError()) { return FALSE; } LPCTSTR pszVersion = re.GetString(TEXT("")); return 0 == lstrcmp(pszVersion, PANTHER_VERSION_VALUE); } BOOL FAnyUnknownTransports() { RegEntry TransportsKey(TRANSPORTS_KEY, HKEY_LOCAL_MACHINE, FALSE); RegEnumSubKeys EnumTransports(&TransportsKey); while( 0 == EnumTransports.Next() ) { LPCTSTR pszName = EnumTransports.GetName(); if ((0 != lstrcmpi(pszName, REGKEY_PSTN)) && (0 != lstrcmpi(pszName, REGKEY_TCPIP)) && (0 != lstrcmpi(pszName, REGKEY_IPX)) && (0 != lstrcmpi(pszName, REGKEY_NETBIOS)) && (0 != lstrcmpi(pszName, REGKEY_DIRCB))) { return TRUE; } } return FALSE; } BOOL FAnyIncompatibleApps() { return FAnyUnknownTransports() || FAnyBadIntelApps() || FAnyBadPantherApps(); } HRESULT Setup(HINSTANCE hInst, LPTSTR pszInfFile, LPTSTR pszFriendlyName, BOOL fQuietInstall) { if (FIsNT5()) { _MessageBox(hInst, IDS_SETUP_WIN2K, pszFriendlyName, MB_OK); // if the SHFT-CTRL was pressed continue with the install, else exit if ((0 == GetAsyncKeyState(VK_CONTROL)) || (0 == GetAsyncKeyState(VK_SHIFT))) { return S_FALSE; } } if (!FIsIE4Installed()) { _MessageBox(hInst, IDS_SETUP_IE4, pszFriendlyName, MB_OK); return S_FALSE; } while (FIsNetMeetingRunning()) { if (IDCANCEL == _MessageBox(hInst, IDS_SETUP_RUNNING, pszFriendlyName, MB_OKCANCEL)) { return S_FALSE; } } if (!fQuietInstall) { if (FAnyIncompatibleApps()) { if (IDNO == _MessageBox(hInst, IDS_SETUP_INCOMPATIBLE, pszFriendlyName, MB_YESNO)) { return S_FALSE; } } } HRESULT hr = S_FALSE; HINSTANCE hLib = NmLoadLibrary(g_cszSetupDll,TRUE); if (NULL != hLib) { PFNSETUPENTRY pfnEntry = (PFNSETUPENTRY)GetProcAddress(hLib, g_cszSetupEntry); if (pfnEntry) { TCHAR szArgs[MAX_PATH]; lstrcpyn(szArgs, pszInfFile, ARRAYSIZE(szArgs)); if (fQuietInstall) { lstrcat(szArgs, TEXT(",,1,N")); } else { lstrcat(szArgs, TEXT(",,,N")); } int iRet = pfnEntry(NULL, GetModuleHandle(NULL), szArgs, SW_SHOWNORMAL); if (0 == iRet) { if (!fQuietInstall) { _MessageBox(hInst, IDS_SETUP_SUCCESS, pszFriendlyName, MB_OK); } hr = S_OK; } } else { ERROR_OUT(("Could not find setup DLL entry point")); } FreeLibrary(hLib); } else { ERROR_OUT(("Could not load setup DLL")); } return hr; } // main: // // The entry point of the program, it pulls everything together using the // above utility functions. void __cdecl main( void) { HRESULT hr; HINSTANCE hInstance; BOOL fErrorReported = FALSE; TCHAR szFolderPath[MAX_PATH]; // Initialization hInstance = GetModuleHandle(NULL); DBGINIT(&ghZone, rgZones); DBG_INIT_MEMORY_TRACKING(hInstance); hr = CoInitialize(NULL); // Process the command line. if (SUCCEEDED(hr)) { hr = ProcessCommandLineArgs() ? S_OK : E_INVALIDARG; } else if (!fErrorReported) { ERROR_OUT(("CoInitialize fails")); fErrorReported = TRUE; } // Retreive the path of the Programs folder if (SUCCEEDED(hr)) { if ((g_goAction != GRPOP_NTDDINSTALL) && (g_goAction != GRPOP_NTDDUNINSTALL) && (g_goAction != GRPOP_SETUP)) { hr = GetFolderPathname( szFolderPath, CCHMAX(szFolderPath), g_fCommonGroup ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS, g_pszGroupName); } } else if (!fErrorReported) { ERROR_OUT(("Invalid command line parameters specified.")); fErrorReported = TRUE; } // Add or delete the program item, as appropriate if (SUCCEEDED(hr)) { PROGRAM_ITEM_INFO pii; switch(g_goAction) { case GRPOP_NTDDINSTALL: // // Hack: Use program name for source mnmdd.dll // Use program path for dest mnmdd.dll // hr = NtDDInstall(g_pszProgramName, g_pszProgramPath); break; case GRPOP_NTDDUNINSTALL: // // Hack: Use program name for source mnmdd.dll // hr = NtDDUninstall(g_pszProgramName); break; case GRPOP_ADD: pii.pszProgramGroup = szFolderPath; pii.pszProgramName = g_pszProgramName; pii.pszProgramPath = g_pszProgramPath; hr = CreateProgramItem(&pii); break; case GRPOP_DEL: pii.pszProgramGroup = szFolderPath; pii.pszProgramName = g_pszProgramName; hr = DeleteProgramItem(&pii); break; case GRPOP_SETUP: hr = Setup(hInstance, g_pszProgramName, g_pszFriendlyName, g_fQuietInstall); break; default: ERROR_OUT(("No operation type specified")); hr = E_INVALIDARG; break; } } else if (!fErrorReported) { ERROR_OUT(("GetFolderPathname returns %lu", hr)); fErrorReported = TRUE; } // Process cleanup CoUninitialize(); DBG_CHECK_MEMORY_TRACKING(hInstance); DBGDEINIT(&ghZone); ExitProcess(SUCCEEDED(hr) ? 0 : 2); }