//+============================================================================
//
//  File:       PrpSetup.cxx
//
//  Purpose:    This file builds to an executable which installs the
//              IProp DLL in the System(32) directory.  This is provided
//              for the use of applications which re-distribute that DLL.
//
//  Usage:      PrpSetup [/u] [/c]
//
//              The /u option indicates that an un-install should be performed.
//              The /c option indicates that console output is desired.
//
//  History:    10/30/96    MikeHill    Get "iprop.dl_" from the exe's resources.
//
//+============================================================================

//  --------
//  Includes
//  --------

#include <windows.h>
#include <ole2.h>
#include <tchar.h>
#include <stdio.h>

//  -------------
//  Global values
//  -------------

// Name-related information for the DLL
const LPTSTR tszResourceType       = TEXT( "FILE" );        // Resource type
const LPTSTR tszCompressedFilename = TEXT( "IPROP.DL_" );   // Temp file name
const LPTSTR tszTargetFilename     = TEXT( "IPROP.DLL" );   // Final file name

// The reg key where we keep the DLL's install ref-count.
const LPTSTR tszRegSharedDLLs
                = TEXT( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\SharedDLLs" );

// Registration functions in IProp DLL.
const LPTSTR tszRegistrationFunction    = TEXT( "DllRegisterServer" );
const LPTSTR tszUnregistrationFunction  = TEXT( "DllUnregisterServer" );


//  ------------
//  Return Codes
//  ------------

#define RETURN_SUCCESS                          0
#define RETURN_ARGUMENT_ERROR                   1
#define RETURN_COULDNT_CREATE_TEMP_FILE         2
#define RETURN_COULDNT_INSTALL_DLL              3
#define RETURN_COULDNT_DELETE_DLL               4
#define RETURN_COULDNT_REGISTER_DLL             5
#define RETURN_COULDNT_ACCESS_REGISTRY          6
#define RETURN_OUT_OF_MEMORY                    7
#define RETURN_INTERNAL_ERROR                   8


//+----------------------------------------------------------------------------
//
//  Function:   Register
//
//  Synopsis:   This function registers or de-registers the IProp DLL.
//
//  Inputs:     [BOOL] fUninstall (in)
//                  If true, call DllUnregisterServer, otherwise call
//                  DllRegisterServer
//
//  Returns:    [HRESULT]
//
//+----------------------------------------------------------------------------


HRESULT Register( BOOL fUninstall )
{
    HRESULT hr;
    HINSTANCE hinst = NULL;

    // A function pointer for the registration function
    typedef HRESULT (STDAPICALLTYPE FNREGISTRATION)();
    FNREGISTRATION *pfnRegistration = NULL;

    // Load the DLL

    hinst = LoadLibrary( tszTargetFilename );
    if( NULL == hinst )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Exit;
    }

    // Get the registration function

    pfnRegistration = (FNREGISTRATION*)
                      GetProcAddress( hinst,
                                      fUninstall ? tszUnregistrationFunction
                                                 : tszRegistrationFunction );
    if( NULL == pfnRegistration )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Exit;
    }

    // Register or De-register IProp.

    hr = (*pfnRegistration)();
    if( FAILED(hr) ) goto Exit;

    //  ----
    //  Exit
    //  ----

Exit:

    if( NULL != hinst )
        FreeLibrary( hinst );

    return( hr );
}



//+----------------------------------------------------------------------------
//
//  Function:   main()
//
//  Synopsis:   This program loads/removes IProp.DLL into/from the
//              System directory.  A ref-count of the number of installs
//              of this DLL is kept in the Registry.  The DLL is
//              also registered/deregistered.
//
//+----------------------------------------------------------------------------

HRESULT __cdecl main(int argc, char **argv)
{
    // File names and paths

    TCHAR tszSystemPath[_MAX_PATH+1];        // Path to System(32) directory
    TCHAR tszTempFilename[_MAX_PATH+1];      // Used by VerInstallFile()
    UINT cbTempFilename = sizeof( tszTempFilename ) - sizeof(TCHAR);
    TCHAR tszTargetPathAndFile[_MAX_PATH+1]; // E.g. "C:\Win\System32\IProp.dll"
    TCHAR tszTempPath[_MAX_PATH+1];          // E.g. "C:\Temp\"
                                             // E.g. "C:\Temp\iprop.dl_"
    TCHAR tszTempPathAndFile[_MAX_PATH+1] = {""};

    // Index into argv
    int nArgIndex;
    
    // User-settable flags.
    BOOL fConsole = FALSE;
    BOOL fInstall = FALSE;
    BOOL fUninstall = FALSE;

    // Registry data
    HKEY hkey;
    DWORD dwRegValueType;
    DWORD dwRefCount;
    DWORD cbRefCountSize = sizeof( dwRefCount );
    DWORD dwDisposition;

    // Handles for reading "iprop.dl_" out of the resources
    HRSRC hrsrcIProp = NULL;       // Handle to the "iprop.dl_" resource.
    HGLOBAL hglobIProp = NULL;     // Handle to the "iprop.dl_" data.
    LPVOID lpvIProp = NULL;        // Pointer to the "iprop.dl_" data.
    HMODULE hmodCurrent = NULL;    // Our module handle
    HANDLE hfileIProp = NULL;      // Handle to "%TEMP%\iprop.dl_" file


    // Misc.
    HRESULT hr = S_OK;
    INT  nReturnCode = RETURN_INTERNAL_ERROR;

    //  -----------------
    //  Process the Input
    //  -----------------

    for( nArgIndex = 1; nArgIndex < argc; nArgIndex++ )
    {
        if( // Is this argument an option?
            ( argv[nArgIndex][0] == '/'
              ||
              argv[nArgIndex][0] == '-'
            )
            && // and is it more than one character?
            argv[nArgIndex][1] != '\0'
            && // and is it exactly two characters?
            argv[nArgIndex][2] == '\0'
          )
        {
            // See if it's an argument we recognize.
            switch( argv[nArgIndex][1] )
            {
                // Installation

                case 'i':
                case 'I':

                    fInstall = TRUE;
                    break;

                // Uninstall
                case 'u':
                case 'U':

                    fUninstall = TRUE;
                    break;

                // Console output
                case 'c':
                case 'C':

                    fConsole = TRUE;
                    break;
            }
        }   // if( ( argv[nArgIndex][0] == '/' ...
    }   // for( nArgIndex = 1; nArgIndex < argc; nArgIndex++ )

    // Did we get an illegal command-line combination?

    if( fInstall && fUninstall )
    {
        nReturnCode = RETURN_ARGUMENT_ERROR;
        goto Exit;
    }

    // Did the user fail to tell us what to do?  If so,
    // display usage information.

    if( !fInstall && !fUninstall )
    {
        _tprintf( TEXT("\n") );
        _tprintf( TEXT("   Installation program for the Microsoft OLE Property Set Implementation\n") );
        _tprintf( TEXT("   Usage:    IProp [/i | /u] [/c]\n") );
        _tprintf( TEXT("   Options:  /i => Install\n")
                  TEXT("             /u => Uninstall\n")
                  TEXT("             /c => Console output\n") );
        _tprintf( TEXT("   Examples: IProp /i\n")
                  TEXT("             IProp /u /c\n") );
        
        nReturnCode = RETURN_SUCCESS;
        goto Exit;
    }


    //  ----------
    //  Initialize
    //  ----------

    // Find the target installation directory.

    if( GetSystemDirectory( tszSystemPath,
                            sizeof(tszSystemPath) - sizeof(TCHAR))
        == 0 )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        nReturnCode = RETURN_COULDNT_INSTALL_DLL;
        goto Exit;
    }
      
    // Determine the target's total path & filename.

    _tcscpy( tszTargetPathAndFile, tszSystemPath );
    _tcscat( tszTargetPathAndFile, TEXT("\\") );
    _tcscat( tszTargetPathAndFile, tszTargetFilename );

    // Generate the filename we'll use for the compressed
    // IProp DLL file ("iprop.dl_"); get the temp directory
    // and post-pend a filename to it.

    if( !GetTempPath( sizeof(tszTempPath), tszTempPath ))
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        nReturnCode = RETURN_COULDNT_CREATE_TEMP_FILE;
        goto Exit;
    }

    _tcscpy( tszTempPathAndFile, tszTempPath );
    _tcscat( tszTempPathAndFile, tszCompressedFilename );

    // Open the registry key that holds this DLL's ref-count.

    hr = RegCreateKeyEx( HKEY_LOCAL_MACHINE,    // Open key
                       tszRegSharedDLLs,        // Name of subkey
                       0L,                      // Reserved
                       NULL,                    // Class
                       0,                       // Options
                       KEY_ALL_ACCESS,          // SAM desired
                       NULL,                    // Security attributes
                       &hkey,                   // Result
                       &dwDisposition );        // "Created" or "Opened"
    if( ERROR_SUCCESS != hr )
    {
        hr = HRESULT_FROM_WIN32( hr );
        nReturnCode = RETURN_COULDNT_ACCESS_REGISTRY;
        goto Exit;
    }

    // Attempt to read our ref-count

    hr = RegQueryValueEx( hkey,                 // Open key
                          tszTargetPathAndFile, // Value name
                          NULL,                 // Reserved
                          &dwRegValueType,      // Out: value type
                          (LPBYTE) &dwRefCount, // Out: value
                          &cbRefCountSize );    // In: buf size, out: data size

    if( ERROR_FILE_NOT_FOUND == hr )
        // This entry didn't already exist.
        dwRefCount = 0;

    else if( ERROR_SUCCESS != hr )
    {
        // There was a real error during the Query attempt.
        hr = HRESULT_FROM_WIN32(hr);
        nReturnCode = RETURN_COULDNT_ACCESS_REGISTRY;
        goto Exit;
    }

    else if ( REG_DWORD != dwRegValueType )
    {
        // This is an invalid entry.  We won't abort, we'll just
        // re-initialize it to zero, and at the end we'll overwrite
        // whatever was already there.

        dwRefCount = 0;
    }


    if( fConsole )
    {
        if( fUninstall )
            _tprintf ( TEXT("Uninstalling \"%s\"\n"), tszTargetPathAndFile );
        else
            _tprintf( TEXT("Installing \"%s\"\n"), tszTargetPathAndFile );
    }

    //  ------------------------------
    //  Installation or Uninstallation
    //  ------------------------------

    if( fUninstall )
    {   // We're doing an Un-Install

        // Should we actually delete it?  We haven't done a dec-ref yet,
        // so in the normal case, on the last delete, the RefCount will 
        // currently be 1.

        if( dwRefCount <= 1 )
        {
            // Yes - we need to do a delete.  First unregister the IProp
            // DLL.  If there's an error we'll abort.  So we might leave
            // an unused file on the machine, but that's better than
            // possibly deleting a file that is still in use by another
            // app.

            hr = Register( fUninstall );
            if( FAILED(hr) )
            {
                nReturnCode = RETURN_COULDNT_REGISTER_DLL;
                goto Exit;
            }

            // And delete the file

            if( !DeleteFile( tszTargetPathAndFile )
                &&
                ERROR_FILE_NOT_FOUND != GetLastError() )
            {
                hr = HRESULT_FROM_WIN32( GetLastError() );
                nReturnCode = RETURN_COULDNT_DELETE_DLL;
                goto Exit;
            }

            if( fConsole )
                _tprintf( TEXT("Removed IProp.DLL\n") );

            // Zero-out the ref count.  We'll delete it from the 
            // registry later
 
            dwRefCount = 0;
        }
        else
        {
            // We don't need to delete it, just dec-ref it.
            dwRefCount--;

            if( fConsole )
                _tprintf( TEXT("IProp.DLL not removed (reference count is now %d)\n"), dwRefCount );
        }
    }   // if( fUninstall )

    else
    {   // We're doing an Install

        DWORD dwSize;           // Size of "iprop.dl_".
        DWORD cbWritten = 0;

        if( fConsole )
            _tprintf( TEXT("Extracting \"%s\"\n"), tszTempPathAndFile );

        // Get our module handle;

        hmodCurrent = GetModuleHandle( NULL );
        if( NULL == hmodCurrent )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_OUT_OF_MEMORY;
            goto Exit;
        }

        // Get the resource which is actually the compressed IProp DLL

        hrsrcIProp = FindResource( hmodCurrent,
                                   tszCompressedFilename,
                                   tszResourceType );
        if( NULL == hrsrcIProp )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_OUT_OF_MEMORY;
            goto Exit;
        }

        // Get the size of "iprop.dl_"

        dwSize = SizeofResource( hmodCurrent, hrsrcIProp );
        if( 0 == dwSize )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_OUT_OF_MEMORY;
            goto Exit;
        }

        // Get "iprop.dl_" into a memory buffer.

        hglobIProp = LoadResource( hmodCurrent, hrsrcIProp );
        if( NULL == hglobIProp )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_OUT_OF_MEMORY;
            goto Exit;
        }

        // Get a pointer to the "iprop.dl_" data.

        lpvIProp = LockResource( hglobIProp );
        if( NULL == lpvIProp )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_OUT_OF_MEMORY;
            goto Exit;
        }

        // Create a temporary file, which will be "iprop.dl_"

        hfileIProp = CreateFile(
                            tszTempPathAndFile,             // E.g. "C:\Temp\iprop.dl_"
                            GENERIC_READ | GENERIC_WRITE,   // Requested access
                            FILE_SHARE_READ,                // Sharing mode
                            NULL,                           // No security attributes
                            CREATE_ALWAYS,                  // Overwrite existing
                            FILE_ATTRIBUTE_NORMAL,          // Default attributes
                            NULL );                         // No template file
        if( INVALID_HANDLE_VALUE == hfileIProp )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_COULDNT_CREATE_TEMP_FILE;
            goto Exit;
        }
        
        // Write the contents of "iprop.dl_"

        if( !WriteFile( hfileIProp, lpvIProp, dwSize, &cbWritten, NULL ))
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            nReturnCode = RETURN_COULDNT_CREATE_TEMP_FILE;
            goto Exit;
        }

        // We must close the file, or VerInstallFile won't open it.

        CloseHandle( hfileIProp );
        hfileIProp = NULL;

        // Install the file.

        hr = VerInstallFile(  0,                      // Flags
                              tszCompressedFilename,  // Source filename
                              tszTargetFilename,      // Dest filename
                              tszTempPath,            // Source location
                              tszSystemPath,          // Target location
                              tszSystemPath,          // Location of old version
                              tszTempFilename,        // Out: name of temp file
                              &cbTempFilename);       // In: size of buf, Out: name

        // If VerInstallFile left a temporary file, delete it now.

        if( hr & VIF_TEMPFILE )
        {
            TCHAR tszDeleteTempFile[_MAX_PATH+1];

            _tcscpy( tszDeleteTempFile, tszSystemPath );
            _tcscat( tszDeleteTempFile, TEXT("\\") );
            _tcscat( tszDeleteTempFile, tszTempFilename );
            DeleteFile( tszDeleteTempFile );
        }

        // If the file was installed successfully, register it.

        if( 0 == hr )
        {
            hr = Register( fUninstall );
            if( FAILED(hr) )
            {
                nReturnCode = RETURN_COULDNT_REGISTER_DLL;
                goto Exit;
            }
        }

        // If the error wasn't "newer version exists", then we
        // have a fatal error.

        else if( 0 == (hr & VIF_SRCOLD) )
        {
            nReturnCode = RETURN_COULDNT_INSTALL_DLL;
            goto Exit;
        }
        else if( fConsole )
        {
            _tprintf( TEXT("A newer version of the file is already installed\n") );
        }


        // Do an add-ref.
        dwRefCount++;

    }   // if( fUninstall ) ... else


    //  ------------------
    //  Save the Ref-Count
    //  ------------------

    // Did we actually delete the DLL?

    if( 0 == dwRefCount )
    {
        // Delete our entry from the SharedDlls entry
        hr = RegDeleteValue( hkey, tszTargetPathAndFile );
        
        if( ERROR_FILE_NOT_FOUND == hr )
            hr = ERROR_SUCCESS;

        else if( ERROR_SUCCESS != hr )
        {
            hr = HRESULT_FROM_WIN32(hr);
            nReturnCode = RETURN_COULDNT_ACCESS_REGISTRY;
            goto Exit;
        }
    }
    else
    {
        // Otherwise, put the new ref-count in the registry.
        hr = RegSetValueEx(  hkey,                  // Open key
                             tszTargetPathAndFile,  // Value name
                             0,                     // Reserved
                             REG_DWORD,             // Value type
                             (LPBYTE) &dwRefCount,  // Value buffer
                             sizeof( dwRefCount )); // Size of value
        if( ERROR_SUCCESS != hr )
        {
            hr = HRESULT_FROM_WIN32(hr);
            nReturnCode = RETURN_COULDNT_ACCESS_REGISTRY;
            goto Exit;
        }
    }    // if( 0 == dwRefCount ) ... else


    //  ----
    //  Exit
    //  ----

Exit:

    if( fConsole )
    {
        // We only succeeded if hr is 0; VerInstallFile might return
        // a bitmapped error that doesn't look like an HRESULT error
        // code.

        if( 0 == hr )
            _tprintf( TEXT("%s successful\n"),
                      fUninstall ? TEXT("Uninstall") : TEXT("Install") );
        else
            _tprintf( TEXT("%s failed.  Return code = %d (%08X)\n"),
                      nReturnCode,
                      fUninstall ? TEXT("Uninstall") : TEXT("Install"),
                      hr );
    }

    // Remove the temporary file (we initialized this to "", so this
    // call should always return success or file-not-found).

    DeleteFile( tszTempPathAndFile );

    // Free all the handles we've used.

    if( hfileIProp ) CloseHandle( hfileIProp );
    if( lpvIProp )   GlobalUnlock( lpvIProp );


    return( nReturnCode );
    
}