/*++ Copyright (c) 1995-97 Microsoft Corporation All rights reserved. Module Name: SrvInst.c Purpose: Server side install code. This code will be called from a process created by the spooler to do a "server" side install of a printer driver. Author: Patrick Vine (pvine) - 22 March 2000 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "srvinst.hxx" const TCHAR gcszNTPrint[] = _TEXT("inf\\ntprint.inf"); const TCHAR gcszPrintKey[] = _TEXT("SYSTEM\\CurrentControlSet\\Control\\Print"); const TCHAR gcszTimeOut[] = _TEXT("ServerInstallTimeOut"); const TCHAR gcSpace = _TEXT(' '); #define DEFAULT_MAX_TIMEOUT 300000 // 5 minute timeout. /*++ Routine Name: ServerInstall Routine Description: Server side install code to be called by a process created by spooler. Arguments: hwnd - Window handle of stub window. hInstance, - Rundll instance handle. pszCmdLine - Pointer to command line. nCmdShow - Show command value always TRUE. Return Value: Returns the last error code. This can be read by the spooler by getting the return code from the process. --*/ DWORD ServerInstallW( IN HWND hwnd, IN HINSTANCE hInstance, IN LPCTSTR pszCmdLine, IN UINT nCmdShow ) { CServerInstall Installer; if( Installer.ParseCommand( (LPTSTR)pszCmdLine) && Installer.OpenPipe()) { if( Installer.GetInstallParameters() ) Installer.InstallDriver(); Installer.SendError(); Installer.ClosePipe(); } return Installer.GetLastError(); } //////////////////////////////////////////////////////////////////////////////// // // Method definitions for CServerInstall Class. // //////////////////////////////////////////////////////////////////////////////// CServerInstall:: CServerInstall() : _dwLastError(ERROR_SUCCESS), _tsDriverName(), _tsInf(), _tsSource(), _tsFlags(), _tsPipeName(), _hPipe(INVALID_HANDLE_VALUE) { SetMaxTimeOut(); } CServerInstall:: ~CServerInstall() { ClosePipe(); } void CServerInstall:: SetMaxTimeOut() { HKEY hKey; DWORD dwDummy; DWORD dwSize = sizeof(_dwMaxTimeOut); _dwMaxTimeOut = DEFAULT_MAX_TIMEOUT; if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, gcszPrintKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS ) { if(RegQueryValueEx( hKey, gcszTimeOut, 0, &dwDummy, (LPBYTE)&_dwMaxTimeOut, &dwSize ) != ERROR_SUCCESS) { _dwMaxTimeOut = DEFAULT_MAX_TIMEOUT; } RegCloseKey( hKey ); } } BOOL CServerInstall:: InstallDriver() { if( SetInfDir() && bValidateSourcePath() && DriverNotInstalled() ) { _dwLastError = ::InstallDriverSilently(_tsInf, _tsDriverName, _tsSource); } return (_dwLastError == ERROR_SUCCESS); } /*++ Parameter structure: 1st word : Flags = default == 0 for now if flags = 0 2nd word : Pipe name to open --*/ BOOL CServerInstall:: ParseCommand( LPTSTR pszCommandStr ) { TCHAR * pTemp; DWORD dwCount = 0; // // If we don't have a valid command string // if( !pszCommandStr || !*pszCommandStr ) { _dwLastError = ERROR_INVALID_PARAMETER; goto Cleanup; } // // Lets grab the flags field // pTemp = _tcschr( pszCommandStr, gcSpace ); if( !pTemp ) { // // No flags field, fail. // _dwLastError = ERROR_INVALID_PARAMETER; goto Cleanup; } *(pTemp++) = 0; if( !_tsFlags.bUpdate( pszCommandStr )) { _dwLastError = ::GetLastError(); goto Cleanup; } // // Currently we only have one case - so we don't need to worry about the // flags nor branch on them. however we may want to in the future. // // // The rest of the command line is the pipe's name. // if( !_tsPipeName.bUpdate( pTemp )) _dwLastError = ::GetLastError(); Cleanup: return (_dwLastError == ERROR_SUCCESS); } BOOL CServerInstall:: GetInstallParameters() { if(!GetOneParam( &_tsDriverName )) goto Done; if( _tsDriverName.bEmpty() ) { _dwLastError = ERROR_INVALID_PARAMETER; goto Done; } if(!GetOneParam( &_tsInf )) goto Done; GetOneParam( &_tsSource ); Done: return (_dwLastError == ERROR_SUCCESS); } // // Read the size of the string to follow from the pipe. // Then reads the string and places it in the TString passed. // BOOL CServerInstall:: GetOneParam( TString * tString ) { DWORD dwSize = 0; DWORD dwRet = 0; LPTSTR pszString = NULL; LPVOID pData = NULL; if( !ReadOverlapped( _hPipe, &dwSize, sizeof(dwSize), &dwRet )) goto Done; if( dwSize == 0 ) goto Done; // // The data that we're receiving will be WCHARs as spooler only works with UNICODE. // if(!(pData = LocalAllocMem((dwSize + 1)*sizeof(WCHAR)))) { _dwLastError = ::GetLastError(); goto Done; } if( !ReadOverlapped( _hPipe, pData, dwSize*sizeof(WCHAR), &dwRet ) ) goto Done; if( dwRet != dwSize*sizeof(WCHAR) ) _dwLastError = ERROR_INVALID_PARAMETER; // // Because ntprint compiles to both unicode and ansi we need to do this conversion. // The string coming in will be unicode as it comes from spooler which only // uses wchars. // #ifdef UNICODE pszString = (LPTSTR)pData; pData = NULL; #else //Get the length of the unicode string dwSize = WideCharToMultiByte( CP_ACP, 0, (LPWSTR)pData, -1, NULL, 0, NULL, NULL ); //Create the TCHAR string of the same length if( !(pszString = (LPTSTR)LocalAllocMem((dwSize + 1)*sizeof(TCHAR)))) goto Done; //Convert the string from unicode to ansi. if( !WideCharToMultiByte( CP_ACP, 0, (LPWSTR)pData, -1, pszString, dwSize, NULL, NULL ) ) { _dwLastError = ::GetLastError(); } #endif Done: if( !tString->bUpdate( pszString )) _dwLastError = ::GetLastError(); if( pData ) LocalFreeMem( pData ); if( pszString ) LocalFreeMem( pszString ); return (_dwLastError == ERROR_SUCCESS); } DWORD CServerInstall:: GetLastError() { SetLastError(_dwLastError); return _dwLastError; } BOOL CServerInstall:: SetInfDir() { if( _tsInf.bEmpty() ) { SetInfToNTPRINTDir(); } else { // // The inf name must be fully qualified. // TCHAR szFullInfName[MAX_PATH]; LPTSTR pszDummy; DWORD dwLength = GetFullPathName( _tsInf, COUNTOF( szFullInfName ), szFullInfName, &pszDummy ); if( !dwLength || !_tsInf.bUpdate( szFullInfName )) _dwLastError = ::GetLastError(); } return (_dwLastError == ERROR_SUCCESS); } // // Returns: TRUE if SUCCESS, FALSE otherwise // // Sets the _stInf string to contain %windir%\inf\ntprint.inf // BOOL CServerInstall:: SetInfToNTPRINTDir() { UINT uiSize = 0; UINT uiAllocSize = 0; PTCHAR pData = NULL; TCHAR szNTPrintInf[MAX_PATH]; _dwLastError = ERROR_INVALID_DATA; // // Get %windir% // If the return is 0 - the call failed. // If the return is greater than MAX_PATH we want to fail as something has managed to change // the system dir to longer than MAX_PATH which is invalid. // uiSize = GetSystemWindowsDirectory( szNTPrintInf, COUNTOF(szNTPrintInf) ); if( !uiSize || uiSize > COUNTOF(szNTPrintInf) ) goto Cleanup; // // If we don't end in a \ then add one. // pData = &szNTPrintInf[ _tcslen(szNTPrintInf) ]; if( *pData != _TEXT('\\') ) *(pData++) = _TEXT('\\'); *(pData) = 0; uiSize = _tcslen( szNTPrintInf ) + _tcslen( gcszNTPrint ) + 1; // // If what we've got sums up to a longer string than the allowable length MAX_PATH - fail // if( uiSize > COUNTOF(szNTPrintInf) ) goto Cleanup; // // Copy the inf\ntprint.inf string onto the end of the %windir%\ string. // _tcscpy( pData, gcszNTPrint ); _dwLastError = ERROR_SUCCESS; Cleanup: if( _dwLastError != ERROR_SUCCESS && szNTPrintInf ) { // // Got here due to some error. Get what the called function set the last error to. // If the function set a success, set some error code. // if( (_dwLastError = ::GetLastError()) == ERROR_SUCCESS ) _dwLastError = ERROR_INVALID_DATA; szNTPrintInf[0] = 0; } if( !_tsInf.bUpdate( szNTPrintInf ) ) _dwLastError = ::GetLastError(); return (_dwLastError == ERROR_SUCCESS); } BOOL CServerInstall:: bValidateSourcePath( ) { if( !_tsSource.bEmpty() && !(GetFileAttributes( (LPCTSTR)_tsSource ) & FILE_ATTRIBUTE_DIRECTORY) ) { _dwLastError = ERROR_DIRECTORY; } return (_dwLastError == ERROR_SUCCESS); } BOOL CServerInstall:: OpenPipe() { if( !_tsPipeName.bEmpty() ) { _hPipe = CreateFile( _tsPipeName, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); if( _hPipe == INVALID_HANDLE_VALUE ) _dwLastError = ::GetLastError(); } else _dwLastError = ERROR_INVALID_HANDLE; return (_dwLastError == ERROR_SUCCESS); } BOOL CServerInstall:: SendError() { DWORD dwDontCare; return (WriteOverlapped( _hPipe, &_dwLastError, sizeof(_dwLastError), &dwDontCare )); } BOOL CServerInstall:: ClosePipe() { BOOL bRet = TRUE; if( _hPipe != INVALID_HANDLE_VALUE ) { bRet = CloseHandle( _hPipe ); _hPipe = INVALID_HANDLE_VALUE; } return bRet; } BOOL CServerInstall:: ReadOverlapped( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead ) { if( hFile != INVALID_HANDLE_VALUE ) { OVERLAPPED Ov; ZeroMemory( &Ov,sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) { _dwLastError = ::GetLastError(); goto Cleanup; } if( !ReadFile( hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &Ov ) && ::GetLastError() != ERROR_IO_PENDING ) { _dwLastError = ::GetLastError(); goto Cleanup; } if( WaitForSingleObject(Ov.hEvent, _dwMaxTimeOut) == WAIT_TIMEOUT ) { CancelIo(hFile); WaitForSingleObject(Ov.hEvent, INFINITE); } if( !GetOverlappedResult(hFile, &Ov, lpNumberOfBytesRead, FALSE) ) _dwLastError = ::GetLastError(); Cleanup: if ( Ov.hEvent ) CloseHandle(Ov.hEvent); } else _dwLastError = ERROR_INVALID_HANDLE; return (_dwLastError == ERROR_SUCCESS); } BOOL CServerInstall:: WriteOverlapped( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead ) { if( hFile != INVALID_HANDLE_VALUE ) { OVERLAPPED Ov; ZeroMemory( &Ov,sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) { _dwLastError = ::GetLastError(); goto Cleanup; } if( !WriteFile( hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &Ov ) && ::GetLastError() != ERROR_IO_PENDING ) { _dwLastError = ::GetLastError(); goto Cleanup; } if( WaitForSingleObject(Ov.hEvent, _dwMaxTimeOut) == WAIT_TIMEOUT ) { CancelIo(hFile); WaitForSingleObject(Ov.hEvent, INFINITE); } if( !GetOverlappedResult(hFile, &Ov, lpNumberOfBytesRead, FALSE) ) _dwLastError = ::GetLastError(); Cleanup: if ( Ov.hEvent ) CloseHandle(Ov.hEvent); } else _dwLastError = ERROR_INVALID_HANDLE; return (_dwLastError == ERROR_SUCCESS); } /*+ This function enumerates the drivers and finds if there is one of the same name currently installed. If there is then open the inf to install with and verify that the inf's version date is newer than the already installed driver. Returns: TRUE - if anything fails or the installed date isn't newer than the inf date. FALSE - only if the driver is installed AND it's date is newer than the inf's date. -*/ BOOL CServerInstall:: DriverNotInstalled() { LPCTSTR pszKey = _TEXT("DriverVer"); LPTSTR pszEntry = NULL; LPDRIVER_INFO_6 pDriverInfo6 = NULL; LPBYTE pBuf = NULL; PSP_INF_INFORMATION pInfo = NULL; SYSTEMTIME Time = {0}; BOOL bRet = TRUE; DWORD dwLength, dwRet, dwIndex; TCHAR *pTemp, *pTemp2; if(!EnumPrinterDrivers( NULL, PlatformEnv[MyPlatform].pszName, 6, pBuf, 0, &dwLength, &dwRet )) { if( ::GetLastError() != ERROR_INSUFFICIENT_BUFFER ) return TRUE; if( (pBuf = (LPBYTE) AllocMem( dwLength )) == NULL || !EnumPrinterDrivers( NULL, PlatformEnv[MyPlatform].pszName, 6, pBuf, dwLength, &dwLength, &dwRet )) { _dwLastError = ::GetLastError(); goto Cleanup; } } else { // // Only way this could succeed is if no drivers installed. // _dwLastError = ERROR_UNKNOWN_PRINTER_DRIVER; return TRUE; } for( dwIndex = 0, pDriverInfo6 = (LPDRIVER_INFO_6)pBuf; dwIndex < dwRet; dwIndex++, pDriverInfo6++ ) { if( _tcscmp( pDriverInfo6->pName, (LPCTSTR)_tsDriverName ) == 0 ) break; } if(dwIndex >= dwRet) { // // Driver not found // _dwLastError = ERROR_UNKNOWN_PRINTER_DRIVER; goto Cleanup; } // // The driver has been found... Open up inf and look at it's date. // // // Firstly get the size that will be needed for pInfo. // if( !SetupGetInfInformation( (LPCTSTR)_tsInf, INFINFO_INF_NAME_IS_ABSOLUTE, pInfo, 0, &dwLength ) ) { _dwLastError = ::GetLastError(); goto Cleanup; } // // Alloc pInfo and fill it. // if( (pInfo = (PSP_INF_INFORMATION) AllocMem( dwLength )) != NULL && SetupGetInfInformation( (LPCTSTR)_tsInf, INFINFO_INF_NAME_IS_ABSOLUTE, pInfo, dwLength, &dwLength ) ) { // // Get the size of the date string // if( SetupQueryInfVersionInformation( pInfo, 0, pszKey, pszEntry, 0, &dwLength )) { // // Alloc pszEntry and fill it. // if( (pszEntry = (LPTSTR) AllocMem( dwLength*sizeof(TCHAR) )) != NULL && SetupQueryInfVersionInformation( pInfo, 0, pszKey, pszEntry, dwLength, &dwLength )) { // // Now convert the date string into a SYSTEMTIME // Date is of the form 03/22/2000 // // Get the month - 03 part // if( (pTemp = _tcschr( pszEntry, _TEXT('/'))) != NULL ) { *pTemp++ = 0; Time.wMonth = (WORD)_ttoi( pszEntry ); pTemp2 = pTemp; // // Get the day - 22 part // if( (pTemp = _tcschr( pTemp2, _TEXT('/'))) != NULL ) { *pTemp++ = 0; Time.wDay = (WORD)_ttoi( pTemp2 ); pTemp2 = pTemp; // // Get the year - 2000 part // pTemp = _tcschr( pTemp2, _TEXT('/')); if( pTemp ) *pTemp = 0; Time.wYear = (WORD)_ttoi( pTemp2 ); } else _dwLastError = ERROR_INVALID_PARAMETER; } else _dwLastError = ERROR_INVALID_PARAMETER; } else _dwLastError = ::GetLastError(); } else _dwLastError = ::GetLastError(); } else _dwLastError = ::GetLastError(); // // If we got all the way to filling in the year, we may have something useful... // if( Time.wYear ) { FILETIME ftTime = {0}; if(SystemTimeToFileTime( &Time, &ftTime )) { // // If the inf time is more recent than what is installed, // reinstall, otherwise don't // if( CompareFileTime(&ftTime, &pDriverInfo6->ftDriverDate) < 1 ) { bRet = FALSE; } } // // Getting here and return TRUE or FALSE is still a successful call. // _dwLastError = ERROR_SUCCESS; } else _dwLastError = ERROR_INVALID_PARAMETER; Cleanup: if( pBuf ) FreeMem( pBuf ); if( pInfo ) FreeMem( pInfo ); if( pszEntry ) FreeMem( pszEntry ); return bRet; }