/*++ Copyright (c) 1995 Microsoft Corporation Module Name: progman.c Abstract: Routines to manipulate program groups and items. Entry points: CreateInitialProgmanItems Author: Ted Miller (tedm) 5-Apr-1995 Revision History: Based on various other code that has been rewritten/modified many times by many people. --*/ #include "setupp.h" #pragma hdrstop #define DDEEXECUTE_TIMEOUT 20000 #define WAITINPUTIDLE_TIMEOUT 120000 #define TERMINATE_TIMEOUT 10000 // // DDE App and Topic name. // PCWSTR szProgman = L"PROGMAN"; HCONV ProgmanConversation = NULL; HWND ProgmanWindow = NULL; HANDLE ProgmanProcess = NULL; BOOL DdeInitialized = FALSE; DWORD idDDEMLInst = 0; BOOL ExecuteDdeCommand( IN PCWSTR Command ); #define DDEDBG #ifdef DDEDBG #define DBGOUT(x) DbgOut x VOID DbgOut( IN PCSTR FormatString, ... ) { CHAR Str[256]; va_list arglist; wsprintfA(Str,"SETUP (%u): ",GetTickCount()); OutputDebugStringA(Str); va_start(arglist,FormatString); wvsprintfA(Str,FormatString,arglist); va_end(arglist); OutputDebugStringA(Str); OutputDebugStringA("\n"); } #else #define DBGOUT(x) #endif HDDEDATA CALLBACK DdeCallback( IN UINT wType, IN UINT wFmt, IN HCONV hConv, IN HSZ hsz1, IN HSZ hsz2, IN HDDEDATA hData, IN DWORD dwData1, IN DWORD dwData2 ) { UNREFERENCED_PARAMETER(wType); UNREFERENCED_PARAMETER(wFmt); UNREFERENCED_PARAMETER(hConv); UNREFERENCED_PARAMETER(hsz1); UNREFERENCED_PARAMETER(hsz2); UNREFERENCED_PARAMETER(hData); UNREFERENCED_PARAMETER(dwData1); UNREFERENCED_PARAMETER(dwData2); return(0); } BOOL EndProgmanDde( VOID ) { DWORD d; DBGOUT(("EndProgmanDde: enter")); if(ProgmanConversation) { // // Only send exit command if we actually executed progman // with CreateProcess() (as opposed to the case where // it was already running when we came along). // if(ProgmanProcess) { DBGOUT(("EndProgmanDde: terminating progman...")); // // The exit command we send specifies to save state on exit. // ExecuteDdeCommand(L"[exitprogman(1)]"); // // Wait for progman to die. Want to let everything get cleaned up // before we enter another install section and attempt additional // program group/item operations. // // Note that if the timeout fails, there's nothing meaningful we can do // about it. We don't want Setup to hang. // d = WaitForSingleObject(ProgmanProcess,TERMINATE_TIMEOUT); if(d != WAIT_OBJECT_0) { DBGOUT(( "EndProgmanDde: problem waiting for progman to die, wait returned %u, last err = %u", d, GetLastError() )); } if(IsWindow(ProgmanWindow)) { DBGOUT(("SETUP: Warning: progman process is dead but its window lives on")); } // // Progman process is gone as far as we are concerned. // CloseHandle(ProgmanProcess); ProgmanProcess = NULL; } else { // // Just do a disconnect. // if(DdeDisconnect(ProgmanConversation)) { DBGOUT(("EndProgmanDde: DdeDisconnect success")); } else { DBGOUT(("EndProgmanDde: DdeDisconnect failure (%lx)",DdeGetLastError(idDDEMLInst))); } } // // Conversation is ended. // ProgmanConversation = NULL; } else { DBGOUT(("EndProgmanDde: No conversation to end")); } // // Forget the progman window handle. // ProgmanWindow = NULL; // // Uninitialize DDE. // if(DdeInitialized) { if(DdeUninitialize(idDDEMLInst)) { DBGOUT(("EndProgmanDde: DdeUninitialize success")); } else { DBGOUT(("EndProgmanDde: DdeUninitialize failure")); } idDDEMLInst = 0; DdeInitialized = FALSE; } else { DBGOUT(("EndProgmanDde: Dde not intitialized")); } DBGOUT(("EndProgmanDde: exit success")); return(TRUE); } BOOL ActivateProgmanWorker( VOID ) /*++ Routine Description: Perform a single DDE transaction, sending a dde command to progman. Assumes that a valid conversation with progman exists. Arguments: Command - supplies the command to be sent to progman. Return Value: TRUE if the transaction succeeeded and was acknowledged by progman. FALSE if not or if there is no valid conversation with progman. --*/ { HSZ hszApp; HSZ hszTopic; DWORD dw; MSG rMsg; CONVINFO ci; STARTUPINFO si; PROCESS_INFORMATION pi; BOOL b; DBGOUT(("ActivateProgman: Enter")); // // If dde has not been started, start it. // if(!DdeInitialized) { DBGOUT(("ActivateProgman: DDE not started yet...")); // // We shouln't have a dde instance yet! // MYASSERT(idDDEMLInst == 0); if((dw = DdeInitialize(&idDDEMLInst,DdeCallback,APPCMD_CLIENTONLY,0)) == DMLERR_NO_ERROR) { DBGOUT(("ActivateProgman: DDE started successfully; DDEML id = %lx",idDDEMLInst)); DdeInitialized = TRUE; // // Since we just initialized DDE, there shouldn't be a conversation // going on yet and we shouldn't have progman's process or window handle. // MYASSERT(ProgmanConversation == NULL); MYASSERT(ProgmanProcess == NULL); MYASSERT(ProgmanWindow == NULL); } else { DBGOUT(("ActivateProgman: error %u from DdeInitialize!",dw)); return(FALSE); } } // // Find out if a connection has already been established with progman. // If not try to connect. // if(!ProgmanConversation) { DBGOUT(("ActivateProgman: no conversation yet...")); // // If progman is already running in the system, we should be able to // connect with it -- no need to call CreateProcess(). // hszApp = DdeCreateStringHandle(idDDEMLInst,szProgman,0); hszTopic = DdeCreateStringHandle(idDDEMLInst,szProgman,0); ProgmanConversation = DdeConnect(idDDEMLInst,hszApp,hszTopic,NULL); if(ProgmanConversation) { DBGOUT(("ActivateProgman: first DdeConnect succeded")); } else { WCHAR wszProgman[] = L"PROGMAN /NTSETUP"; DBGOUT(("ActivateProgman: first DdeConnect failed (%lx); attempting to invoke progman",DdeGetLastError(idDDEMLInst))); // // Connect failed -- try to run progman. // Initialize startup info and call CreateProcess(). // si.cb = sizeof(STARTUPINFO); si.lpReserved = NULL; si.lpDesktop = NULL; si.lpTitle = NULL; si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL; si.lpReserved2 = NULL; si.cbReserved2 = 0; b = CreateProcess( NULL, // lpApplicationName wszProgman, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes DETACHED_PROCESS, // dwCreationFlags FALSE, // bInheritHandles NULL, // lpEnvironment NULL, // lpCurrentDirectory &si, // lpStartupInfo &pi // lpProcessInformation ); if(b) { DBGOUT(("ActivateProgman: CreateProcess succeded")); // // Exec was successful. // Close thread handle immediately and save process handle. // CloseHandle(pi.hThread); ProgmanProcess = pi.hProcess; if(dw = WaitForInputIdle(ProgmanProcess,WAITINPUTIDLE_TIMEOUT)) { // // Timeout or error. Close the process handle and forget it, // so we don't try to clean it up when dde is terminated. // The user will see an error, and retry should eventually get // things going. // CloseHandle(ProgmanProcess); ProgmanProcess = NULL; DBGOUT(("ActivateProgman: WaitForInputIdle() timed out!")); } else { // // Empty the message queue till no messages // are left in the queue or till WM_ACTIVATEAPP is processed. Then // try connecting to progman. I am using PeekMessage followed // by GetMessage because PeekMessage doesn't remove some messages // ( WM_PAINT for one ). // while(PeekMessage(&rMsg,NULL,0,0,PM_NOREMOVE) && GetMessage(&rMsg,NULL,0,0)) { TranslateMessage(&rMsg); DispatchMessage(&rMsg); if(rMsg.message == WM_ACTIVATEAPP) { break; } } // // Now try to connect. // if(ProgmanConversation = DdeConnect(idDDEMLInst,hszApp,hszTopic,NULL)) { DBGOUT(("ActivateProgman: second DdeConnect succeded")); } else { DBGOUT(("ActivateProgman: second DdeConnect failed (%lx)",DdeGetLastError(idDDEMLInst))); } } } else { DBGOUT(("ActivateProgman: CreateProcess failed (%u)",GetLastError())); } } DdeFreeStringHandle(idDDEMLInst,hszApp); DdeFreeStringHandle(idDDEMLInst,hszTopic); // // If we've connected, restore progman window if iconic and // bring progman window to foreground. // if(ProgmanConversation) { ci.cb = sizeof(CONVINFO); if(ProgmanWindow = (DdeQueryConvInfo(ProgmanConversation,QID_SYNC,&ci) ? ci.hwndPartner : NULL)) { DBGOUT(("ActivateProgman: Progman Window = %lx",ProgmanWindow)); if(IsIconic(ProgmanWindow)) { ShowWindow(ProgmanWindow,SW_RESTORE); } SetForegroundWindow(ProgmanWindow); } else { DBGOUT(("ActivateProgman: unable to determine progman Window (%lx)",DdeGetLastError(idDDEMLInst))); } } } DBGOUT(("ActivateProgman: exit %s",ProgmanConversation ? "success" : "failure")); return(ProgmanConversation != NULL); } BOOL ActivateProgman( VOID ) { // // Try twice. // if(ActivateProgmanWorker()) { return(TRUE); } else { Sleep(1000); return(ActivateProgmanWorker()); } } BOOL ExecuteDdeCommandWorker( IN PCSTR Command ) /*++ Routine Description: Perform a single DDE transaction, sending a dde command to progman. Assumes that a valid conversation with progman exists. Arguments: Command - supplies the command to be sent to progman. Return Value: TRUE if the transaction succeeeded and was acknowledged by progman. FALSE if not or if there is no valid conversation with progman. --*/ { DWORD dwResult; HDDEDATA hResult; DBGOUT(("ExecuteDdeCommand: enter: %s",Command)); // // Ensure that we have a valid conversation with progman. // if(!ProgmanConversation) { DBGOUT(("ExecuteDdeCommand: exit -- no conversation!")); return(FALSE); } // // Send the command and process the result. // hResult = DdeClientTransaction( (PSTR)Command, lstrlenA(Command) + 1, ProgmanConversation, 0, 0, XTYP_EXECUTE, DDEEXECUTE_TIMEOUT, &dwResult ); if(hResult) { DBGOUT(("ExecuteDdeCommand: DdeClientTransaction succeeded, result=%lx",dwResult)); } else { DBGOUT(("ExecuteDdeCommand: DdeClientTransaction failed (%lx)",DdeGetLastError(idDDEMLInst))); } DBGOUT(( "ExecuteDdeCommand: exit %s", (hResult ? ((dwResult & DDE_FACK) != 0) : FALSE) ? "success" : "failure" )); return(hResult ? ((dwResult & DDE_FACK) != 0) : FALSE); } BOOL ExecuteDdeCommand( IN PCWSTR Command ) { PCSTR AnsiCommand; BOOL b; b = FALSE; AnsiCommand = UnicodeToAnsi(Command); if(AnsiCommand) { // // Try twice. // b = ExecuteDdeCommandWorker(AnsiCommand); if(!b) { Sleep(250); b = ExecuteDdeCommandWorker(AnsiCommand); } MyFree(AnsiCommand); } if(!b) { LogItem0(LogSevWarning,MSG_LOG_PROGMAN_DDEFAIL,Command); } return(b); } BOOL CreateProgmanGroup( IN PCWSTR Group, IN PCWSTR Path, OPTIONAL IN BOOL CommonGroup ) { PCWSTR CmdBase = L"[CreateGroup(%s%s%s,%s)]"; PWSTR Command; BOOL b; b = FALSE; if(Path == NULL) { Path = L""; } if(!ActivateProgman()) { LogItem2( LogSevError, MSG_LOG_PROGMAN_CREATGRP_FAIL, Group, MSG_LOG_ACTIVATEPROGMAN ); goto err0; } Command = MyMalloc((lstrlen(CmdBase)+lstrlen(Group)+lstrlen(Path))*sizeof(WCHAR)); if(!Command) { LogItem2( LogSevError, MSG_LOG_PROGMAN_CREATGRP_FAIL, Group, MSG_LOG_OUTOFMEMORY ); goto err0; } wsprintf( Command, CmdBase, Group, *Path ? L"," : Path, Path, CommonGroup ? L"1" : L"0" ); b = ExecuteDdeCommand(Command); MyFree(Command); err0: return(b); } BOOL RemoveProgmanGroup( IN PCWSTR Group, IN BOOL CommonGroup ) { PCWSTR CmdBase = L"[DeleteGroup(%s,%s)]"; BOOL b; PWSTR Command; b = FALSE; if(!ActivateProgman()) { LogItem2( LogSevError, MSG_LOG_PROGMAN_REMGRP_FAIL, Group, MSG_LOG_ACTIVATEPROGMAN ); goto err0; } Command = MyMalloc((lstrlen(CmdBase)+lstrlen(Group))*sizeof(WCHAR)); if(!Command) { LogItem2( LogSevError, MSG_LOG_PROGMAN_REMGRP_FAIL, Group, MSG_LOG_OUTOFMEMORY ); goto err0; } wsprintf(Command,CmdBase,Group,CommonGroup ? "1" : "0"); b = ExecuteDdeCommand(Command); MyFree(Command); err0: return(b); } BOOL ShowProgmanGroup( IN PCWSTR Group, IN UINT ShowCommand, IN BOOL CommonGroup ) { PCWSTR CmdBase = L"[ShowGroup(%s, %s,%s)]"; PWSTR Command; BOOL b; WCHAR showCommand[24]; b = FALSE; if(!ActivateProgman()) { LogItem2( LogSevError, MSG_LOG_PROGMAN_SHOWGRP_FAIL, Group, MSG_LOG_ACTIVATEPROGMAN ); goto err0; } wsprintf(showCommand,L"%u",ShowCommand); Command = MyMalloc((lstrlen(CmdBase)+lstrlen(Group)+lstrlen(showCommand))*sizeof(WCHAR)); if(!Command) { LogItem2( LogSevError, MSG_LOG_PROGMAN_SHOWGRP_FAIL, Group, MSG_LOG_OUTOFMEMORY ); goto err0; } wsprintf(Command,CmdBase,Group,showCommand,CommonGroup ? L"1" : L"0"); b = ExecuteDdeCommand(Command); MyFree(Command); err0: return(b); } BOOL CreateProgmanItem( IN PCWSTR Group, IN PCWSTR Item, IN PCWSTR Cmd, IN PCWSTR IconFile, IN INT IconNum, IN BOOL CommonGroup, IN BOOL CreateGroupFirst ) { PCWSTR CmdBase = L"[AddItem(%s, %s, %s, %d)]"; UINT Size; PWSTR Command; BOOL b; // // Create the group first if necessary. // if(CreateGroupFirst) { if(!CreateProgmanGroup(Group,NULL,CommonGroup)) { return(FALSE); } } else { if(!ActivateProgman()) { LogItem3( LogSevError, MSG_LOG_PROGMAN_CREATITEM_FAIL, Group, Item, MSG_LOG_ACTIVATEPROGMAN ); return(FALSE); } } b = FALSE; Size = (lstrlen(CmdBase) + lstrlen(Item) + lstrlen(Cmd) + lstrlen(IconFile) + 20) * sizeof(WCHAR); Command = MyMalloc(Size); if(!Command) { LogItem3( LogSevError, MSG_LOG_PROGMAN_CREATITEM_FAIL, Group, Item, MSG_LOG_OUTOFMEMORY ); goto err0; } wsprintf(Command,CmdBase,Cmd,Item,IconFile,IconNum+666); b = ExecuteDdeCommand(Command); MyFree(Command); err0: return(b); } BOOL RemoveProgmanItem( IN PCWSTR Group, IN PCWSTR Item, IN BOOL CommonGroup ) { PCWSTR CmdBase = L"[DeleteItem(%s)]"; PWSTR Command; BOOL b; // // Create the group first. // if(!CreateProgmanGroup(Group,NULL,CommonGroup)) { return(FALSE); } b = FALSE; Command = MyMalloc((lstrlen(CmdBase)+lstrlen(Item))*sizeof(WCHAR)); if(!Command) { LogItem3( LogSevError, MSG_LOG_PROGMAN_REMITEM_FAIL, Group, Item, MSG_LOG_OUTOFMEMORY ); goto err0; } wsprintf(Command,CmdBase,Item); b = ExecuteDdeCommand(Command); MyFree(Command); err0: return(b); } BOOL AddItemsToProgmanGroup( IN HINF InfHandle, IN PCWSTR GroupDescription, IN PCWSTR SectionName, IN BOOL Upgrade ) { INFCONTEXT InfContext; UINT LineCount,LineNo; PCWSTR Description; PCWSTR Binary; PCWSTR CommandLine; PCWSTR IconFile; PCWSTR IconNumberStr; PCWSTR UpgradeStr; INT IconNumber; BOOL b; BOOL DoItem; WCHAR Dummy; PWSTR FilePart; // // Get the number of lines in the section. The section may be empty // or non-existant; this is not an error condition. // LineCount = (UINT)SetupGetLineCount(InfHandle,SectionName); if((LONG)LineCount <= 0) { return(TRUE); } b = TRUE; for(LineNo=0; LineNo