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.
692 lines
16 KiB
692 lines
16 KiB
//+--------------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|