You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1499 lines
39 KiB
1499 lines
39 KiB
//+--------------------------------------------------------------------------
|
|
//
|
|
// 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 )
|
|
{
|
|
ULONG ulNoChars;
|
|
HRESULT hr;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
ulNoChars = FileExtension.GetStringSize() + 1 + 1;
|
|
*ppwszExtension = (WCHAR*) LocalAlloc(
|
|
0,
|
|
ulNoChars * 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 '.'
|
|
//
|
|
|
|
hr = StringCchCopy( *ppwszExtension + 1, ulNoChars - 1, FileExtension.GetStringValue() );
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
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 (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, wszClassContext, -1, COM_INPROC_CONTEXT, -1) == CSTR_EQUAL)
|
|
{
|
|
dwInprocClsCtx |= CLSCTX_INPROC_SERVER;
|
|
}
|
|
else if (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, wszClassContext, -1, COM_INPROCHANDLER_CONTEXT, -1) == CSTR_EQUAL)
|
|
{
|
|
dwInprocClsCtx |= CLSCTX_INPROC_HANDLER;
|
|
}
|
|
else if (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, wszClassContext, -1, COM_LOCALSERVER_CONTEXT, -1) == CSTR_EQUAL)
|
|
{
|
|
dwClsCtx |= CLSCTX_LOCAL_SERVER;
|
|
}
|
|
else if (CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, wszClassContext, -1, COM_REMOTESERVER_CONTEXT, -1) == CSTR_EQUAL)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|