DOINST.C
/******************************************************************************\ 
*       This is a part of the Microsoft Source Code Samples.  
*       Copyright 1995 - 1998 Microsoft Corporation. 
*       All rights reserved.  
*       This source code is only intended as a supplement to  
*       Microsoft Development Tools and/or WinHelp documentation. 
*       See these sources for detailed information regarding the  
*       Microsoft samples programs. 
\******************************************************************************/ 
 
#include <windows.h>    // includes basic windows functionality 
#include <string.h>     // includes the string functions 
#include <shlobj.h>     // To update the desktop 
#include <regstr.h> 
 
#include "setupapi.h"   // includes the inf setup api  
#include "instwiz.h"    // includes the application-specific information 
#include "infinst.h"    // includes the application-specific information 
#include "infdesc.h"    // includes the specifics of how this 
                        // inf is layed out, like the inf names 
                        // the HKeys and the DirIds 
 
DWORD InstallFinish(BOOL DoRunOnce); 
 
// 
//    FUNCTION: BOOL DoInstallation( HWND hWnd, INSTALLINFO * si ) 
// 
//    PURPOSE: Install components via setupapi.dll. 
// 
//   COMMENTS: 
//     
//      The function does inf install operations based 
//      on the content of the INSTALLINFO data. 
//      These steps could be done during the wizard but 
//      this technique allows for installations with user input. 
// 
// This routine will take a INSTALLINFO 
// and do an installation based on those settings 
// using the setupapi.dll 
 
BOOL DoInstallation( HWND hWnd, INSTALLINFO * si ) 
{ 
 
    HINF hInf; 
    char szSourcePath[MAX_PATH]; 
    char szInfFileName[MAX_PATH]; 
     
    DWORD dwResult; 
    BOOL bResult = FALSE; 
 
    // Context for my call back routine 
    MY_INSTALL_DATA MyInstallData; 
 
    HSPFILEQ FileQueue; 
 
    // 
    // First we setup the inf to what the wizard collected 
    // note the hInf will maintain the information and let 
    // you build copy list as you go 
    // This sample seperates the wizard from the setapi stuff 
    // just to keep the setupapi stuff in one place 
    // 
 
    // 
    // The install process overview is:  
    //   TASK                       SETUPAPI 
    //   open a specific inf        SetupOpenInfFile 
    //   call the wizard for input  CreateWizard (implemented in instwiz.c) 
    //   set the directory ids      SetupSetDirectoryId  
    //   create a file queue        SetupOpenFileQueue 
    //   create a queue context     SetupInitDefaultQueueCallback 
    //   add files to queue         SetupInstallFilesFromInfSection 
    //   do the copy                SetupCommitFileQueue 
    //???   do the registry stuff      SetupInstallFromInfSection 
    //   close the queue            SetupTermDefaultQueueCallback 
    //   close the inf              SetupCloseFileQueue 
     
    // 
    // In this sample we assume the inf is in the base of the 
    // base installation source path--it usually is for most installs 
    // 
     
    GetModuleFileName(NULL, szSourcePath, _MAX_PATH); 
    *(strrchr(szSourcePath, '\\') + 1) = '\0';        // Strip setup.exe off path 
 
    strcpy(szInfFileName, szSourcePath); 
    strcat(szInfFileName, "product.inf"); 
 
    // 
    // Get inf handle 
    // must know where the inf is located  
    // SetupOpenInfFile will only look in windows\inf by default 
    // 
 
    hInf = SetupOpenInfFile (  
        szInfFileName,       // If path,needs full path, else looks in %windir%\inf 
        NULL,                // Inf Type, matches Class in [Version] section SetupClass=SAMPLE 
        INF_STYLE_WIN4,      // or INF_STYLE_OLDNT 
        NULL                 // Line where error occurs if inf is has a problem 
        ); 
 
    if (hInf == INVALID_HANDLE_VALUE)  
    { 
        dwResult = GetLastError(); 
 
        //    
        // TODO: handle case where inf cannot be opened 
        //       by asking the user to locate the inf 
     
        return FALSE; 
    } 
 
    // Run the wizard 
    if (CreateWizard(hWnd, si->hInst)) 
    { 
        RuntimeRegistration(si); 
    } 
    else 
    { 
        SetupCloseInfFile(hInf); 
        return FALSE; 
    } 
  
    // 
    // Special case the uninstall destitation path 
    // this is because we need to get the registry value 
    // for where to delete the files from.  
    // 
 
    if (IDC_INSTALL_TYPE_UNINSTALL == si->iInstall_Type) 
    { 
        // get path installed to here! 
        // query registry for si->pszDestPath 
 
        GetRegString (MYPRODUCT_KEY, TEXT("DestinationPath"), 
                si->pszDestPath); 
    } 
 
    // 
    // Directory ids are set per HINF 
    // 
    // TODO: verify dest path as a valid file name 
 
    bResult = SetupSetDirectoryId(hInf,          // the HINF that will hold these ids 
                        (DWORD) DESTINATION_DIR, // the id that matchs one in the inf 
                        si->pszDestPath);        // the user's string 
     
    if(!bResult)  
    { 
        dwResult = GetLastError(); 
         
        // TODO: handle case where we cannot set dirids, should we default or bail? 
        //       perhaps write this out to an error log 
        //  
        // Close the inf file and return 
        // 
 
        SetupCloseInfFile(hInf); 
        return FALSE; 
    } 
 
    // 
    // Create a Setup file queue and initialize the default Setup 
    // queue callback routine. 
    // 
 
    FileQueue = SetupOpenFileQueue(); 
 
    if(!FileQueue || (FileQueue == INVALID_HANDLE_VALUE))  
    { 
        dwResult = ERROR_NOT_ENOUGH_MEMORY; 
         
        // 
        // Close the queue and return 
        // 
 
        SetupCloseInfFile(hInf); 
        return FALSE; 
    } 
 
    // 
    // This initialize memory for the default call back 
    // This context MUST be used if our private callback routine  
    //     wants to callback into the default routine, we do it here with  
    //     a global value for the default context 
    // 
    // We want to use the default progress dialog. 
    // We could override this by specifying a window that 
    // will handle the ui in param 2.   
    // By not passing a progress HWND this is the same as  
    // using SetupInitDefaultQueueCallback. 
    // 
 
    MyInstallData.pDefaultContext = SetupInitDefaultQueueCallbackEx( 
                    hWnd,  // HWND of owner window 
                    NULL,  // HWND of alternate progress dialog which receives -- for example 
                           //      if you wanted to display your progress bar right in the wizard 
                    0,     // Message sent to above window indicating a progress message 
                    0,     // DWORD Reserved 
                    NULL   // PVOID Reserved 
                    ); 
 
    if(!(MyInstallData.pDefaultContext))  
    { 
        dwResult = ERROR_NOT_ENOUGH_MEMORY; 
 
        // 
        // Close the queue and the inf file and return 
        // 
 
        SetupCloseFileQueue(FileQueue); 
        SetupCloseInfFile(hInf); 
        return FALSE; 
    } 
 
    // 
    // Now that we have a FileQueue, a Queue Context, and an HINF 
    // we want to map the DirectoryIds in the INF to the ones 
    // obtained by the user  
 
    // 
    // Queue file operations and commit the queue. 
    // 
 
    // 
    // Install each option they have selected 
    // 
 
    if (IDC_INSTALL_TYPE_UNINSTALL == si->iInstall_Type) 
    { 
        //TODO: allow the user to remove specific components 
        //      note you would want to update the wizard to reflect this 
         
        bResult = SetupInstallFilesFromInfSection( 
                  hInf,     // HINF that has the directory ids set above 
                  NULL,          // layout.inf if you have one, this a convient 
                                 //     place to do all of your file to media id mapping 
                  FileQueue,     // Queue to add files to 
                  TEXT("MyProgramUninstall"),   // SectionName, 
                  szSourcePath,    // Path where the source files are located 
                  SP_COPY_NEWER);// The controls how to version check 
                                 // and how to prompt 
    } 
    else 
    { 
 
      if (si->iCustom_Options1){ 
        bResult = SetupInstallFilesFromInfSection( 
                      hInf,     // HINF that has the directory ids set above 
                      NULL,          // layout.inf if you have one, this a convient 
                                     //     place to do all of your file to media id mapping 
                      FileQueue,     // Queue to add files to 
                      INF_OPTION1,   // SectionName, 
                      szSourcePath,    // Path where the source files are located 
                      SP_COPY_NEWER);// The controls how to version check 
                                     // and how to prompt 
        } 
         
        if (si->iCustom_Options2) { 
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,  
                FileQueue, INF_OPTION2, szSourcePath, SP_COPY_NEWER ); 
        } 
 
        if (si->iCustom_Options3) { 
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,  
                FileQueue, INF_OPTION3, szSourcePath, SP_COPY_NEWER ); 
        } 
 
        if (si->iCustom_Options4) { 
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,  
                FileQueue, INF_OPTION4, szSourcePath, SP_COPY_NEWER ); 
        } 
    } 
 
    // 
    // All the files for each component are now in one queue 
    // now we commit it to start the copy ui, this way the 
    // user has one long copy progress dialog--and for a big install 
    // can go get the cup of coffee  
    // 
    if(bResult) { 
         
        bResult = SetupCommitFileQueue( 
                hWnd,                      // Owner 
                FileQueue,                 // Queue with the file list 
                (PSP_FILE_CALLBACK) MyQueueCallback,  
                                           // This is our handler, it calls the default for us 
                                           // NOTE: 
                                           // (PSP_FILE_CALLBACK) SetupDefaultQueueCallback 
                                           // would use the default message handler automatically 
                &MyInstallData             // Pointer to resources allocated with SetupInitDefaultQueueCallback/Ex                  
                ); 
 
        dwResult = bResult ? NO_ERROR : GetLastError(); 
 
    } else { 
         
        dwResult = GetLastError(); 
     
    } 
 
    // 
    // Do registry munging, etc.  
    // NOTE: you can do the entire install 
    // for a section with this api but in this case 
    // we build the file list conditionally and 
    // do only out ProductInstall section for registy stuff 
    // Also using SPINST_FILES will do the files 
    // as above but only one section at a time 
    // so the progress bar would keep completing and starting over 
    // SPINST_ALL does files, registry and inis 
    //  
    if (si->iCustom_Options1) 
    { 
      bResult = SetupInstallFromInfSection( 
            hWnd, 
            hInf, 
            INF_OPTION1, 
            SPINST_REGISTRY | SPINST_INIFILES, 
            NULL, 
            NULL,//szSourcePath,    // Path where the source files are located 
            0,//SP_COPY_NEWER, 
            NULL,//(PSP_FILE_CALLBACK) MyQueueCallback,  
            NULL,//&MyInstallData, 
            NULL,  
            NULL 
            ); 
    } 
 
    // 
    // Perserve first non-success error code. 
    // 
    if(!bResult && (dwResult == NO_ERROR)) { 
     
        dwResult = GetLastError(); 
     
    } 
 
    // 
    // We're done so free the context, close the queue, 
    // and release the inf handle 
    // 
 
    SetupTermDefaultQueueCallback(MyInstallData.pDefaultContext); 
    SetupCloseFileQueue(FileQueue); 
    SetupCloseInfFile(hInf); 
     
    if(dwResult != NO_ERROR) { 
     
        // TODO: Log an error here 
     
    } 
 
    if (bResult) { 
     
        // do whatever you need to complete a successful install 
 
        // 
        // Refresh the desktop for folders and icons. 
        // 
        InstallFinish(TRUE);     
 
    } else { 
 
        MessageBox( hWnd,  
            TEXT("Installation script did not complete successfully!"),  
            TEXT("Product Sample Install Error"),  
            MB_OK); 
    } 
     
    return TRUE; 
} 
 
LRESULT  
WINAPI 
MyQueueCallback ( 
    IN MY_INSTALL_DATA* pMyInstallData, 
    IN UINT Notification, 
    IN UINT Param1, 
    IN UINT Param2 
    ) 
{ 
    if (SPFILENOTIFY_DELETEERROR == Notification) 
    { 
        // Skip any file delete errors 
        // this sample only deletes files on an unintall 
        // so if the delete encounters an error simply skip the operation 
        // and continue processing the queue 
        return FILEOP_SKIP; 
    } 
    else 
    { 
        // Pass all other notifications through without modification 
        return SetupDefaultQueueCallback(pMyInstallData->pDefaultContext,  
                                         Notification, Param1, Param2); 
    } 
} 
 
 
DWORD 
InstallFinish( 
    IN BOOL DoRunOnce 
    ) 
/*++ 
 
Routine Description: 
 
    This routine sets up runonce/grpconv to run after a successful INF installation. 
 
Arguments: 
 
    DoRunOnce - If TRUE, then invoke (via WinExec) the runonce utility to perform the 
        runonce actions.  If this flag is FALSE, then this routine simply sets the 
        runonce registry values and returns. 
 
        NOTE:  The return code from WinExec is not currently being checked, so the return 
        value of InstallStop only reflects whether the registry values were set up 
        successfully--_not_ whether 'runonce -r' was successfully run. 
 
Return Value: 
 
    If successful, the return value is NO_ERROR, otherwise it is the Win32 error code 
    indicating the error that was encountered. 
 
--*/ 
{ 
    HKEY  hKey, hSetupKey; 
    DWORD Error; 
    LONG l; 
 
    // 
    // First, open the key "HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce" 
    // 
    if((l = RegOpenKeyEx(HKEY_LOCAL_MACHINE,  
                         REGSTR_PATH_RUNONCE,  
                         0, KEY_ALL_ACCESS, &hKey)) != ERROR_SUCCESS) { 
        return (DWORD)l; 
    } 
 
    // 
    // If we need to run the runonce exe for the setup key... 
    // 
    if(RegOpenKeyEx(hKey, 
                    TEXT("Setup"), 
                    0, 
                    KEY_READ, 
                    &hSetupKey) == ERROR_SUCCESS) { 
        // 
        // We don't need the key--we just needed to check its existence. 
        // 
        RegCloseKey(hSetupKey); 
 
        // 
        // Add the runonce value. 
        // 
        Error = (DWORD)RegSetValueEx(hKey, 
                                     TEXT("Wrapper"), 
                                     0, 
                                     REG_SZ, 
                                     TEXT("runonce"), 
                                     sizeof(TEXT("runonce")) 
                                    ); 
    } else { 
        // 
        // We're OK so far. 
        // 
        Error = NO_ERROR; 
    } 
 
    // 
    // GroupConv is always run. 
    // 
    if(RegSetValueEx(hKey, 
                     TEXT("GrpConv"), 
                     0, 
                     REG_SZ, 
                     TEXT("grpconv -o"), 
                     sizeof(TEXT("grpconv -o"))) != ERROR_SUCCESS) { 
        // 
        // Since GrpConv is always run, consider it a more serious error than any error 
        // encountered when setting 'runonce'.  (This decision is rather arbitrary, but 
        // in practice, it should never make any difference.  Once we get the registry key 
        // opened, there's no reason either of these calls to RegSetValueEx should fail.) 
        // 
        Error = (DWORD)l; 
    } 
 
    RegCloseKey(hKey); 
 
    if(DoRunOnce) { 
        WinExec("runonce -r", SW_SHOWNORMAL); 
    } 
 
    return Error; 
}