//======================================================================= // // Copyright (c) 1998-1999 Microsoft Corporation. All Rights Reserved. // // File: ccdm.cpp // // Owner: YanL // // Description: // // CDM support from the site // //======================================================================= #include "stdafx.h" #include #include #include #define LOGGING_LEVEL 1 #include #include #include #include #include #include #include "printers.h" static DWORD OpenReinstallKey(HKEY* phkeyReinstall); static DWORD GetReinstallString(LPCTSTR szHwID, tchar_buffer& bchReinstallString); static DWORD DeleteReinstallKey(LPCTSTR szHwID); bool DownloadToBuffer( IN LPCTSTR szPath, IN CWUDownload *pDownload, //pointer to internet server download class. IN CDiamond *pDiamond, //pointer to diamond de-compression class. OUT byte_buffer& bufOut ) { byte_buffer bufTmp; if (!pDownload->MemCopy(szPath, bufTmp)) return false; if (pDiamond->IsValidCAB(bufTmp)) { if (!pDiamond->Decompress(bufTmp, bufOut)) return false; } else { //else the oem table is in uncompressed format. bufOut << bufTmp; } return true; } //Note: This method works specifically with a CCdm class. The CCdm class contains //the returned inventory item array. So if multiple threads are used then //multiple instances of the CCdm class will be required. This should not ever be a //problem since we do not ever plan or have any reason to do single instance groveling //for device drivers. //This method adds CDM records to an internal CDM inventory list. The CCatalog //prune method performs the final copy and insertion of these records into the //final inventory catalog. void CCdm::CreateInventoryList( IN CBitmask *pBm, //bitmask to be used to prune the inventory list. IN CWUDownload *pDownload, //pointer to internet server download class. IN CDiamond *pDiamond, //pointer to diamond de-compression class. IN PUID puidCatalog, //puid identifier of catalog where device drivers are stored. IN PBYTE pOemInfoTable //Pointer OEM info table that OEM detection needs. ) { LOG_block("CCdm::CreateInventoryList"); USES_CONVERSION; // Start m_iCDMTotalItems = 0; //check input arguments if (NULL == pBm || NULL == pDownload || NULL == pDiamond) { return; } //First prune list based on bitmask //This is accomplished by anding the appropriate bitmasks to form a global bitmask. DWORD langid = GetMachineLangDW(); PBYTE pBitmaskBits = pBm->GetClientBits(GetMachinePnPID(pOemInfoTable), langid); if (NULL == pBitmaskBits) { return; } { //Note: a cdm catalog cannot have a 0 puid since that the inventory.plt will //never have a device driver insertion record. So this function will not be //called for the inventory.plt catalog. TCHAR szPath[MAX_PATH]; wsprintf(szPath, _T("%d/inventory.cdm"), puidCatalog); if (!DownloadToBuffer(szPath, pDownload, pDiamond, m_bufInventory)) throw HRESULT_FROM_WIN32(GetLastError()); } vector aitemUpdates; /* Regular drivers */ { CDrvInfoEnum DrvInfoEnum; auto_pointer pDrvInfo; while (DrvInfoEnum.GetNextDrvInfo(&pDrvInfo)) { tchar_buffer bufDeviceInstanceID; if (!pDrvInfo->GetDeviceInstanceID(bufDeviceInstanceID)) { LOG_error("!pDrvInfo->GetDeviceInstanceID()"); continue; } LOG_block(T2A(bufDeviceInstanceID)); if (pDrvInfo->IsPrinter()) { LOG_error("!pDrvInfo->IsPrinter()"); continue; } tchar_buffer bufHardwareIDs; if (!pDrvInfo->GetAllHardwareIDs(bufHardwareIDs)) { LOG_error("!pDrvInfo->GetAllHardwareIDs()"); continue; } tchar_buffer bufMatchingDeviceId; pDrvInfo->GetMatchingDeviceId(bufMatchingDeviceId); // It's OK not to get it // #ifdef _WUV3TEST if (pDrvInfo.valid() && pDrvInfo->HasDriver()) { if (bufMatchingDeviceId.valid()) LOG_out("driver installed on MatchingDeviceId %s", (LPCTSTR)bufMatchingDeviceId); else LOG_error("driver installed, but HWID is not available"); } else { if (bufMatchingDeviceId.valid()) LOG_error("driver is not installed, but MatchingDeviceId is %s", (LPCTSTR)bufMatchingDeviceId); else LOG_out("no driver installed"); } // #endif // Updates bool fMoreSpecific = true; for (LPCTSTR szHardwareId = bufHardwareIDs; fMoreSpecific && *szHardwareId; szHardwareId += lstrlen(szHardwareId) + 1) { // MatchingDeviceID is the last one to pay attention to fMoreSpecific = !bufMatchingDeviceId.valid() || 0 != lstrcmpi(szHardwareId, bufMatchingDeviceId); ULONG ulHashIndex = IsInMap(szHardwareId); if (-1 == ulHashIndex) continue; //else download the server bucket file byte_buffer& bufBucket = ReadBucketFile(pDownload, pDiamond, puidCatalog, ulHashIndex); FILETIME ftDriverInstalled = {0,0}; if (!fMoreSpecific) { // Then it has to have a driver - Matching device ID is set if (!pDrvInfo->GetDriverDate(ftDriverInstalled)) { LOG_error("!pDrvInfo->GetDriverDate(ftDriverInstalled)"); break; } } DRIVER_MATCH_INFO DriverMatchInfo; PUID puid = CDM_FindUpdateInBucket(szHardwareId, fMoreSpecific ? NULL : &ftDriverInstalled, bufBucket, bufBucket.size(), pBitmaskBits, &DriverMatchInfo); // See if it's being added already if (0 == puid) { LOG_out("CDM_FindUpdateInBucket returns 0"); continue; } // something has been found AddItem(aitemUpdates, puid, bufMatchingDeviceId.valid() ? CDM_UPDATED_DRIVER : CDM_NEW_DRIVER, A2T(DriverMatchInfo.pszHardwareID), A2T(DriverMatchInfo.pszDriverVer)); break; } // Installed if (bufMatchingDeviceId.valid()) { LPCTSTR szHardwareId = bufMatchingDeviceId; tchar_buffer bchReinstallString; if (NO_ERROR != GetReinstallString(szHardwareId, bchReinstallString)) { LOG_out("No reinstall string for %s", szHardwareId); continue; } ULONG ulHashIndex = IsInMap(szHardwareId); if (-1 == ulHashIndex) continue; //else download the server bucket file byte_buffer& bufBucket = ReadBucketFile(pDownload, pDiamond, puidCatalog, ulHashIndex); DRIVER_MATCH_INFO DriverMatchInfo; PUID puid = CDM_FindInstalledInBucket(pDrvInfo, szHardwareId, bufBucket, bufBucket.size(), pBitmaskBits, &DriverMatchInfo); // See if it's being added already if (0 == puid) { LOG_out("CDM_FindInstalledInBucket returns 0"); continue; } // something has been found AddItem(aitemUpdates, puid, CDM_CURRENT_DRIVER, A2T(DriverMatchInfo.pszHardwareID), A2T(DriverMatchInfo.pszDriverVer)); } } // while (DrvInfoEnum.GetNextDrvInfo(&pDrvInfo)) } /* Printer drivers */ { CPrinterDriverInfoArray ainfo; for(DWORD dwDriverIdx = 0; dwDriverIdx < ainfo.GetNumDrivers(); dwDriverIdx ++) { LPDRIVER_INFO_6 pinfo = ainfo.GetDriverInfo(dwDriverIdx); if (NULL == pinfo) continue; tchar_buffer bufHardwareID; if (!ainfo.GetHardwareID(pinfo, bufHardwareID)) continue; ULONG ulHashIndex = IsInMap(bufHardwareID); if (-1 == ulHashIndex) continue; //else download the server bucket file byte_buffer& bufBucket = ReadBucketFile(pDownload, pDiamond, puidCatalog, ulHashIndex); DRIVER_MATCH_INFO DriverMatchInfo; PUID puid = CDM_FindUpdateInBucket(bufHardwareID, &(pinfo->ftDriverDate), bufBucket, bufBucket.size(), pBitmaskBits, &DriverMatchInfo); if (0 == puid) { LOG_out("CDM_FindInstalledInBucket returns 0"); continue; } AddItem(aitemUpdates, puid, CDM_UPDATED_DRIVER, A2T(DriverMatchInfo.pszHardwareID), A2T(DriverMatchInfo.pszDriverVer), pinfo->pName, ainfo.GetArchitecture(pinfo)); } } //Do converson AddInventoryRecords(aitemUpdates); } // Check if given PNPID is current hash table mapping. //if PnpID is in hash table then return index ULONG CCdm::IsInMap( IN LPCTSTR pHwID //hardware id to be retrieved ) { LOG_block("CCdm::IsInMap"); // check if the member variable m_bufInventory is not NULL and the input argument is not NULL if (NULL == (PCDM_HASHTABLE)(LPBYTE)m_bufInventory || NULL == pHwID) { return -1; } PCDM_HASHTABLE pHashTable = (PCDM_HASHTABLE)(LPBYTE)m_bufInventory; ULONG ulTableEntry = CDM_HwID2Hash(pHwID, pHashTable->hdr.iTableSize); if(GETBIT(pHashTable->pData, ulTableEntry)) { LOG_out("%s (hash %d) is found", pHwID, ulTableEntry); return ulTableEntry; } LOG_out("%s (hash %d) is not found", pHwID, ulTableEntry); return -1; } //Reads and initializes a compressed CDM bucket file from an internet server. //Returnes the array index where the bucket file is stored. byte_buffer& CCdm::ReadBucketFile( IN CWUDownload *pDownload, //pointer to internet server download class. IN CDiamond *pDiamond, //pointer to diamond de-compression class. IN PUID puidCatalog, //PUID id of catalog for which cdm hash table is to be retrieved. IN ULONG ulHashIndex //Hash table index of bucket file to be retrieved ) { LOG_block("CCdm::ReadBucketFile"); // If it there return it for(int i = 0; i < m_aBuckets.size(); i ++) { if (ulHashIndex == m_aBuckets[i].first) return m_aBuckets[i].second; } // download it byte_buffer bufBucket; { TCHAR szPath[MAX_PATH]; wsprintf(szPath, _T("%d/%d.bkf"), puidCatalog, ulHashIndex); if (!DownloadToBuffer(szPath, pDownload, pDiamond, bufBucket)) throw HRESULT_FROM_WIN32(GetLastError()); } m_aBuckets.push_back(my_pair(ulHashIndex, bufBucket)); return m_aBuckets[m_aBuckets.size() - 1].second; } PINVENTORY_ITEM CCdm::ConvertCDMItem( int index //Index of cdm record to be converted ) { if ( index < 0 || index >= m_iCDMTotalItems ) return NULL; // this is a no-op, we just hand off the existing pointer return m_items[index]; } // add detected item to a list void CCdm::AddItem( vector& acdmItem, PUID puid, enumDriverDisposition fDisposition, LPCTSTR szHardwareId, LPCTSTR szDriverVer, LPCTSTR szPrinterDriverName /*= NULL*/, LPCTSTR szArchitecture /*= NULL*/ ) { LOG_block("CCdm::AddItem"); LOG_out("%s PUID = %d, HardwareId = %s", szPrinterDriverName ? _T("Prt") : _T("Dev"), puid, szHardwareId); // First check if this puid is included bool fNew = true; for (int nItem = 0; nItem < acdmItem.size(); nItem ++) { if ((acdmItem[nItem].puid == puid) && (acdmItem[nItem].fInstallState == fDisposition)) { fNew = false; break; } } if (fNew) { // new item; LOG_out("Adding as a new item"); acdmItem.push_back(SCdmItem()); SCdmItem& item = acdmItem.back(); item.puid = puid; item.fInstallState = fDisposition; item.sDriverVer = szDriverVer; if (szArchitecture) item.sArchitecture = szArchitecture; if (szPrinterDriverName) item.sPrinterDriverName = szPrinterDriverName; item.bufHardwareIDs.resize(lstrlen(szHardwareId) + 2); if(!item.bufHardwareIDs.valid()) return; //out of memory lstrcpy(item.bufHardwareIDs, szHardwareId); ((LPTSTR)item.bufHardwareIDs)[item.bufHardwareIDs.size() - 2] = 0; ((LPTSTR)item.bufHardwareIDs)[item.bufHardwareIDs.size() - 1] = 0; } else { SCdmItem& item = acdmItem[nItem]; // check if Hardware ID is already included for (LPCTSTR szCurHardwareId = acdmItem[nItem].bufHardwareIDs; *szCurHardwareId; szCurHardwareId += lstrlen(szCurHardwareId) + 1) { if (0 == lstrcmpi(szCurHardwareId, szHardwareId)) { // Got It LOG_out("Already present"); return; } } LOG_out("Appending hardware ID"); int cnOldSize = item.bufHardwareIDs.size(); item.bufHardwareIDs.resize(cnOldSize + lstrlen(szHardwareId) + 1); if(!item.bufHardwareIDs.valid()) return; //out of memory lstrcpy((LPTSTR)item.bufHardwareIDs + cnOldSize - 1, szHardwareId); ((LPTSTR)item.bufHardwareIDs)[item.bufHardwareIDs.size() - 2] = 0; ((LPTSTR)item.bufHardwareIDs)[item.bufHardwareIDs.size() - 1] = 0; } } //This function is used to create a CDM inventory record. This record is initialized with //the CDM bucket information. void CCdm::AddInventoryRecords( vector& acdmItem ) { LOG_block("CCdm::AddInventoryRecords"); for (int nItem = 0; nItem < acdmItem.size(); nItem ++) { PINVENTORY_ITEM pItem = 0; try { //allocate and initialize a new catalog item //note that this memory is never freed by this class //We rely on the fact that this pointer is going to be //handed off (in ConvertCDMItem()) and someone else will free it. pItem = (PINVENTORY_ITEM)V3_malloc(sizeof(INVENTORY_ITEM) + sizeof(WU_INV_FIXED) + sizeof(WU_VARIABLE_FIELD)); //we need to set these up by the inventory catalog copy and insert routine. pItem->pd = NULL; //first setup the fixed part of the inventory item pItem->pf = (PWU_INV_FIXED)(((PBYTE)pItem) + sizeof(INVENTORY_ITEM)); //We need to fix the record type here as it has to be correct before it is //inserted into the inventory list. if (acdmItem[nItem].sPrinterDriverName.length()) { //printer pItem->pf->d.type = SECTION_RECORD_TYPE_PRINTER; pItem->recordType = WU_TYPE_RECORD_TYPE_PRINTER; } else { // pnp device pItem->pf->d.type = SECTION_RECORD_TYPE_DRIVER_RECORD; pItem->recordType = WU_TYPE_CDM_RECORD; //Corporate catalog device driver } pItem->pf->d.puid = acdmItem[nItem].puid; pItem->pf->d.flags = 0; pItem->pf->d.link = 0; //set up the state of the item. This used to take place in ConvertCDMItem() //initialize and store the internal item state structure pItem->ps = (PWU_INV_STATE)V3_malloc(sizeof(WU_INV_STATE)); //translate the internal CDM item states to the public WUV3IS item //states. switch (acdmItem[nItem].fInstallState) { case CDM_NEW_DRIVER: pItem->ps->state = WU_ITEM_STATE_INSTALL; break; case CDM_UPDATED_DRIVER: pItem->ps->state = WU_ITEM_STATE_UPDATE; break; case CDM_CURRENT_DRIVER: pItem->ps->state = WU_ITEM_STATE_CURRENT; break; default: pItem->ps->state = WU_ITEM_STATE_UNKNOWN; }; pItem->ps->bChecked = FALSE; pItem->ps->bHidden = FALSE; pItem->ps->dwReason = WU_STATE_REASON_NONE; //setup the variable part of the item. In the main catalog inventory case this //is all that we need to do. In the case of the corporate catalog we will need //to add one more variable field. This field is the CDM bucket hashIndex id. //This allows the corporate catalog to perform defered detection on device //driver records. //We need to add the ending variable size record since the list needs to //be initialized before AddVariableSizeField() will work. pItem->pv = (PWU_VARIABLE_FIELD)((PBYTE)pItem + sizeof(INVENTORY_ITEM) + sizeof(WU_INV_FIXED)); pItem->pv->id = WU_VARIABLE_END; pItem->pv->len = sizeof(WU_VARIABLE_FIELD); //Add in the needed variable field items. (Note: Only variable detection //items are placed in the catalog inventory list to mimimize download size). PWU_VARIABLE_FIELD pVf = CreateVariableField(WU_CDM_HARDWARE_ID, (PBYTE)(LPTSTR)acdmItem[nItem].bufHardwareIDs, acdmItem[nItem].bufHardwareIDs.size() * sizeof(TCHAR)); AddVariableSizeField(&pItem, pVf); V3_free(pVf); pVf = CreateVariableField(WU_VARIABLE_DRIVERVER, (PBYTE)(LPTSTR)acdmItem[nItem].sDriverVer.c_str(), (acdmItem[nItem].sDriverVer.length() + 1) * sizeof(TCHAR)); AddVariableSizeField(&pItem, pVf); V3_free(pVf); if (CDM_CURRENT_DRIVER == acdmItem[nItem].fInstallState) { pVf = CreateVariableField(WU_KEY_UNINSTALLKEY, (PBYTE)"Yes", 4); // Just to say that uninstall key is present AddVariableSizeField(&pItem, pVf); V3_free(pVf); } if ( acdmItem[nItem].sPrinterDriverName.length() ) { pVf = CreateVariableField(WU_CDM_DRIVER_NAME, (PBYTE)(LPTSTR)acdmItem[nItem].sPrinterDriverName.c_str(), (acdmItem[nItem].sPrinterDriverName.length() + 1) * sizeof(TCHAR)); AddVariableSizeField(&pItem, pVf); V3_free(pVf); } if ( acdmItem[nItem].sArchitecture.length() ) { pVf = CreateVariableField(WU_CDM_PRINTER_DRIVER_ARCH, (PBYTE)(LPTSTR)acdmItem[nItem].sArchitecture.c_str(), (acdmItem[nItem].sArchitecture.length() + 1) * sizeof(TCHAR)); AddVariableSizeField(&pItem, pVf); V3_free(pVf); } } catch(HRESULT hr) { if ( pItem ) { V3_free(pItem); pItem = 0; } } if (pItem) m_items[m_iCDMTotalItems++] = pItem; } } //This function installs a driver on Windows NT. //The active function that is called on NT to install a device driver resides in newdev.dll //Its prototype is: //BOOL //InstallWindowsUpdateDriver( // HWND hwndParent, // LPCWSTR HardwareId, // LPCWSTR InfPathName, // LPCWSTR DisplayName, // BOOL Force, // BOOL Backup, // PDWORD pReboot // ) //This API takes a HardwareID. Newdev will cycle through all devices that match this hardware ID //and install the specified driver on them all. //It also takes a BOOL value Backup which specifies whether or not to backup the current drivers. // Note that newdev will only backup the drivers once if we find multiple matches for the HardwareID. static DWORD InstallNT( EDriverStatus eds, LPCTSTR szHwIDs, LPCTSTR szInfPathName, LPCTSTR szDisplayName, PDWORD pReboot ) { LOG_block("InstallNT"); USES_CONVERSION; typedef BOOL (*PFN_InstallWindowsUpdateDriver)(HWND hwndParent, LPCWSTR HardwareId, LPCWSTR InfPathName, LPCWSTR DisplayName, BOOL Force, BOOL Backup, PDWORD pReboot); // Load newdev.dll and get pointer to our function auto_hlib hlib = LoadLibrary(_T("newdev.dll")); return_error_if_false(hlib.valid()); PFN_InstallWindowsUpdateDriver pfnInstallWindowsUpdateDriver = (PFN_InstallWindowsUpdateDriver)GetProcAddress(hlib,"InstallWindowsUpdateDriver"); return_error_if_false(pfnInstallWindowsUpdateDriver); // make sure the hardware ID's are aligned LPCTSTR szTempHwIDs; TSTR_ALIGNED_STACK_COPY(&szTempHwIDs, szHwIDs); szHwIDs = szTempHwIDs; // walk through Hardware IDs DWORD dwRebootFinal = 0; for (LPCTSTR szHwID = szHwIDs; *szHwID; szHwID += lstrlen(szHwID) + 1) { tchar_buffer bchInfPathName; if (edsBackup == eds) { // Old INF name is in the registry return_if_error(GetReinstallString(szHwID, bchInfPathName)); TCHAR* ptr = _tcsrchr((LPCTSTR)bchInfPathName, _T('\\')); if (ptr) *ptr = 0; // cut file title } else { bchInfPathName.resize(lstrlen(szInfPathName) + 1); return_error_if_false(bchInfPathName.valid()); lstrcpy(bchInfPathName, szInfPathName); } BOOL fForce = edsNew != eds; BOOL fBackup = edsNew == eds; LOG_out("InstallWindowsUpdateDriver(%s, %s, %s, fForce=%d, fBackup=%d)", szHwID, (LPCTSTR)bchInfPathName, szDisplayName, fForce, fBackup); DWORD dwReboot = 0; BOOL bRc = (pfnInstallWindowsUpdateDriver)(GetActiveWindow(), T2W((LPTSTR)szHwID), T2W(bchInfPathName), T2W((LPTSTR)szDisplayName), fForce, fBackup, &dwReboot); DWORD dwError = GetLastError(); LOG_out("InstallWindowsUpdateDriver() returns %d, LastError = %d, need reboot = %d", bRc, dwError, dwReboot); if ( !bRc ) { if (NO_ERROR == dwError) dwError = SPAPI_E_DI_DONT_INSTALL; return dwError; } // cleanup if ( edsBackup == eds ) { DeleteReinstallKey(szHwID); // Delete directory DeleteNode(bchInfPathName); // Remove empty directory tree do { //remove *.* TCHAR* psz = _tcsrchr((LPTSTR)bchInfPathName, _T('\\')); if (NULL == psz) break; *psz = 0; } while (RemoveDirectory(bchInfPathName)); } if (dwReboot) dwRebootFinal = 1; } *pReboot = dwRebootFinal; return NO_ERROR; } void CdmInstallDriver(BOOL bWindowsNT, EDriverStatus eds, LPCTSTR szHwIDs, LPCTSTR szInfPathName, LPCTSTR szDisplayName, PDWORD pReboot) { DWORD dwError = InstallNT(eds, szHwIDs, szInfPathName, szDisplayName, pReboot); if (NO_ERROR != dwError ) { throw HRESULT_FROM_WIN32(dwError); } } // //We need to create a unique backup directory for this device, so we will // use the unique Hardware ID to come up with this unique directory. //Basically I will replace all of the illegal file/registry characters // \/:*?"<>| with # (for 98) , for NT will only do this with \ // static void HwID2Key(LPCTSTR szHwID, tchar_buffer& bufKey) { static TCHAR szIllegal[] = _T("\\/:*?\"<>|"); if (IsWindowsNT()) szIllegal[1] = 0; bufKey.resize(ua_lstrlen(szHwID) + 1); if (!bufKey.valid()) return; // out of memory // use the macro to copy unligned strings ua_tcscpy(bufKey, szHwID); for (TCHAR* pch = bufKey; *pch; pch ++) { if (_tcschr(szIllegal, *pch) != NULL) *pch = _T('#'); } } static DWORD RegQueryValueBuf(HKEY hKey, LPCTSTR szValue, tchar_buffer& buf) { DWORD dwSize = 0; DWORD dwError = RegQueryValueEx(hKey, szValue, NULL, NULL, NULL, &dwSize); if (NO_ERROR != dwError) return dwError; buf.resize(dwSize/sizeof(TCHAR)); if (!buf.valid()) return ERROR_OUTOFMEMORY; return RegQueryValueEx(hKey, szValue, NULL, NULL, (LPBYTE)(LPTSTR)buf, &dwSize); } static DWORD OpenReinstallKey(HKEY* phkeyReinstall) { return RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Reinstall"), 0, KEY_ALL_ACCESS, phkeyReinstall); } static DWORD GetReinstallString(LPCTSTR szHwID, tchar_buffer& bchReinstallString) { auto_hkey hkeyReinstall; DWORD dwError = OpenReinstallKey(&hkeyReinstall); if (NO_ERROR != dwError) return dwError; tchar_buffer bufKey; HwID2Key(szHwID, bufKey); auto_hkey hkeyHwID; dwError = RegOpenKeyEx(hkeyReinstall, bufKey, 0, KEY_READ, &hkeyHwID); if (NO_ERROR != dwError) return dwError; return RegQueryValueBuf(hkeyHwID, _T("ReinstallString"), bchReinstallString); } static DWORD DeleteReinstallKey(LPCTSTR szHwID) { LOG_block("DeleteReinstallKey"); auto_hkey hkeyReinstall; return_if_error(OpenReinstallKey(&hkeyReinstall)); tchar_buffer bufKey; HwID2Key(szHwID, bufKey); return_if_error(RegDeleteKey(hkeyReinstall, bufKey)); return NO_ERROR; }