//+-------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 2001. // // File: msiclass.cpp // // Contents: msi class collection abstraction // // Classes: // // // History: 4-14-2000 adamed Created // //--------------------------------------------------------------------------- #include "precomp.hxx" WCHAR* CClassCollection::_wszQueries[ TYPE_COUNT ] = { QUERY_EXTENSIONS, QUERY_CLSIDS, QUERY_VERSION_INDEPENDENT_PROGIDS }; CClassCollection::CClassCollection( PACKAGEDETAIL* pPackageDetail ) : _pPackageDetail( pPackageDetail ), _cMaxClsids( 0 ), _cMaxExtensions( 0 ), _InstallLevel( 0 ) { // // All memory referenced by the pPackageDetail must be // freed by the caller after the GetClasses method is called, // even if the call fails. // // // We need to clear any existing class information in the // PACKAGEDETAIL structure since we are going to overwrite // it eventually anyway // // // First clear the clsid's // DWORD iClass; // // Free each individual class // for ( iClass = 0; iClass < _pPackageDetail->pActInfo->cClasses; iClass++ ) { FreeClassDetail( &(_pPackageDetail->pActInfo->pClasses[ iClass ]) ); } // // Now free the vector that held the classes // LocalFree( _pPackageDetail->pActInfo->pClasses ); // // Set our vector reference to the initial state of none // _pPackageDetail->pActInfo->pClasses = NULL; // // Set the initial state of no clsid's since they have all been freed // _pPackageDetail->pActInfo->cClasses = 0; // // Now clear the extensions // DWORD iExtension; // // For each individual extension // for ( iExtension = 0; iExtension < _pPackageDetail->pActInfo->cShellFileExt; iExtension++ ) { LocalFree( _pPackageDetail->pActInfo->prgShellFileExt[ iExtension ] ); } // // Free the vector that held the extensions // LocalFree( _pPackageDetail->pActInfo->prgShellFileExt ); // // Also destroy the vector that held extension priorities // LocalFree( _pPackageDetail->pActInfo->prgPriority ); // // Set our vector references to the initial state of none // _pPackageDetail->pActInfo->prgShellFileExt = NULL; _pPackageDetail->pActInfo->prgPriority = NULL; // // Set the initial state of no file extensions since they have all been freed // _pPackageDetail->pActInfo->cShellFileExt = 0; } HRESULT CClassCollection::GetClasses( BOOL bFileExtensionsOnly ) { HRESULT hr; LONG Status; DWORD cTransforms; // // This method obtains the class metadata from an msi package + transforms. // The goal is to approximate the set of class data that would be advertised // on any system (regardless of system configuration) if the package were // advertised. // // // The classes will all be stored in the PACKAGEDETAIL structure. The caller // must free this memory after finishing with the structure, even if this // method fails // // // First, we must create a database representation of the package + transforms // // // The source list vector contains the package + transforms in application order -- // we must subtract one source since the original package is included in the list // cTransforms = _pPackageDetail->cSources - 1; // // Now we create a database out of the package plus transforms. Since the // first item in the source list is the package, we pass that in as the package, // and all other items after it in the vector are passed in as the transform vector // Status = _Database.Open( _pPackageDetail->pszSourceList[0], cTransforms, cTransforms ? &(_pPackageDetail->pszSourceList[1]) : NULL ); if ( ERROR_SUCCESS == Status ) { // // We've successfully opened the package, now obtain its friendly name. // Status = GetFriendlyName(); if (ERROR_SUCCESS == Status) { // // Now obtain its install level. // The install level affects whether or not a class will get advertised // Status = GetInstallLevel(); } if ( ERROR_SUCCESS == Status ) { // // Now that we know the install level of the package, we have // enough information to flag each advertisable feature in the database. // We need this because a class is only advertised if its associated // feature is advertised. // Status = FlagAdvertisableFeatures(); } // // We may now retrieve the set of classes that will be advertised based // on the set of advertised features we flagged earlier. We care only // about 3 types of classes: Clsid's, ProgId's, and File Extenions. // if ( ! bFileExtensionsOnly ) { if ( ERROR_SUCCESS == Status ) { Status = GetClsids(); } if ( ERROR_SUCCESS == Status ) { Status = GetProgIds(); } } if ( ERROR_SUCCESS == Status ) { Status = GetExtensions(); } LONG StatusFree; // // We must remove the scratch flags we added to the database // StatusFree = RemoveAdvertisableFeatureFlags(); if ( ERROR_SUCCESS == Status ) { // // Take care to preserve the return value -- a failure // before cleaning up the database takes precedence over // a failure in cleaning up the database. // Status = StatusFree; } } return HRESULT_FROM_WIN32(Status); } LONG CClassCollection::GetExtensions() { LONG Status; BOOL bTableExists; // // First check to see if we even have an extension table to query // Status = _Database.TableExists( TABLE_FILE_EXTENSIONS, &bTableExists ); if ( ( ERROR_SUCCESS == Status ) && bTableExists ) { // // Set up a destination in the user's PACKAGEDETAIL structure // for the shell extension class data // DataDestination Destination( TYPE_EXTENSION, (void**)&(_pPackageDetail->pActInfo->prgShellFileExt), &(_pPackageDetail->pActInfo->cShellFileExt), (UINT*) &_cMaxExtensions); // // Now retrieve the shell extensions // Status = GetElements( TYPE_EXTENSION, &Destination ); if ( ERROR_SUCCESS == Status ) { // // We've successfully retrieved the shell extensions -- // the caller also expects a parallel array of priorities // with each shell extension -- the values are unimportant // since the caller will fill those in, but the memory must // exist, so we will allocate it. // _pPackageDetail->pActInfo->prgPriority = (UINT*) LocalAlloc( 0, sizeof(UINT) * _pPackageDetail->pActInfo->cShellFileExt ); if ( ! _pPackageDetail->pActInfo->prgPriority ) { Status = ERROR_NOT_ENOUGH_MEMORY; } } } return Status; } LONG CClassCollection::GetClsids() { LONG Status; BOOL bTableExists; // // First check to see if we even have a clsid table to query // Status = _Database.TableExists( TABLE_CLSIDS, &bTableExists ); if ( ( ERROR_SUCCESS == Status ) && bTableExists ) { // // Set the destination for the clsid's to a location // in the caller's PACKAGEDETAIL structure // DataDestination Destination( TYPE_CLSID, (void**)&(_pPackageDetail->pActInfo->pClasses), &(_pPackageDetail->pActInfo->cClasses), (UINT*) &_cMaxClsids); // // Now retrieve the clsid's for each package // Status = GetElements( TYPE_CLSID, &Destination ); } return Status; } LONG CClassCollection::GetProgIds() { LONG Status; BOOL bTableExists; // // First check to see if we even have a ProgId table to query // Status = _Database.TableExists( TABLE_PROGIDS, &bTableExists ); if ( ( ERROR_SUCCESS == Status ) && bTableExists ) { // // This method MUST be called AFTER GetClsids -- progid's // are stored within their associated clsid's, so we will // not have a place to store the progid's unless we've // already obtained the clsid's. // // // At this point, we know only that we want to retrieve ProgId's -- // we do not know their destination because this differs for // each progid depending on the associated clsid -- the NULL // parameters indicate that some callee will need to determine // the location for this data. // DataDestination Destination( TYPE_PROGID, NULL, NULL, NULL); // // Retrieve the progid's into the appropriate locations in the structure // Status = GetElements( TYPE_PROGID, &Destination ); } return ERROR_SUCCESS; } LONG CClassCollection::GetElements( DWORD dwType, DataDestination* pDestination ) { LONG Status; CMsiQuery ElementQuery; // // Perform the query for the class elements // Status = _Database.GetQueryResults( _wszQueries[ dwType ], &ElementQuery); if ( ERROR_SUCCESS != Status ) { return Status; } for (;;) { // // We've obtained the results -- now we enumerate them so // that we can persist them in the caller's PACKAGEDETAIL // structure. // // // Note that we start a new scope so that our record object // will automatically free its resources // { CMsiRecord CurrentRecord; // // Enumerate the next record in the query result set // Status = ElementQuery.GetNextRecord( &CurrentRecord ); if ( ERROR_SUCCESS != Status ) { if ( ERROR_NO_MORE_ITEMS == Status ) { Status = ERROR_SUCCESS; } break; } // // Now attempt to add the class data from this record into // the PACKAGEDETAIL structure // Status = ProcessElement( dwType, &CurrentRecord, pDestination); } if ( ERROR_SUCCESS != Status ) { break; } } return Status; } LONG CClassCollection::FlagAdvertisableFeatures() { LONG Status; CMsiQuery FeatureQueryCreate; // // We will attempt to mark each feature in the database // with a flag indicating whether or not it will be advertised // // // First, add a column to the feature table of the database // so that we can use the column to flag whether or not the // feature is advertised. // Status = _Database.GetQueryResults( QUERY_ADVERTISED_FEATURES_CREATE, &FeatureQueryCreate); CMsiQuery FeatureQueryInit; // // Now intialize the new column's flags to 0 which // indicates that no features will be advertised (yet) // if ( ERROR_SUCCESS == Status ) { Status = _Database.GetQueryResults( QUERY_ADVERTISED_FEATURES_INIT, &FeatureQueryInit); } CMsiQuery AllFeatures; // // Now we perform the query to retrieve all features -- // records in this query will contain the newly added // flag column. // if ( ERROR_SUCCESS == Status ) { Status = _Database.GetQueryResults( QUERY_ADVERTISED_FEATURES_RESULT, &AllFeatures); } CMsiQuery SetAdvertised; // // Create a query that will allow us to set the flag -- // this query is not yet computed, simply initialized // if ( ERROR_SUCCESS == Status ) { Status = _Database.OpenQuery( QUERY_FEATURES_SET, &SetAdvertised); } // // Now we enumerate through all the features and // set the flag for each feature that passes the tests // for advertisability. // for (; ERROR_SUCCESS == Status ;) { CMsiRecord CurrentRecord; BOOL bAdvertised; // // Retrieve the current feature // Status = AllFeatures.GetNextRecord( &CurrentRecord); if ( ERROR_SUCCESS != Status ) { if ( ERROR_NO_MORE_ITEMS == Status ) { Status = ERROR_SUCCESS; } break; } // // Determine whether or not this feature should be advertised // Status = GetFeatureAdvertiseState( &CurrentRecord, &bAdvertised ); if ( ( ERROR_SUCCESS == Status ) && bAdvertised ) { // // This feature is advertisable -- use our SetAdvertised query // to set the advertisability flag to true. // Status = SetAdvertised.UpdateQueryFromFilter( &CurrentRecord ); } } return Status; } LONG CClassCollection::RemoveAdvertisableFeatureFlags() { LONG Status; CMsiQuery FreeQuery; // // Retrieving the results of this query will // eliminate the extra flag column we added // to the feature table to flag advertisable // features. // Status = _Database.GetQueryResults( QUERY_ADVERTISED_FEATURES_DESTROY, &FreeQuery); return Status; } LONG CClassCollection::GetInstallLevel() { LONG Status; CMsiQuery InstallLevelQuery; // // Perform a query which retrieves the install level // property from the package's property table // Status = _Database.GetQueryResults( QUERY_INSTALLLEVEL, &InstallLevelQuery); CMsiRecord InstallLevelRecord; // // This query should only have one record in it since // it was targeted at the specific record for install level -- // we now read that record // if ( ERROR_SUCCESS == Status ) { Status = InstallLevelQuery.GetNextRecord( &InstallLevelRecord); } if ( ERROR_SUCCESS == Status ) { CMsiValue InstallLevelProperty; // // We now attempt to obtain the installlevel property value // from the retrieved record. // Status = InstallLevelRecord.GetValue( CMsiValue::TYPE_DWORD, PROPERTY_COLUMN_VALUE, &InstallLevelProperty); if ( ERROR_SUCCESS == Status ) { // // We've successfully obtained the value, so we set it // _InstallLevel = InstallLevelProperty.GetDWORDValue(); } } else if ( ERROR_NO_MORE_ITEMS == Status ) { // // This will only happen if the install level property // is not present. As fundamental as this property is, // some packages do not specify it. The Darwin engine // treats this case as an implicit install level of 1, so // we must do the same here // _InstallLevel = 1; Status = ERROR_SUCCESS; } return Status; } LONG CClassCollection::GetFriendlyName() { LONG Status; CMsiQuery FriendlyNameQuery; // // Perform a query which retrieves the install level // property from the package's property table // Status = _Database.GetQueryResults( QUERY_FRIENDLYNAME, &FriendlyNameQuery); CMsiRecord FriendlyNameRecord; // // This query should only have one record in it since // it was targeted at the specific record // we now read that record // if ( ERROR_SUCCESS == Status ) { Status = FriendlyNameQuery.GetNextRecord( &FriendlyNameRecord); } if ( ERROR_SUCCESS == Status ) { CMsiValue FriendlyNameProperty; // // We now attempt to obtain the property value // from the retrieved record. // Status = FriendlyNameRecord.GetValue( CMsiValue::TYPE_STRING, PROPERTY_COLUMN_VALUE, &FriendlyNameProperty); if ( ERROR_SUCCESS == Status ) { // // We've successfully obtained the value, so we set it // CString szName = FriendlyNameProperty.GetStringValue(); OLESAFE_DELETE(_pPackageDetail->pszPackageName); OLESAFE_COPYSTRING(_pPackageDetail->pszPackageName, szName); } } return Status; } LONG CClassCollection::GetFeatureAdvertiseState( CMsiRecord* pFeatureRecord, BOOL* pbAdvertised ) { LONG Status; CMsiValue Attributes; CMsiValue InstallLevel; // // Set the out paramter's initial value to FALSE, // indicating that the feature is not advertised // *pbAdvertised = FALSE; // // The Attributes column of the feature table // contains a flag indicating that a feature // should be not advertised // Status = pFeatureRecord->GetValue( CMsiValue::TYPE_DWORD, FEATURE_COLUMN_ATTRIBUTES, &Attributes); if ( ERROR_SUCCESS == Status ) { // // If the disable advertise flag is set, this feature // cannot be advertised // if ( Attributes.GetDWORDValue() & MSI_DISABLEADVERTISE ) { return ERROR_SUCCESS; } // // The disable flag was not set -- that still does not mean that // the feature is advertised -- we must check the install level. // We retrieve the install level for this feature here // Status = pFeatureRecord->GetValue( CMsiValue::TYPE_DWORD, FEATURE_COLUMN_LEVEL, &InstallLevel); } if ( ERROR_SUCCESS == Status ) { DWORD dwInstallLevel; // // Obtain the value for the install level so // we can compare against the package install level // dwInstallLevel = InstallLevel.GetDWORDValue(); // // An install level of 0 indicates that the package will // not be advertised. The install level of the feature // must be no higher than the package's global install // level // if ( ( 0 != dwInstallLevel ) && ( dwInstallLevel <= _InstallLevel ) ) { // // This feature passes the tests -- set the out parameter // to TRUE to indicate that the feature should be advertised // *pbAdvertised = TRUE; } } return Status; } LONG CClassCollection::AddElement( void* pvDataSource, DataDestination* pDataDestination) { DWORD* pcMax; BYTE* pNewResults; DWORD cCurrent; // // We attempt to add an element to a vector // // // Set the count for how many elements are stored in the vector to // that specified by the caller // cCurrent = *(pDataDestination->_pcCurrent); // // Set the element count for the maximum number of elements that // will fit in the vector currently to that specified by the caller // pcMax = (DWORD*) pDataDestination->_pcMax; // // Set our results to point to the vector specified by the caller // pNewResults = (BYTE*) pDataDestination->_ppvData; // // If we already have the maximum number of elements in the vector, // we will have to make room for more // if ( *pcMax >= cCurrent) { DWORD cbSize; // // Calculate the new size in bytes so that we can ask the system // for memory. We take our current size in elements and add on a fixed // allocation increment. The caller has specified the size // of each individual element, so we use that to turn the number // of elements to a memory size. // cbSize = ( *pcMax + CLASS_ALLOC_SIZE ) * pDataDestination->_cbElementSize; // // Make the request for memory // pNewResults = (BYTE*) LocalAlloc( 0, cbSize ); if ( ! pNewResults ) { return ERROR_NOT_ENOUGH_MEMORY; } // // Clear the memory -- any data structures embedded in the element // will have NULL references and thus will be properly initialized // memset( pNewResults, 0, cbSize ); // // If the original maximum size of the vector was nonzero, then we must // copy to original contents of the vector to the newly allocated memory // location. // if ( *pcMax ) { memcpy( pNewResults, *(pDataDestination->_ppvData), *pcMax * pDataDestination->_cbElementSize); } // // Free the original vector as it is no longer needed // LocalFree( *(pDataDestination->_ppvData) ); // // Change the caller's reference to point to the new vector // *(pDataDestination->_ppvData) = pNewResults; // // Set the new maximum size (in elements) to that of the newly allocated vector // *pcMax += CLASS_ALLOC_SIZE; } // // At this point, we know we have a memory location in the vector into // which we can safely copy the new element // memcpy( pNewResults + ( cCurrent * pDataDestination->_cbElementSize ), pvDataSource, pDataDestination->_cbElementSize); // // Update the count of elements currently stored in the vector // *(pDataDestination->_pcCurrent) = cCurrent + 1; return ERROR_SUCCESS; } LONG CClassCollection::ProcessElement( DWORD dwType, CMsiRecord* pRecord, DataDestination* pDataDestination) { LONG Status = ERROR_SUCCESS; void* pvData; WCHAR* wszData; CLASSDETAIL ClassDetail; pvData = NULL; wszData = NULL; // // We attempt to create a new class element based // on the record passed in by the caller, and then // add that element to the caller's PACKAGEDETAIL structure // // // The type of element to be added depends on the type // of class requested by the caller. The pvData variable // will point to the element to be added if we can successfully // create a representation for it. // switch ( dwType ) { case TYPE_EXTENSION: // // Get a file extension representation from the record -- // note that wszData points to memory allocated by the callee // on success, so it must be freed by this function. // Status = ProcessExtension( pRecord, &wszData); if ( ERROR_SUCCESS == Status ) { pvData = &wszData; } break; case TYPE_CLSID: // // Get a clsid representation from the record -- // in this case, the ClassDetail itself does not // need to be freed since it does not contain any references // to memory after this call // BOOL bIgnoreClsid; Status = ProcessClsid( pRecord, &ClassDetail, &bIgnoreClsid); if ( ERROR_SUCCESS == Status ) { // // Check to see if we should add this clsid -- we may be prohibited from // this because it is a duplicate of an exsting clsid, which would be // redundant and furthermore the PACKAGEDETAIL format requires // that all (clsid, clsctx) pairs be unique. Or the clsid itself // may have an unsupported clsctx. This is not a failure // case, so we return success here and simply avoid addding this // class // if ( bIgnoreClsid ) { return ERROR_SUCCESS; } pvData = &ClassDetail; } break; case TYPE_PROGID: // // Get a progid representation from the record. In addition // to retrieving the progid in the form of an allocated string // which must be freed by this funciton, we also retrieve the // location at which to add the progid to the caller's // PACKAGEDETAIL structure. This is necessary since the // ProgId must be part of the CLASSDETAIL structure with which // it is associated. // Status = ProcessProgId( pRecord, pDataDestination, &wszData); if ( ( ERROR_SUCCESS == Status ) && wszData ) { pvData = &wszData; } break; default: ASSERT(FALSE); break; } // // If we were successful in obtaining a representation of the record // that can be stored in the caller's PACKAGEDETAIL structure, attempt // to add it to the structure // if ( pvData ) { Status = AddElement( pvData, pDataDestination); } // // Be sure that in the failure case, we free any memory // that may have been allocated. // if ( ERROR_SUCCESS != Status ) { if (wszData ) { LocalFree( wszData ); } } return Status; } LONG CClassCollection::ProcessExtension( CMsiRecord* pRecord, WCHAR** ppwszExtension) { LONG Status; CMsiValue FileExtension; *ppwszExtension = NULL; // // We retrieve the actual file extension string // Status = pRecord->GetValue( CMsiValue::TYPE_STRING, EXTENSION_COLUMN_EXTENSION, &FileExtension); if ( ERROR_SUCCESS == Status ) { // // We have the value. Note that it does not contain // an initial '.', but the usage of the PACKAGEDETAIL // structure mandates that file extensions begin with the '.' // char, so we will have to prepend the '.' here. // // // First, get space for a copy of the string that includes // the '.' as well as the zero terminator. // *ppwszExtension = (WCHAR*) LocalAlloc( 0, (FileExtension.GetStringSize() + 1 + 1) * sizeof(WCHAR) ); if ( ! *ppwszExtension ) { Status = ERROR_NOT_ENOUGH_MEMORY; return Status; } // // Set the first char to be '.' // **ppwszExtension = L'.'; // // Now append the actual extension to the '.' // lstrcpy( *ppwszExtension + 1, FileExtension.GetStringValue() ); } return Status; } LONG CClassCollection::ProcessClsid( CMsiRecord* pRecord, CLASSDETAIL* pClsid, BOOL* pbIgnoreClsid) { LONG Status; DWORD dwClsCtx; CMsiValue GuidString; CMsiValue ClassContext; // // Clear the clsid to a safe state // memset( pClsid, 0, sizeof( *pClsid ) ); // // Reset out parameters // *pbIgnoreClsid = FALSE; dwClsCtx = 0; // // Retrieve the actual clsid // Status = pRecord->GetValue( CMsiValue::TYPE_STRING, CLSID_COLUMN_CLSID, &GuidString); if ( ERROR_SUCCESS == Status ) { // // Get the clsctx for this clsid // Status = pRecord->GetValue( CMsiValue::TYPE_STRING, CLSID_COLUMN_CONTEXT, &ClassContext); } if ( ERROR_SUCCESS == Status ) { CMsiValue Attribute; WCHAR* wszClassContext; DWORD dwInprocClsCtx; dwInprocClsCtx = 0; // // Retrieve a string representation of the clsctx for this clsid // wszClassContext = ClassContext.GetStringValue(); // // Now map the clsctx strings to COM CLSCTX_* values // if ( 0 == lstrcmpi( wszClassContext, COM_INPROC_CONTEXT) ) { dwInprocClsCtx |= CLSCTX_INPROC_SERVER; } else if ( 0 == lstrcmpi( wszClassContext, COM_INPROCHANDLER_CONTEXT) ) { dwInprocClsCtx |= CLSCTX_INPROC_HANDLER; } else if ( 0 == lstrcmpi( wszClassContext, COM_LOCALSERVER_CONTEXT) ) { dwClsCtx |= CLSCTX_LOCAL_SERVER; } else if ( 0 == lstrcmpi( wszClassContext, COM_REMOTESERVER_CONTEXT) ) { dwClsCtx |= CLSCTX_REMOTE_SERVER; } else { // // If the clsctx is one we do not support, we will ignore it // *pbIgnoreClsid = TRUE; return ERROR_SUCCESS; } BOOL b64Bit; b64Bit = FALSE; // // We must disginguish between 32-bit and 64-bit in-process servers, since // 64-bit Windows does not allows modules of different bitness to coexist in the // same process. If this is an in-process component, we will also check to see // whether it is 64-bit or not. // if ( ( dwInprocClsCtx & CLSCTX_INPROC_HANDLER ) || ( dwInprocClsCtx & CLSCTX_INPROC_SERVER ) ) { // // The Attributes column of the record has a flag indicating bitness -- this // will only fail if the property is NULL // Status = pRecord->GetValue( CMsiValue::TYPE_DWORD, CLSID_COLUMN_ATTRIBUTES, &Attribute); // // Check the flag to see if this is 64-bit // if ( ERROR_SUCCESS == Status ) { b64Bit = Attribute.GetDWORDValue() & MSI_64BIT_CLASS; } else { // // This means the property is NULL, so we interpret that as // meaning the application is not 64 bit // Status = ERROR_SUCCESS; } // // Map this 64-bit clsctx to a custom (non-COM) CLSCTX that // indicates that this is a 64-bit-only in-process class. // if ( ( ERROR_SUCCESS == Status ) && b64Bit ) { if ( dwInprocClsCtx & CLSCTX_INPROC_SERVER ) { dwClsCtx |= CLSCTX64_INPROC_SERVER; } if ( dwInprocClsCtx & CLSCTX_INPROC_HANDLER ) { dwClsCtx |= CLSCTX64_INPROC_HANDLER; } } } // // In the 32-bit case, just or in the values we already computed for // inproc case // if ( ! b64Bit ) { dwClsCtx |= dwInprocClsCtx; } } // // Check to see if this is a duplicate -- we do this because our query // returned results distinct in (clsid, clsctx, attribute). Since we // are mapping attribute to clsctx above and we only support 1 attribute // flag (the 64-bit flag) out of several, we may end up with duplicate // (clsid, clsctx) pairs, and the PACKAGEDETAIL format requires that // we have unique (clsid, clsctx) pairs. Another way to get this would // be if COM introduced new clsctx types which we did not support -- these // would map to zero, and again we could have duplicates // if ( ERROR_SUCCESS == Status ) { CLASSDETAIL* pClassDetail; pClassDetail = NULL; Status = FindClass( GuidString.GetStringValue(), &pClassDetail); // // If we already have an entry for this clsid, check to see if // it has the same clsctx bits -- if so it is a duplicate entry // and we will cease processing it // if ( ( ERROR_SUCCESS == Status ) && pClassDetail ) { *pbIgnoreClsid = ( dwClsCtx & pClassDetail->dwComClassContext ); if ( *pbIgnoreClsid ) { return ERROR_SUCCESS; } } } // // Convert the clsid string to a guid as mandated by the // CLASSDETAIL structure // if ( ERROR_SUCCESS == Status ) { HRESULT hr; hr = CLSIDFromString( GuidString.GetStringValue(), &(pClsid->Clsid)); if ( FAILED(hr) ) { Status = ERROR_GEN_FAILURE; } } // // Set the clsctx we computed above. // if ( ERROR_SUCCESS == Status ) { pClsid->dwComClassContext = dwClsCtx; } return Status; } LONG CClassCollection::ProcessProgId( CMsiRecord* pRecord, DataDestination* pDataDestination, WCHAR** ppwszProgId) { LONG Status; CMsiValue ProgIdString; CMsiValue ClsidString; CLASSDETAIL* pClassDetail; // // We attempt to map a progid record to a // clsid that we've already processed, since // the progid will eventually need to go // inside the clsid's structure. // *ppwszProgId = NULL; pClassDetail = NULL; // // Retrieve the value for the progid itself // Status = pRecord->GetValue( CMsiValue::TYPE_STRING, PROGID_COLUMN_PROGID, &ProgIdString); // // Retrieve the value of the clsid associated with // the progid // if ( ERROR_SUCCESS == Status ) { Status = pRecord->GetValue( CMsiValue::TYPE_STRING, PROGID_COLUMN_CLSID, &ClsidString); } // // We must find the existing CLASSDETAIL structure // that we are maintaining for the progid since the // progid must eventually be referenced in that structure. // if ( ERROR_SUCCESS == Status ) { Status = FindClass( ClsidString.GetStringValue(), &pClassDetail); } if ( ERROR_SUCCESS == Status ) { // // If we have successfully found the class, // if ( pClassDetail ) { // // Give the caller the progid string since // we know that we have a class in which // to place it // *ppwszProgId = ProgIdString.DuplicateString(); if ( ! *ppwszProgId ) { Status = ERROR_NOT_ENOUGH_MEMORY; } else { // // Set the caller's data destination to that of the // progid vector within the clsid associated with this progid // pDataDestination->_ppvData = (void**) &( pClassDetail->prgProgId ); pDataDestination->_pcCurrent = (UINT*) &( pClassDetail->cProgId ); pDataDestination->_pcMax = (UINT*) &( pClassDetail->cMaxProgId ); } } } // // On failure, free any resources we've allocated // if ( ( ERROR_SUCCESS != Status ) && *ppwszProgId ) { LocalFree( *ppwszProgId ); } return Status; } LONG CClassCollection::FindClass( WCHAR* wszClsid, CLASSDETAIL** ppClass) { CLSID Clsid; HRESULT hr; // // Attempt to find a CLASSDETAIL structure in the PACKAGEDETAIL structure // for the clsid given in string form in wszClsid // *ppClass = NULL; // // The PACKAGEDETAIL structure stores the clsid in guid form, // so we must convert the string to that form before searching // hr = CLSIDFromString( wszClsid, &Clsid); if ( FAILED(hr) ) { return ERROR_GEN_FAILURE; } UINT iClsid; // // We now perform a simple linear search for the clsid // for ( iClsid = 0; iClsid < _pPackageDetail->pActInfo->cClasses; iClsid++) { if ( IsEqualGUID( _pPackageDetail->pActInfo->pClasses[iClsid].Clsid, Clsid) ) { *ppClass = &(_pPackageDetail->pActInfo->pClasses[iClsid]); return ERROR_SUCCESS; } } return ERROR_SUCCESS; } void CClassCollection::FreeClassDetail( CLASSDETAIL* pClass ) { DWORD iProgId; // // Free each individual progid string // for ( iProgId = 0; iProgId < pClass->cProgId; iProgId++ ) { LocalFree( pClass->prgProgId[ iProgId ] ); } // // Free the array of progid strings // LocalFree( pClass->prgProgId ); } DataDestination::DataDestination( DWORD dwType, void** prgpvDestination, UINT* pcCurrent, UINT* pcMax ) : _pcCurrent( pcCurrent ), _ppvData( prgpvDestination ), _pcMax ( pcMax ) { // // The size of the elements stored by // the vector referenced from this class // depend on the type of element -- // clsid, file extension, or progid // switch ( dwType ) { case TYPE_EXTENSION: _cbElementSize = sizeof( WCHAR* ); break; case TYPE_CLSID: _cbElementSize = sizeof( CLASSDETAIL ); break; case TYPE_PROGID: _cbElementSize = sizeof( WCHAR* ); break; default: ASSERT(FALSE); } }