//+-------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 2001. // // File: msibase.cpp // // Contents: msi database abstractions // // History: 4-14-2000 adamed Created // //--------------------------------------------------------------------------- #include "precomp.hxx" CMsiState::CMsiState() : _MsiHandle( NULL ) { // // The MSIHANDLE encapsulates the state for // all msi operations / data -- clearing this // member is akin to clearing the state. // } CMsiState::~CMsiState() { // // The lifetime of the object is the lifetime // of the underlying state -- be sure to release it // MsiCloseHandle( _MsiHandle ); } void CMsiState::SetState( MSIHANDLE MsiHandle ) { // // Set the state of this object based on // a handle retrieved from an MSI operation -- // note that this should only be done if this // object has an empty state // ASSERT( ! _MsiHandle ); _MsiHandle = MsiHandle; } MSIHANDLE CMsiState::GetState() { // // Allow callers that need to perform explicit MSI // operations to retrieve state compatible with MSI // return _MsiHandle; } CMsiValue::CMsiValue() : _dwDiscriminant( TYPE_NOT_SET ), _wszValue( NULL ), _cchSize( sizeof( _wszDefaultBuf ) / sizeof( *_wszDefaultBuf ) ) { // // The goal of this initialization is to set this object to // an "empty" state -- consumers must explicitly invoke methods // on this object to alter this condition so that Get methods // will succeed. // } CMsiValue::~CMsiValue() { // // Setting the type to "none" implicitly clears our state // (e.g. allocated memory, any other resources) // SetType( TYPE_NOT_SET ); } DWORD CMsiValue::GetDWORDValue() { ASSERT( TYPE_DWORD == _dwDiscriminant ); // // Retrieve this value as a DWORD -- note that this // does not coerce non-DWORD values to DWORD -- the // value must already be a DWORD for this to have meaning // return _dwValue; } WCHAR* CMsiValue::GetStringValue() { ASSERT( TYPE_STRING == _dwDiscriminant ); // // Retrieve this value as a string -- note that this // does not coerce non-string values to string -- the // value must already be a string for this to have meaning. // Note that the value is returned as a reference to the address // at which this value actually stores the string -- thus, this // may also be used to retrieve the value's buffer so that its // contents may be edited outside the strictures of this class. // return _wszValue; } WCHAR* CMsiValue::DuplicateString() { WCHAR* wszResult; ASSERT( TYPE_STRING == _dwDiscriminant ); // // The caller requires ownership of a duplicate // of this string's data. // // // First, allocate memory for this // ULONG ulNoChars = lstrlen ( _wszValue ) + 1; wszResult = (WCHAR*) LocalAlloc( 0, sizeof(WCHAR*) * ulNoChars ); // // If we successfully obtained room for the string, // copy it // if ( wszResult ) { HRESULT hr; hr = StringCchCopy ( wszResult, ulNoChars, _wszValue); ASSERT(SUCCEEDED(hr)); } return wszResult; } void CMsiValue::SetDWORDValue( DWORD dwValue ) { // // This operation will implicitly set the type // of this value to DWORD // SetType( TYPE_DWORD ); // // Now we can safely set the value // _dwValue = dwValue; } LONG CMsiValue::SetStringValue( WCHAR* wszValue ) { DWORD cchSize; LONG Status; Status = ERROR_SUCCESS; // // This operation will implicitly set the // type of this value to string // SetType( TYPE_STRING ); // // We need to determine the size of this string, // in chars, without the null terminator, in order to // allow this value to represent it // cchSize = lstrlen( wszValue ); if ( cchSize > _cchSize ) { // // Attempt to get space for this string // by setting its size -- if this fails, // our type will be implicitly set to none // Here, allocating one extra byte than required. // Status = SetStringSize( cchSize ); if ( ERROR_SUCCESS != Status ) { return Status; } // // We have room for the string, so copy it // into its newly allocated space // HRESULT hr; hr = StringCchCopy( _wszValue, cchSize+1, wszValue ); ASSERT(SUCCEEDED(hr)); } return Status; } DWORD CMsiValue::GetStringSize() { ASSERT( TYPE_STRING == _dwDiscriminant ); // // Retrieve the size of this string in chars, // WITHOUT the null terminator // return _cchSize; } LONG CMsiValue::SetStringSize( DWORD cchSize ) { ASSERT( TYPE_STRING == _dwDiscriminant ); // // This method only makes sense if the // type of this object is already string // // // If the requested size is less than or // equal to our current size, we already have // enough space -- we can exit now. We do // not "shrink" space, only expand as necessary // if ( cchSize <= _cchSize ) { return ERROR_SUCCESS; } // // At this point, we know we don't have enough // space, so we'll have to allocate it. Before we // do so, reset our type to none so that if we fail // to get space, we can indicate the indeterminate // state. // SetType( TYPE_NOT_SET ); // // Allocate space, and include the zero terminator // _wszValue = new WCHAR [ cchSize + 1 ]; if ( ! _wszValue ) { return ERROR_NOT_ENOUGH_MEMORY; } // // We are successful, remember the current size // _cchSize = cchSize; // // Change the type back to string since we can // safely represent a string of this size // SetType( TYPE_STRING ); return ERROR_SUCCESS; } void CMsiValue::SetType( DWORD dwType ) { // // Setting the type to a new type implicitly clears // state associated with the new type // // // If the current type and requested type are the same // this is a no op and we are done. // if ( dwType == _dwDiscriminant ) { return; } // // If the requested type is string, we need to // set this object to have appropriate state // if ( TYPE_STRING == dwType ) { // // If we have no space for a string // if ( ! _wszValue ) { // // Use the default buffer... // _wszValue = _wszDefaultBuf; // // ... and set the size accordingly // _cchSize = sizeof( _wszDefaultBuf ) / sizeof( *_wszDefaultBuf ); } // // We are done -- this object can now represent a string, though // at this point it must be a string of size _cchSize -- the size // will have to be increased through SetStringSize if there's // a need to represent a larger string // return; } // // If the current type is string, we use the fact that the requested // type is not string as a hint to free the state associated with // the string. This is a heuristic designed to ensure that we // do not continue to hold memory of which we are not actively making // use. // if ( TYPE_STRING == _dwDiscriminant ) { // // If the string's current storage is not that of our default // buffer (which is part of the object itself), we // release that storage as it was allocated on the heap. // if ( _wszValue != _wszDefaultBuf ) { delete [] _wszValue; _wszValue = NULL; } } // // We may now set the type to that requested by the caller // _dwDiscriminant = dwType; } LONG CMsiRecord::GetValue( DWORD dwType, DWORD dwValue, CMsiValue* pMsiValue) { LONG Status = ERROR_SUCCESS; // // Values are the properties of the column of an // msi record -- we are retrieving members of the // record // // // The value is our out parameter -- set it // to the type desired by the caller // pMsiValue->SetType( dwType ); switch ( dwType ) { case CMsiValue::TYPE_STRING: DWORD cchSize; // // We must determine the maximum size of the // string that can be represented by the value // so we can pass it to the msi api // cchSize = pMsiValue->GetStringSize(); // // Attempt to retrieve the string by storing // it in the buffer of the value // Status = MsiRecordGetString( GetState(), dwValue, pMsiValue->GetStringValue(), &cchSize); // // Our attempt to retrieve the string data will // fail if the value's string buffer is not sufficiently // large. // if ( ERROR_MORE_DATA == Status ) { // // In the case where the value's buffer is not large enough, // we explicitly set the size of the value to that of the // size returned by the msi api PLUS a zero terminator -- // this is because the size returned by MSI does NOT // include the zero terminator. // cchSize++; Status = pMsiValue->SetStringSize( cchSize ); // // We now retry the string retrieval since we have the // correct size now. // if ( ERROR_SUCCESS == Status ) { Status = MsiRecordGetString( GetState(), dwValue, pMsiValue->GetStringValue(), &cchSize); } } break; case CMsiValue::TYPE_DWORD: Status = ERROR_INVALID_PARAMETER; int IntegerValue; // // Retrieve an integer by calling the msi api // IntegerValue = MsiRecordGetInteger( GetState(), dwValue); if ( MSI_NULL_INTEGER != IntegerValue ) { // // We now set the value to that retrieved by the api // pMsiValue->SetDWORDValue( (DWORD) IntegerValue ); Status = ERROR_SUCCESS; } break; default: ASSERT( FALSE ); break; } return Status; } LONG CMsiQuery::GetNextRecord( CMsiRecord* pMsiRecord) { LONG Status; MSIHANDLE MsiHandle; // // The MsiViewFetch api will retrieve a record from a query -- // it does this in an enumeration style, so we are retrieving // the next record in the query // Status = MsiViewFetch( GetState(), &MsiHandle); if ( ERROR_SUCCESS == Status ) { // // We successfully obtained an MSIHANDLE corresponding to the // retrieved record, so we use this to set the state of our // abstraction of the record // pMsiRecord->SetState( MsiHandle ); } return Status; } LONG CMsiQuery::UpdateQueryFromFilter( CMsiRecord* pFilterRecord ) { LONG Status; // // The MsiViewExecute api causes the results of the query to // be computed. The filter record passed in allows us to // specify a filter for the query results // Status = MsiViewExecute( GetState(), pFilterRecord ? pFilterRecord->GetState() : NULL ); return Status; } LONG CMsiDatabase::Open( WCHAR* wszPath, DWORD cTransforms, WCHAR** rgwszTransforms) { MSIHANDLE DatabaseHandle; LONG Status; // // The MsiOpenDatabase api abstracts an .msi package // Status = MsiOpenDatabase( wszPath, MSIDBOPEN_READONLY, &DatabaseHandle); if ( ERROR_SUCCESS == Status ) { DWORD iTransform; // // The successful open above does not include transforms -- // we need to add each transform to generate a resultant // database that includes the changes of each transform // // // We apply the transforms in the order in which they are // stored in the vector -- this order conforms to that // specified by the administrator, and since order affects // the result, we must honor the administrator's ordering // for ( iTransform = 0; iTransform < cTransforms; iTransform++ ) { if ( ERROR_SUCCESS == Status ) { // // This api adds the effects of the transform to the // database. // Status = MsiDatabaseApplyTransform( DatabaseHandle, rgwszTransforms[iTransform], 0); } if ( ERROR_SUCCESS != Status ) { // // If we failed to apply a transform, we bail // break; } } if ( ERROR_SUCCESS == Status ) { // // We have successfully created an database of the // package + transforms, so we allow the lifetime of its state // to be controlled by this object // SetState( DatabaseHandle ); } else { // // If we failed to apply a transform, the database // resource is useless, so we free it // MsiCloseHandle( DatabaseHandle ); } } return Status; } LONG CMsiDatabase::OpenQuery( WCHAR* wszQuery, CMsiQuery* pQuery ) { LONG Status; MSIHANDLE MsiHandle; // // This api will initialize a query without comoputing its // results. This will allow the caller finer control over result // computation later, which distinguishes this method from GetQueryResults // Status = MsiDatabaseOpenView( GetState(), wszQuery, &MsiHandle); if ( ERROR_SUCCESS == Status ) { // // Give the caller's query object the state for the query // so that it can control its lifetime // pQuery->SetState( MsiHandle ); } return Status; } LONG CMsiDatabase::GetQueryResults( WCHAR* wszQuery, CMsiQuery* pQuery ) { LONG Status; MSIHANDLE MsiHandle; // // This api will initialize a query without computing the results // Status = MsiDatabaseOpenView( GetState(), wszQuery, &MsiHandle); if ( ERROR_SUCCESS == Status ) { // // The semantics of this method are that the caller may also // enumerate results after calling the method, so we must // now computer the results so that the caller may enumerate -- // the api below will do this // Status = MsiViewExecute( MsiHandle, NULL); if ( ERROR_SUCCESS == Status ) { // // In the success case, we give the lifetime of the msi // state to the query object // pQuery->SetState( MsiHandle ); } else { // // On failure, we must clear the msi query state // since it is useless now. // MsiCloseHandle( MsiHandle ); } } return Status; } LONG CMsiDatabase::TableExists( WCHAR* wszTableName, BOOL* pbTableExists ) { MSICONDITION TableState; TableState = MsiDatabaseIsTablePersistent( GetState(), wszTableName ); if ( MSICONDITION_ERROR == TableState ) { return ERROR_INVALID_PARAMETER; } *pbTableExists = MSICONDITION_TRUE == TableState; return ERROR_SUCCESS; }