//=======================================================================
//
//  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
//
//  File:    install.cpp
//
//  Purpose: Install/uninstall components
//
//======================================================================= 

#include "stdafx.h"

#include "install.h"
#include <advpub.h>


extern CState g_v3state;   //defined in CV3.CPP


// This function installs an active setup catalog item.
void InstallActiveSetupItem(LPCTSTR szLocalDir, LPCTSTR pszCifFile, PSELECTITEMINFO pStatusInfo, IWUProgress* pProgress)
{
	USES_CONVERSION;

	MSG 				msg;
	HRESULT 			hr;
	DWORD				dwEngineStatus;
	IInstallEngine2*	pInsEng = NULL;
	ICifFile*			picif = NULL;
	ICifComponent*		pcomp = NULL;
	IEnumCifComponents* penum = NULL;
	CInstallEngineCallback* pCallback = NULL;

	try
	{
		//
		// Create active setup engine
		//
		hr = CoCreateInstance(CLSID_InstallEngine, NULL, CLSCTX_INPROC_SERVER, IID_IInstallEngine2,(void **)&pInsEng);
		if (FAILED(hr))
			throw hr;

		// Tell active setup the direct and directory to use. This is the directory
		// where we downloaded the files previously.
		hr = pInsEng->SetDownloadDir(T2A(szLocalDir));
		if (FAILED(hr))
			throw hr;
		
		hr = pInsEng->SetLocalCif(T2A(pszCifFile));
		if (FAILED(hr))
			throw hr;

		//
		// Create the callback object and register the install engines callback interface
		//
		pCallback = new CInstallEngineCallback;
		if (!pCallback)
		{
			throw HRESULT_FROM_WIN32(GetLastError());
		}
		pCallback->SetProgressPtr(pProgress);

		pInsEng->RegisterInstallEngineCallback((IInstallEngineCallback *)pCallback);

		pCallback->SetEnginePtr(pInsEng);

		//
		// Get a pointer to the CIF interface we need in order to enum the
		// CIF components and we need to do that on this single item CIF
		// becuase we need to tell the install engine what action to perform.
		//
		hr = pInsEng->GetICifFile(&picif);
		if (FAILED(hr))
			throw hr;

		hr = picif->EnumComponents(&penum, 0, NULL);
		if (FAILED(hr))
			throw hr;

		hr = penum->Next(&pcomp);
		if (FAILED(hr))
			throw hr;

		// Set action to install and then install the component.
		pcomp->SetInstallQueueState(SETACTION_INSTALL);

		hr = pInsEng->InstallComponents(EXECUTEJOB_IGNORETRUST | EXECUTEJOB_IGNOREDOWNLOADERROR);
		if (FAILED(hr))
		{
			TRACE_HR(hr, "InstallActiveSetupItem: inseng.InstallComponents failed");
			throw hr;
		}

		do
		{
			pInsEng->GetEngineStatus(&dwEngineStatus);

			while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			SleepEx(50, TRUE);
		} while (dwEngineStatus != ENGINESTATUS_READY);

		if (pcomp->IsComponentInstalled() != ICI_INSTALLED)
		{
			hr = pCallback->LastError();

			//
			// if we don't know the exact error from the callback, use E_FAIL
			//
			if (hr == NOERROR)
				hr = E_FAIL;

			TRACE_HR(hr, "InstallActiveSetupItem: inseng failed to install");
			throw hr;
		}


		//
		// get the return status
		//
		if (pCallback->GetStatus() & STOPINSTALL_REBOOTNEEDED)
			pStatusInfo->iStatus = ITEM_STATUS_SUCCESS_REBOOT_REQUIRED;
		else
			pStatusInfo->iStatus = ITEM_STATUS_SUCCESS;
		pStatusInfo->hrError = NOERROR;

		// 
		// release interfaces
		//
		if (penum)
			penum->Release();

		if (picif)
			picif->Release();

		if (pInsEng)
			pInsEng->Release();

		if (pCallback)
			delete pCallback;
	}
	catch(HRESULT hr)
	{
		pStatusInfo->iStatus = ITEM_STATUS_FAILED;
		pStatusInfo->hrError = hr;

		if (penum)
			penum->Release();

		if (picif)
			picif->Release();

		if (pInsEng)
			pInsEng->Release();

		if (pCallback)
			delete pCallback;

		throw hr;
	}

}


//This function handles installation of a Device driver package.
void InstallDriverItem(
	LPCTSTR szLocalDir,					//Local directory where installation files are.
	BOOL bWindowsNT,					//TRUE if client machine is NT else FALSE.
	LPCTSTR pszTitle,					//Description of package, Device Manager displays this in its install dialog.
	PINVENTORY_ITEM pItem,				//Install Item Information
	PSELECTITEMINFO pStatusInfo			//Returned status information.
	)
{
	LOG_block("InstallDriverItem");

	try
	{
		//Note: GetCatalog automatically prunes any drivers if the system is
		//not either NT 5 or Windows 98 since we only support installation
		//of drivers on these platforms.

		//If we do not have a hardware id then we cannot install the device.
		PWU_VARIABLE_FIELD	pvTmp = pItem->pv->Find(WU_CDM_HARDWARE_ID);
		if (!pvTmp)
		{
			pStatusInfo->iStatus = ITEM_STATUS_FAILED;
			pStatusInfo->hrError = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
			return;
		}

		BOOL reboot = FALSE;
		CdmInstallDriver(bWindowsNT, 
			(WU_ITEM_STATE_CURRENT == pItem->ps->state) ? edsCurrent : edsNew, 
			(LPCTSTR)pvTmp->pData, szLocalDir, pszTitle, (PULONG)&reboot);

		if (reboot)
			pStatusInfo->iStatus = ITEM_STATUS_SUCCESS_REBOOT_REQUIRED;
		else
			pStatusInfo->iStatus = ITEM_STATUS_SUCCESS;
		pStatusInfo->hrError = NOERROR;
	}
	catch(HRESULT hr)
	{
		pStatusInfo->iStatus = ITEM_STATUS_FAILED;
		pStatusInfo->hrError = hr;

		throw hr;
	}
}


HRESULT UninstallDriverItem(PINVENTORY_ITEM pItem, PSELECTITEMINFO pStatusInfo)
{
	
	PBYTE pTitle;
	PWU_VARIABLE_FIELD pvTitle;
	PWU_VARIABLE_FIELD pvTmp;
	BOOL bWindowsNT;
	TCHAR szLocalDir[MAX_PATH];
	BOOL bReboot = FALSE;
	

	bWindowsNT = IsWindowsNT();

	if (!(pvTmp = pItem->pv->Find(WU_CDM_HARDWARE_ID)) )
	{
		pStatusInfo->iStatus = ITEM_STATUS_FAILED;
		pStatusInfo->hrError = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
		return E_UNEXPECTED;
	}

	if (pvTitle = pItem->pd->pv->Find(WU_DESCRIPTION_TITLE))
	{
		pTitle = (PBYTE)pvTitle->pData;
	}
	else
	{
		pTitle = (PBYTE)"";
	}

				
	pStatusInfo->iStatus = ITEM_STATUS_SUCCESS;

	//we will call UpdateCDMDriver with our cache directory but this directory should not
	//really be used by UpdateCDMDriver in case of an uninstall since the uninstall case
	//reads the correct directory from the registry
	GetWindowsUpdateDirectory(szLocalDir);

	try
	{
		CdmInstallDriver(bWindowsNT, edsBackup, (LPCTSTR)pvTmp->pData, szLocalDir, (LPCTSTR)pTitle, (PULONG)&bReboot);
		if (bReboot)
		{
			g_v3state.m_bRebootNeeded = TRUE;
		}

	}
	catch(HRESULT hr)
	{
		pStatusInfo->iStatus = ITEM_STATUS_FAILED;
		pStatusInfo->hrError = hr;
		return hr;
	}

	return S_OK;
}



HRESULT GetUninstallCmdFromKey(LPCTSTR pszUninstallKey, LPTSTR pszCmdValue)                
{
	
	const TCHAR UNINSTALLKEY[] = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\");
	const TCHAR UNINSTALLVALUE[] = _T("UninstallString");
	const TCHAR QUIETUNINSTALLVALUE[] = _T("QuietUninstallString");

	HKEY hRegKey = NULL;
	DWORD dwRegType;
	DWORD dwRegSize;
	TCHAR szRegKey[256];
	LONG lRet;
	TCHAR szValue[256];

	if (pszUninstallKey == NULL)
		return E_FAIL;

	if (pszUninstallKey[0] == _T('"'))
	{
		//strip quotes from the key
		lstrcpy(szValue, (pszUninstallKey + 1));
		int l = lstrlen(szValue);
		if (l > 0)
			szValue[l - 1] = _T('\0');
	}
	else
	{
		lstrcpy(szValue, pszUninstallKey);
	}
	

	lstrcpy(szRegKey, UNINSTALLKEY);
	lstrcat(szRegKey, szValue);

	lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_READ, &hRegKey);
	if (lRet != ERROR_SUCCESS)
		return HRESULT_FROM_WIN32(lRet);

	//check the uninstall value
	dwRegSize = sizeof(szValue);
	lRet = RegQueryValueEx(hRegKey, UNINSTALLVALUE, NULL, &dwRegType, (LPBYTE)szValue, &dwRegSize);

	if (lRet != ERROR_SUCCESS)
	{
		// try to find quiteuninstall value
		dwRegSize = sizeof(szValue);
		lRet = RegQueryValueEx(hRegKey, QUIETUNINSTALLVALUE, NULL, &dwRegType, (LPBYTE)szValue, &dwRegSize);
	}

	if (lRet != ERROR_SUCCESS)
	{
		HRESULT hr = HRESULT_FROM_WIN32(lRet);
		RegCloseKey(hRegKey);
		return hr;
	}
	RegCloseKey(hRegKey);

	//
	// copy the key to the output parameter
	//
	lstrcpy(pszCmdValue, szValue);

	return S_OK;
}


//Given an uninstall key, looks up in the registry to find the uninsall command and launches it
//If quotes are found around the uninstall key, they are striped.  This is done to make it 
//compatible with the value specified in the AS CIF file.  This function also understands the
//special INF syntax of the uninstall key.
HRESULT UninstallActiveSetupItem(LPCTSTR pszUninstallKey)
{
	const TCHAR INFUNINSTALLCOMMAND[] = _T("RunDll32 advpack.dll,LaunchINFSection ");
	const TCHAR INFDIRECTORY[] = _T("INF\\");

	
	TCHAR szCmd[256] = {_T('\0')};
	TCHAR szActualKey[128] = {_T('\0')};
	LONG lRet;
	LPCTSTR pszParse;

	HRESULT hr = S_OK;

	if (pszUninstallKey == NULL)
		return E_FAIL;
	
	if (lstristr(pszUninstallKey, _T(".inf")) != NULL)
	{
		//
		// we have .INF syntax: frontpad,fpxpress.inf,uninstall, start parsing
		// 
		pszParse = lstrcpystr(pszUninstallKey, _T(","), szActualKey);  // get actual uninstall key
	}
	else
	{
		lstrcpy(szActualKey, pszUninstallKey);
		pszParse = NULL;
	}

	//
	// get the uninstall command from the registry
	//
	hr = GetUninstallCmdFromKey(szActualKey, szCmd);
	
	if (SUCCEEDED(hr))
	{
		// we have the uninstall command, try to launch it
		lRet = LaunchProcess(szCmd, NULL, SW_NORMAL, TRUE);

		TRACE("Uninstall: %s, %d", szCmd, lRet);

		if (lRet != 0)
		{
			hr = HRESULT_FROM_WIN32(lRet);
		}
	}
	else
	{
		// we did not get the uninstall command from registry, launch INF
		if (pszParse != NULL)
		{
	
			TCHAR szTemp[MAX_PATH];

			hr = S_OK;

			//get INF directory
            if (! GetWindowsDirectory(szTemp, sizeof(szTemp) / sizeof(TCHAR)))
			{
				hr = HRESULT_FROM_WIN32(GetLastError());
				TRACE("Uninstall: GetWindowsDirectory failed, hr=0x%x", hr);
				return hr;
			}
			AddBackSlash(szTemp);
			lstrcat(szTemp, INFDIRECTORY);

			//start building the command
			lstrcpy(szCmd, INFUNINSTALLCOMMAND);
			lstrcat(szCmd, szTemp);

			pszParse = lstrcpystr(pszParse, _T(","), szTemp);  // get INF file name
			lstrcat(szCmd, szTemp);

			lstrcat(szCmd, _T(","));

			pszParse = lstrcpystr(pszParse, _T(","), szTemp);  // get INF section
			lstrcat(szCmd, szTemp);

			lRet = LaunchProcess(szCmd, NULL, SW_NORMAL, TRUE);
			if (lRet != 0)
			{
				hr = HRESULT_FROM_WIN32(lRet);
			}

			TRACE("Uninstall: %s, %d", szCmd, lRet);

			if (SUCCEEDED(hr))
			{
				//
				// reboot handling after uninstall only if uninstall was successful
				//
				pszParse = lstrcpystr(pszParse, _T(","), szTemp);  // get the reboot key
				if (lstrcmpi(szTemp, _T("reboot")) == 0)
				{
					g_v3state.m_bRebootNeeded = TRUE;
				}
			}
		}
		else
		{
			hr = E_UNEXPECTED;
		}
	}

	return hr;
}


void InstallPrinterItem(LPCTSTR pszDriverName, LPCTSTR pszInstallFolder, LPCTSTR pszArchitecture)
{
	DWORD dw = InstallPrinterDriver(pszDriverName, pszInstallFolder, pszArchitecture);
	HRESULT hr = HRESULT_FROM_WIN32(dw);

	if (FAILED(hr))
		throw hr;
}



HRESULT AdvPackRunSetupCommand(HWND hwnd, LPCSTR pszInfFile, LPCSTR pszSection, LPCSTR pszDir, DWORD dwFlags)
{
	HRESULT 		hr = E_FAIL;
	RUNSETUPCOMMAND pfRunSetupCommand;
	
	HINSTANCE hAdvPack = LoadLibrary(_T("advpack.dll"));
	
	if (hAdvPack != NULL)
	{
		pfRunSetupCommand = (RUNSETUPCOMMAND)GetProcAddress(hAdvPack, "RunSetupCommand");
		if (pfRunSetupCommand)
		{
			dwFlags |= (RSC_FLAG_INF | RSC_FLAG_NGCONV | RSC_FLAG_QUIET);
			hr = pfRunSetupCommand(hwnd, pszInfFile, pszSection, pszDir, NULL, NULL, dwFlags, NULL);
		}
		FreeLibrary(hAdvPack);
	}
	
	return hr;
}




// checks to see if inseng is up to date
void CheckLocalDll(LPCTSTR pszServer, LPCTSTR pszDllName, LPCTSTR pszServDllName, LPCTSTR pszDllVersion)
{
	USES_CONVERSION;

	const TCHAR TEMPINFFN[] = _T("temp.inf");
	const TCHAR PERIOD[] = _T(".");

	TCHAR szServDllName[32] = _T("\0");
	TCHAR szValue[256] = _T("\0");
	TCHAR szTempFN[MAX_PATH] = _T("\0");
	TCHAR szDllFN[MAX_PATH] = _T("\0");
	TCHAR szInfDir[MAX_PATH] = _T("\0");
	TCHAR szDllName[MAX_PATH] = _T("\0");
	TCHAR szDllVersion[64] = _T("\0");
	TCHAR *pszToken = NULL;
	DWORD dwIniMSVer = 0;
	DWORD dwIniLSVer = 0;
	DWORD dwFileMSVer = 0;
	DWORD dwFileLSVer = 0;
	LPTSTR p = NULL;
	HRESULT hr = S_OK;

	//check input arguments
	if (NULL == pszDllName || NULL == pszServDllName || NULL == pszDllVersion)
	{
		return ;
	}

	if (!GetSystemDirectory(szDllFN, sizeof(szDllFN) / sizeof(TCHAR)))
	{
		return;
	}

	AddBackSlash(szDllFN);
	lstrcat(szDllFN, pszDllName);
	
	lstrcpy(szDllVersion, pszDllVersion);
	lstrcpy(szServDllName, pszServDllName);

	if (FileExists(szDllFN))
	{
		// convert file version 
		ConvertVersionStrToDwords(szDllVersion, &dwIniMSVer, &dwIniLSVer);

		// get file version of the DLL
		if (!GetFileVersionDwords(szDllFN, &dwFileMSVer, &dwFileLSVer))
		{
			TRACE("Could not check file version: %s", szDllFN);
			return;
		}

		// compare the versions
		if ((dwFileMSVer > dwIniMSVer) || ((dwFileMSVer == dwIniMSVer) && (dwFileLSVer >= dwIniLSVer)))
		{
			// install version is up to date
			return;
		}
	}
	
	// 
	// we need to download and install it!
	//
	BLOCK
	{
		CWUDownload	dl(pszServer, 8192);
		
		if (!dl.Copy(szServDllName, NULL, NULL, NULL, DOWNLOAD_NEWER | CACHE_FILE_LOCALLY, NULL))
		{
			throw HRESULT_FROM_WIN32(GetLastError());
		}
		
		// filename with OS ext
		GetWindowsUpdateDirectory(szTempFN);
		lstrcat(szTempFN, szServDllName);
		
		// filename with .dll
		lstrcpy(szDllFN, szTempFN);
		p = _tcschr(szDllFN, _T('.'));
		if (p)
			*p = _T('\0');
		lstrcat(szDllFN, _T(".dll"));
		
		CDiamond dm;
		
		// decompress or copy to .dll name
		if (dm.IsValidCAB(szTempFN)) 
		{
			hr = dm.Decompress(szTempFN, szDllFN);
			if (FAILED(hr)) 
			{
				throw hr;
			}
		}
		else
		{
			if (!CopyFile(szTempFN, szDllFN, FALSE))
			{
				return;
			}
		}
	}
	
	//
	// write out an INF file
	//
	HANDLE hFile;
	DWORD dwBytes;
	char szInfText[1024];


	sprintf (szInfText, 
		"[Version]\r\n\
		Signature=\"$Chicago$\"\r\n\
		AdvancedINF=2.5\r\n\
		[DestinationDirs]\r\n\
		WUSysDirCopy=11\r\n\
		[InstallControl]\r\n\
		CopyFiles=WUSysDirCopy\r\n\
		RegisterOCXs=WURegisterOCXSection\r\n\
		[WUSysDirCopy]\r\n\
		%s,,,32\r\n\
		[WURegisterOCXSection]\r\n\
		%%11%%\\%s\r\n\
		[SourceDisksNames]\r\n\
		55=\"Disk\",,0\r\n\
		[SourceDisksFiles]\r\n\
		%s=55\r\n", 
		T2A(pszDllName), T2A(pszDllName), T2A(pszDllName));

	GetWindowsUpdateDirectory(szTempFN);
	lstrcat(szTempFN, TEMPINFFN);
	
	hFile = CreateFile(szTempFN, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		WriteFile(hFile, (LPCVOID)szInfText, strlen(szInfText), &dwBytes, NULL);
		
		CloseHandle(hFile);
	}

	//
	// RunSetupCommand to install the .INF
	//
	GetWindowsUpdateDirectory(szInfDir);

	hr = AdvPackRunSetupCommand(NULL, T2A(szTempFN), "InstallControl", T2A(szInfDir), 0);

	if (FAILED(hr))
	{
		throw hr;
	}
}

void CheckDllsToJit(LPCTSTR pszServer)
{
	const TCHAR SEPS[] = _T("=");
	TCHAR szValue[1024];
	TCHAR szTempFN[MAX_PATH];
	TCHAR szKey[256];
	TCHAR szDllBaseName[64];
	TCHAR szDllExt[32];
	TCHAR szKeyLhs[64];
	TCHAR szDllName[64];
	TCHAR szDllTempName[64];
	TCHAR szDllVersion[64];
	TCHAR *pszValue;
	long len;

	if (g_v3state.m_bInsengChecked)
		return;

	g_v3state.m_bInsengChecked = TRUE;

	GetWindowsUpdateDirectory(szTempFN);
	lstrcat(szTempFN, FILEVERINI_FN);

	if (0 == GetPrivateProfileSection(_T("version"), szValue, sizeof(szValue) / sizeof(TCHAR), szTempFN))
	{
		// version not available for this file
		TRACE("Section: version is missing from FileVer.ini");
		return;
	}

	pszValue = szValue;
	while (0 != (len = lstrlen(pszValue))) 
	{
	lstrcpyn(szKey, pszValue, len+1);
	lstrcat(szKey, _T("\0"));
	pszValue += len + 1;

	_stscanf(szKey, _T("%[^=]=%s"), szKeyLhs, szDllVersion);
	_stscanf(szKeyLhs, _T("%[^.].%s"), szDllBaseName, szDllExt);
	wsprintf(szDllName, _T("%s.dll"), szDllBaseName);

	wsprintf(szDllTempName, _T("%s."), szDllBaseName);
	AppendExtForOS(szDllTempName);
	if (0 == lstrcmpi(szDllTempName, szKeyLhs))
		CheckLocalDll(pszServer, szDllName, szKeyLhs, szDllVersion);
	}


}

// Pings a URL for tracking purposes
// 
// NOTE: pszStatus must not contain spaces and cannot be null
void URLPingReport(PINVENTORY_ITEM pItem, CCatalog* pCatalog, PSELECTITEMINFO pSelectItem, LPCTSTR pszStatus)
{
	LOG_block("URLPingReport");

	//check for input arguments
	if (NULL == pItem || NULL == pCatalog)
	{
		return;
	}




	// create a download object
	try
	{
		CWUDownload	dl(g_v3state.GetIdentServer(), 8192);

		// build the URL with parameters
		TCHAR szURL[INTERNET_MAX_PATH_LENGTH];
		wsprintf(szURL, _T("wutrack.bin?VER=%s&PUID=%d&PLAT=%d&LOCALE=%s&STATUS=%s&ERR=0x%x&SESSID=%s"), 
            CCV3::s_szControlVer,
						pItem->GetPuid(), 
						pCatalog->GetPlatform(),
						pCatalog->GetMachineLocaleSZ(),
						pszStatus,
						pSelectItem->hrError,
                        CWUDownload::m_szWUSessionID);

        LOG_out("Sending %s", szURL);

		// ping the URL and receive the response in memory
		PVOID pMemBuf;
		ULONG ulMemSize;
		if (dl.QCopy(szURL, &pMemBuf, &ulMemSize))
		{
			// we don't care about the response so we just free it
			V3_free(pMemBuf);	
		}
	}
	catch(HRESULT hr)
	{
		return;
	}
	
}