MSIVAL.CPP

#if 0  // makefile definitions, to build: %vcbin%\nmake -fMsiVal.cpp 

# Copyright 1997 - 1998 Microsoft Corporation

DESCRIPTION = Msi Database Validator Using External API
MODULENAME = MsiVal
SUBSYSTEM = console
FILEVERSION = Msi
!include <MsiTool.Mak>
!if 0 #nmake skips the rest of this file
#endif // end of makefile definitions

// Required headers
#define WINDOWS_LEAN_AND_MEAN // faster compile
#include <windows.h>

#define IDS_NoError 0
#define IDS_DuplicateKey 1
#define IDS_Required 2
#define IDS_BadLink 3
#define IDS_Overflow 4
#define IDS_Underflow 5
#define IDS_NotInSet 6
#define IDS_BadVersion 7
#define IDS_BadCase 8
#define IDS_BadGuid 9
#define IDS_BadWildCard 10
#define IDS_BadIdentifier 11
#define IDS_BadLanguage 12
#define IDS_BadFileName 13
#define IDS_BadPath 14
#define IDS_BadCondition 15
#define IDS_BadFormatted 16
#define IDS_BadTemplate 17
#define IDS_BadDefaultDir 18
#define IDS_BadRegPath 19
#define IDS_BadCustomSource 20
#define IDS_BadProperty 21
#define IDS_MissingData 22
#define IDS_BadCabinet 23
#define IDS_BadCategory 24
#define IDS_BadKeyTable 25
#define IDS_BadMaxMinValues 26
#define IDS_BadShortcut 27
#define IDS_StringOverflow 28
#define IDS_UndefinedError 29
#define IDS_MissingEntry 30
#define IDS_BadLocalizeAttrib 31

#ifndef RC_INVOKED // start of source code
#include <stdio.h> // printf/wprintf
#include <tchar.h> // define UNICODE=1 on nmake command line to build UNICODE
#include "MsiQuery.h"

TCHAR* g_szErrorContext = 0; // Global error string
HANDLE g_hStdOut = 0; // Global handle

// Function prototypes
void Display(LPCTSTR szMessage);
void CheckMsi(UINT iStat, TCHAR* szContext);
BOOL CheckMissingColumns(MSIHANDLE hDatabase);
BOOL Validate(MSIHANDLE hDatabase);
BOOL ValidateOrganizationInstallSequence(MSIHANDLE hDatabase);
BOOL ValidateRequired(MSIHANDLE hDatabase);
BOOL InDialogTable(MSIHANDLE hDatabase, LPCTSTR szAction);
BOOL InCustomActionTable(MSIHANDLE hDatabase, LPCTSTR szAction);
BOOL ValidateInstallSequence(MSIHANDLE hDatabase);

// SQL queries
const TCHAR szSQLTableCatalog[] = TEXT("SELECT `Name` FROM `_Tables`");
const TCHAR szSQLTable[] = TEXT("SELECT * FROM ");
const TCHAR szSQLColMissing[] = TEXT("SELECT `Table`, `Number`, `Name`, `Type` FROM `_Columns` WHERE `Table`=? AND `Name`=?");
const TCHAR szSQLValidationTable[] = TEXT("SELECT `Table`, `Column` FROM `_Validation`");
const TCHAR szSQLInstallSeqTable[] = TEXT("SELECT `Action`, `Sequence` FROM `InstallSequence` ORDER BY `Sequence`");
const TCHAR szSQLInstallValidate[] = TEXT("SELECT `Action`, `SectionFlag` FROM `_InstallValidate` WHERE `Action`=?");
const TCHAR szSQLRequiredTable[] = TEXT("SELECT `Table`, `Value`, `KeyCount` FROM `_Required` ORDER BY `Table`");
const TCHAR szSQLDialogTable[] = TEXT("SELECT `Dialog` FROM `Dialog` WHERE `Dialog`=?");
const TCHAR szSQLCustomActionTable[] = TEXT("SELECT `Action` FROM `CustomAction` WHERE `Action`=?");
const TCHAR szSQLSeqTableQueryNotNull[] = TEXT("SELECT `Dependent` FROM `_Sequence` WHERE `Action`=? AND `Marker`=210 AND `After`=0");
const TCHAR szSQLSeqTableQueryNull[] = TEXT("SELECT `Dependent` FROM `_Sequence` WHERE `Action`=? AND `Marker`=0 AND `After`=1 AND `Optional`=0");
const TCHAR szSQLSeqTableAddCol[] = TEXT("ALTER TABLE `_Sequence` ADD `Marker` SHORT TEMPORARY");
const TCHAR szSQLSeqMarkerInit[] = TEXT("UPDATE `_Sequence` SET `Marker`=0");

const int iMaxNumColumns = 32;
const int cchBuffer = 4096;
const int cbName = 64;

const int cchDisplayBuf = 4096;

// InstallSequence sectional constants
const int isdSearch = 0x00000001L;
const int isdCosting = 0x00000002L;
const int isdSelection = 0x00000004L;
const int isdAdvertise = 0x00000008L; // before execute because must be called before InstallValidate
const int isdExecution = 0x00000010L;

// InstallSequence sectional devisors
const TCHAR szEndSearch[] = TEXT("CostInitialize"); // end Search, begin Costing
const TCHAR szEndCosting[] = TEXT("CostFinalize"); // end Costing, begin Selection
const TCHAR szEndSelection[] = TEXT("RegisterProduct"); // end Selection, begin Advertise
const TCHAR szEndAdvertise[] = TEXT("InstallValidate"); // end Advertise, begin Execution
const TCHAR szReset[] = TEXT("ExecuteFinalize"); // reset to Search

// InstallSequence divisions
const TCHAR szSearch[] = TEXT("Search");
const TCHAR szCosting[] = TEXT("Costing");
const TCHAR szSelection[] = TEXT("Selection");
const TCHAR szAdvertise[] = TEXT("Advertise");
const TCHAR szExecution[] = TEXT("Execution");

//_______________________________________________________________________________________________________________
//
// _tmain -- UNICODE/ANSI main function
//
// Driver routine
//_______________________________________________________________________________________________________________

extern "C" int __cdecl _tmain(int argc, TCHAR* argv[])
{
// Determine handle
g_hStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (g_hStdOut == INVALID_HANDLE_VALUE)
g_hStdOut = 0; // non-zero if stdout redirected or piped

// Bool to allow user to specify option to turn OFF InstallSequence and Required Validation
//!! So that databases won't fail if don't have the _InstallValidate and/or _Required tables
BOOL fOff = FALSE;

if (argc != 2 && argc != 3)
{
_tprintf(TEXT("USAGE:\n msival.exe {database}\n msival.exe {database} -OFF"));
return 1;
}

if (argc == 2 && (lstrcmp(argv[1],TEXT("-?")) == 0 || lstrcmp(argv[1],TEXT("/?")) == 0))
{
_tprintf(TEXT("USAGE:\n msival.exe {database}\n")
TEXT("msival.exe {database} -OFF\n")
TEXT("NOTE:\n")
TEXT(" For validation to proceed. . .\n")
TEXT("\tTables required:\n")
TEXT("\t _Validation (always)\n")
TEXT("\t _InstallValidate (unless -OFF)\n")
TEXT("\t _Required (unless -OFF)\n")
TEXT("\t _Sequence (unless -OFF)\n"));
return 0;
}

if (argc == 3)
{
if (lstrcmp(argv[2],TEXT("-OFF")) == 0 || lstrcmp(argv[2],TEXT("/OFF")) == 0
|| lstrcmp(argv[2],TEXT("-off")) == 0 || lstrcmp(argv[2],TEXT("/off")) == 0)
fOff = TRUE;
else
{
_tprintf(TEXT("USAGE:\n msival.exe {database} -OFF\n"));
return 0;
}
}

BOOL fDataValid = TRUE;
BOOL fColValid = TRUE;
BOOL fSeqOrgValid = TRUE;
BOOL fSeqOrderValid = TRUE;
BOOL fReqValid = TRUE;
try
{
PMSIHANDLE hDatabase;
CheckMsi(MsiOpenDatabase(argv[1],MSIDBOPEN_READONLY,&hDatabase),TEXT("OpenDatabase"));
_tprintf(TEXT("\nINFO: Validating for missing columns. . .\n\n"));
fColValid = CheckMissingColumns(hDatabase);
_tprintf(TEXT("\nINFO: Validating data and foreign keys. . .\n\n"));
fDataValid = Validate(hDatabase);
if (fOff)
{
// Print out warning of database not exactly valid since skipping these validations
_tprintf(TEXT("WARNING!\n Skipping InstallSequence and Required Validation.\n Database may not be completely valid\n"));
}
else
{
_tprintf(TEXT("\nINFO: Validating Install Sequence Table Organization. . .\n\n"));
fSeqOrgValid = ValidateOrganizationInstallSequence(hDatabase);
_tprintf(TEXT("\nINFO: Validating Sequence of Actions In Install Sequence Table. . .\n\n"));
fSeqOrderValid = ValidateInstallSequence(hDatabase);
_tprintf(TEXT("\nINFO: Validating Required Values. . .\n\n"));
fReqValid = ValidateRequired(hDatabase);
}
if (fDataValid && fColValid && fSeqOrgValid && fReqValid && fSeqOrderValid)
_tprintf(TEXT("Database is valid: %s"), argv[1]);
}
catch (UINT iError)
{
_tprintf(TEXT("\n%s error %i"), g_szErrorContext, iError);
MsiCloseAllHandles();
return 1;
}
catch (...)
{
_tprintf(TEXT("\n%s"), TEXT("Unhandled exception"));
MsiCloseAllHandles();
return 99;
}
int iOpenHandles = MsiCloseAllHandles(); // diagnostic check only
if (iOpenHandles != 0)
_tprintf(TEXT("\n%i Handle(s) not closed"), iOpenHandles);
return (fDataValid && fColValid && fSeqOrgValid && fReqValid && fSeqOrderValid) ? 0 : 1;
}


void CheckMsi(UINT iStat, TCHAR* szContext)
/*----------------------------------------------------------------------------------
CheckMsi -- Routine to check return status for error and throw exception if error.
Arguments:
iStat -- error status
szContext -- error string
Returns:
none, but throws error if one
-------------------------------------------------------------------------------------*/
{
if (iStat != ERROR_SUCCESS)
{
g_szErrorContext = szContext;
throw iStat;
}
}

BOOL CheckMissingColumns(MSIHANDLE hDatabase)
/*---------------------------------------------------------------------
CheckMissingColumns -- used _Validation table and _Columns catalog to
determine if any columns/tables are not listed. All columns in
_Validation table must be listed in the _Columns catalog. If a column
is optional and not used in the database, then it should not be found
in the _Validation table or the _Columns catalog. Normal validation
catches the instance where a column is defined in the _Columns catalog
but not in the _Validation table.
---------------------------------------------------------------------*/
{
PMSIHANDLE hValidationView = 0;
PMSIHANDLE hColCatalogView = 0;
PMSIHANDLE hValidationRecord = 0;
PMSIHANDLE hColCatalogRecord = 0;
PMSIHANDLE hExecRecord = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLColMissing, &hColCatalogView), TEXT("OpenColumnCatalogView"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLValidationTable, &hValidationView), TEXT("OpenValidationTableView"));

UINT iRet = 0;
TCHAR szTable[cbName] = {0};
TCHAR szColumn[cbName] = {0};
unsigned long cchTableBuf = sizeof(szTable)/sizeof(TCHAR);
unsigned long cchColumnBuf = sizeof(szColumn)/sizeof(TCHAR);
BOOL fStat = TRUE;

hExecRecord = MsiCreateRecord(2);
CheckMsi(MsiViewExecute(hValidationView, 0), TEXT("ExecuteValidationView"));
for (;;)
{
iRet = MsiViewFetch(hValidationView, &hValidationRecord);
if (iRet == ERROR_NO_MORE_ITEMS || !hValidationRecord)
break;
CheckMsi(iRet, TEXT("ColumnCatalogFetch"));
CheckMsi(MsiRecordGetString(hValidationRecord, 1, szTable, &cchTableBuf), TEXT("GetTableName"));
cchTableBuf = sizeof(szTable)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hValidationRecord, 2, szColumn, &cchColumnBuf), TEXT("GetColumnName"));
cchColumnBuf = sizeof(szColumn)/sizeof(TCHAR);
CheckMsi(MsiRecordSetString(hExecRecord, 1, szTable), TEXT("SetTableName"));
CheckMsi(MsiRecordSetString(hExecRecord, 2, szColumn), TEXT("SetColumnName"));
CheckMsi(MsiViewExecute(hColCatalogView, hExecRecord), TEXT("ExecuteColumnCatalogView"));
iRet = MsiViewFetch(hColCatalogView, &hColCatalogRecord);
if (iRet == ERROR_NO_MORE_ITEMS || !hColCatalogRecord)
{
// Error --> Missing from database
TCHAR szMsgBuf[150];
const TCHAR* szMessage = (TCHAR*)IDS_MissingEntry;
const TCHAR** pszMsg;
pszMsg = &szMessage;
::LoadString(0, *(unsigned*)pszMsg, szMsgBuf, sizeof(szMsgBuf)/sizeof(TCHAR));
*pszMsg = szMsgBuf;
_tprintf(TEXT("Table.Column: %s.%s Message: %s\n"), szTable, szColumn, szMsgBuf);
fStat = FALSE;
}
CheckMsi(MsiViewClose(hColCatalogView), TEXT("CloseView"));
}
MsiViewClose(hValidationView);

return fStat;
}

BOOL InDialogTable(MSIHANDLE hDatabase, LPCTSTR szAction)
{
PMSIHANDLE hviewDialogTable = 0;
PMSIHANDLE hrecExecute = 0;
PMSIHANDLE hrecFetch = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLDialogTable, &hviewDialogTable), TEXT("DialogTableOpenView"));
hrecExecute = MsiCreateRecord(1);
CheckMsi(hrecExecute == NULL, TEXT("CreateRecord"));
CheckMsi(MsiRecordSetString(hrecExecute, 1, szAction), TEXT("RecordSetString"));
CheckMsi(MsiViewExecute(hviewDialogTable, hrecExecute), TEXT("DialogTableViewExecute"));
UINT iStat = MsiViewFetch(hviewDialogTable, &hrecFetch);
CheckMsi(MsiViewClose(hviewDialogTable), TEXT("CloseDialogTableView"));
if (iStat == ERROR_SUCCESS && hrecFetch != 0)
return TRUE;
return FALSE;
}

BOOL InCustomActionTable(MSIHANDLE hDatabase, LPCTSTR szAction)
{
PMSIHANDLE hviewCustomActionTable = 0;
PMSIHANDLE hrecExecute = 0;
PMSIHANDLE hrecFetch = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLCustomActionTable, &hviewCustomActionTable), TEXT("CustomActionTableOpenView"));
hrecExecute = MsiCreateRecord(1);
CheckMsi(hrecExecute == NULL, TEXT("CreateRecord"));
CheckMsi(MsiRecordSetString(hrecExecute, 1, szAction), TEXT("RecordSetString"));
CheckMsi(MsiViewExecute(hviewCustomActionTable, hrecExecute), TEXT("CustomActionTableViewExecute"));
UINT iStat = MsiViewFetch(hviewCustomActionTable, &hrecFetch);
CheckMsi(MsiViewClose(hviewCustomActionTable), TEXT("CloseCustomActionTableView"));
if (iStat == ERROR_SUCCESS && hrecFetch != 0)
return TRUE;
return FALSE;
}

BOOL ValidateOrganizationInstallSequence(MSIHANDLE hDatabase)
/*---------------------------------------------------------------------------------------
ValidateOrganizationInstallSequence -- Routine to validate InstallSequence table of database. Uses
the _InstallValidate table as the basis for the validation.
-----------------------------------------------------------------------------------------*/
{
PMSIHANDLE hviewInstallTable = 0;
PMSIHANDLE hviewValInstallTable = 0;
PMSIHANDLE hrecValExecute = 0;
PMSIHANDLE hrecInstallFetch = 0;
PMSIHANDLE hrecValFetch = 0;

int isd = isdSearch; // initialize section definition to Search

BOOL fValid = TRUE; // validation status
BOOL fRequireExecute = FALSE; // if script operations, must call ExecuteFinalize

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallSeqTable, &hviewInstallTable), TEXT("OpenView on InstallSequence table"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallValidate, &hviewValInstallTable), TEXT("OpenView on _InstallValidate table"));
CheckMsi(MsiViewExecute(hviewInstallTable, 0), TEXT("ExecuteView InstallSequence view"));

hrecValExecute = MsiCreateRecord(1);
CheckMsi(hrecValExecute == NULL, TEXT("CreateRecord"));
UINT iRet = ERROR_SUCCESS;
while ((iRet = MsiViewFetch(hviewInstallTable, &hrecInstallFetch)) != ERROR_NO_MORE_ITEMS)
{
CheckMsi(iRet, TEXT("InstallSequenceTableFetch"));
if (!hrecInstallFetch)
break;
TCHAR szAction[255] = {0};
DWORD cbAction = sizeof(szAction)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecInstallFetch, 1, szAction, &cbAction), TEXT("InstallFetchRecGetString Error"));
CheckMsi(MsiRecordSetString(hrecValExecute, 1, szAction), TEXT("ValExecuteRecSetString Error"));
CheckMsi(MsiViewExecute(hviewValInstallTable, hrecValExecute), TEXT("Execute _InstallValidate view"));

// Determine if have to switch the current section state
// Depends on a certain action defined as the boundary
// This action is required in the InstallSequence table -- CostInitialize, CostFinalize, InstallValidate
// ExecuteFinalize can cause a reset so you execute one script and then continue again
// ExecuteFinalize must be called if any execution operations exist
if (lstrcmp(szAction, szEndSearch) == 0)
isd = isdCosting;
else if (lstrcmp(szAction, szEndCosting) == 0)
isd = isdSelection;
else if (lstrcmp(szAction, szEndSelection) == 0)
isd = isdAdvertise;
else if (lstrcmp(szAction, szEndAdvertise) == 0)
isd = isdExecution;
else if (lstrcmp(szAction, szReset) == 0)
{
if (fRequireExecute)
fRequireExecute = FALSE;
else
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Action: '%s' Can Only Be Called When Script Operations Exist To Be Executed\n"), szAction);
Display(szError);
}
isd = isdSearch; // reset to search
}

// Validate action, using the _InstallValidate table to obtain the section mask to which
// the action belongs and then comparing with the current section state
if ((iRet = MsiViewFetch(hviewValInstallTable, &hrecValFetch)) == ERROR_NO_MORE_ITEMS)
{
// Action not found in _InstallValidate table
// Check to see if CustomAction (located in CustomAction table) or Dialog (located in Dialog table)
if (!InDialogTable(hDatabase, szAction) && !InCustomActionTable(hDatabase, szAction))
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Action: '%s' -- Not in _InstallValidate, CustomAction or Dialog tables\n"), szAction);
Display(szError);
fValid = FALSE;
}
}
else
{
// Action found in _InstallValidate table
// Obtain the SectionFlag
// Compare the SectionFlag to the current flag and determine if match
// If not match, then ERROR --> incorrect section (print action, current section, correct section)
CheckMsi(iRet, TEXT("_InstallValidateFetch Error"));
int iSection = MsiRecordGetInteger(hrecValFetch, 2);
CheckMsi(iSection == MSI_NULL_INTEGER, TEXT("ValFetchRecGetInteger"));
if (iSection & isd)
{
// Action located in correct section, found
if (iSection == isdExecution || iSection == isdAdvertise)
fRequireExecute = TRUE;
}
else
{
// Action not in correct section
// Print out error --> action, current section, correct section
TCHAR szError[cchBuffer] = {0};
TCHAR szCurrentSect[cchBuffer] = {0};
TCHAR szCorrectSect[cchBuffer] = {0};
switch (isd)
{
case isdSearch: lstrcpy(szCurrentSect, szSearch); break;
case isdCosting: lstrcpy(szCurrentSect, szCosting); break;
case isdSelection: lstrcpy(szCurrentSect, szSelection); break;
case isdAdvertise: lstrcpy(szCurrentSect, szAdvertise); break;
case isdExecution: lstrcpy(szCurrentSect, szExecution); break;
}
switch (iSection)
{
case isdSearch: lstrcpy(szCorrectSect, szSearch); break;
case isdCosting: lstrcpy(szCorrectSect, szCosting); break;
case isdSelection: lstrcpy(szCorrectSect, szSelection); break;
case isdAdvertise: lstrcpy(szCorrectSect, szAdvertise); break;
case isdExecution: lstrcpy(szCorrectSect, szExecution); break;
}
wsprintf(szError, TEXT("ERROR: Action: '%s' CurrentSection: '%s' CorrectSection: '%s'\n"), szAction, szCurrentSect, szCorrectSect);
Display(szError);
fValid = FALSE;
}
}
CheckMsi(MsiViewClose(hviewValInstallTable), TEXT("ValInstallTableViewClose"));
}
if (fRequireExecute)
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: ExecuteFinalize must be called as there are script operations\n"));
Display(szError);
fValid = FALSE;
}
return fValid;
}


BOOL ValidateRequired(MSIHANDLE hDatabase)
/*-----------------------------------------------------------------------------------
ValidateRequired -- Uses the _Required table and checks the tables listed for the
'required' values that are listed in the table.

-------------------------------------------------------------------------------------*/
{
PMSIHANDLE hviewRequiredTable = 0;
PMSIHANDLE hviewTable = 0;
PMSIHANDLE hrecTableExecute = 0;
PMSIHANDLE hrecRequiredFetch = 0;
PMSIHANDLE hrecTableFetch = 0;
PMSIHANDLE hrecColInfo = 0;

BOOL fValid = TRUE;
BOOL fFirstRun = TRUE;
UINT iStat = ERROR_SUCCESS;

TCHAR szPrevTable[100] = {0};
TCHAR szTable[100] = {0};
TCHAR szValue[256] = {0};
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLRequiredTable, &hviewRequiredTable), TEXT("OpenViewRequiredTable"));
CheckMsi(MsiViewExecute(hviewRequiredTable, 0), TEXT("RequiredTableViewExecute"));
while ((iStat = MsiViewFetch(hviewRequiredTable, &hrecRequiredFetch)) != ERROR_NO_MORE_ITEMS)
{
CheckMsi(iStat, TEXT("RequiredTableViewFetch"));
if (!hrecRequiredFetch)
break;
int cPrimaryKeys = MsiRecordGetInteger(hrecRequiredFetch, 3);
DWORD cbTable = sizeof(szTable)/sizeof(TCHAR);
DWORD cbValue = sizeof(szValue)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecRequiredFetch, 1, szTable, &cbTable), TEXT("RequiredTableRecordGetString"));
CheckMsi(MsiRecordGetString(hrecRequiredFetch, 2, szValue, &cbValue), TEXT("RequiredTableRecordGetString"));
if (fFirstRun)
fFirstRun = FALSE;
else
CheckMsi(MsiViewClose(hviewTable), TEXT("TableViewClose"));
hrecTableExecute = MsiCreateRecord(cPrimaryKeys);
if (hrecTableExecute == 0)
return FALSE;

if (lstrcmp(szPrevTable, szTable) != 0)
{
// New table, need to open a new view.
TCHAR szSQL[1024] = {0};
PMSIHANDLE hrecPrimaryKeys = 0;
TCHAR szKeyColName[50] = {0};
DWORD cbKey = sizeof(szKeyColName)/sizeof(TCHAR);
CheckMsi(MsiDatabaseGetPrimaryKeys(hDatabase, szTable, &hrecPrimaryKeys), TEXT("DatabaseGetPrimaryKeys"));
CheckMsi(MsiRecordGetString(hrecPrimaryKeys, 1, szKeyColName, &cbKey), TEXT("PrimaryKeysRecordGetString"));
CheckMsi(MsiRecordGetFieldCount(hrecPrimaryKeys) != cPrimaryKeys, TEXT("PrimaryKeyCountWrong"));
CheckMsi(cPrimaryKeys == ERROR_INVALID_HANDLE, TEXT("PrimaryKeysRecordGetFieldCount"));

// Develop query of table to be checked
int cchWritten = wsprintf(szSQL, TEXT("SELECT * FROM `%s` WHERE `%s`=?"), szTable, szKeyColName);
int cchAddition = cchWritten;
for (int i = 2; i <= cPrimaryKeys; i++)
{
cbKey = sizeof(szKeyColName)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecPrimaryKeys, i, szKeyColName, &cbKey), TEXT("PrimaryKeysRecordGetString"));
cchWritten = wsprintf(szSQL + cchAddition, TEXT(" AND `%s`=?"), szKeyColName);
cchAddition = cchWritten;
}
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQL, &hviewTable), TEXT("DatabaseOpenView"));
CheckMsi(MsiViewGetColumnInfo(hviewTable, MSICOLINFO_TYPES, &hrecColInfo), TEXT("GetColumnInfo"));
lstrcpy(szPrevTable, szTable);
}

// Fill in execute record with the key data values
TCHAR* pch = szValue;
TCHAR szKeyValue[256] = {0};
TCHAR szType[32] = {0};
DWORD cbType = sizeof(szType)/sizeof(TCHAR);
int nDex = 0;
for (int j = 1; j <= cPrimaryKeys; j++)
{
while (pch != 0 && *pch != TEXT(';') && *pch != 0)
szKeyValue[nDex++] = *pch++;
szKeyValue[nDex] = 0;
pch++; // for ; or 0
cbType = sizeof(szType)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecColInfo, j, szType, &cbType), TEXT("ColInfoGetString"));
if (szType != 0 && *szType == TEXT('s'))
CheckMsi(MsiRecordSetString(hrecTableExecute, j, szKeyValue), TEXT("TableExecuteRecordSetString"));
else // integer primary key
CheckMsi(MsiRecordSetInteger(hrecTableExecute, j, _ttoi(szKeyValue)), TEXT("TableExecuteRecordSetInteger"));
nDex = 0;
}

// Execute view and attempt to fetch listed item from table
CheckMsi(MsiViewExecute(hviewTable, hrecTableExecute), TEXT("TableViewExecute"));
iStat = MsiViewFetch(hviewTable, &hrecTableFetch);
if (iStat == ERROR_NO_MORE_ITEMS)
{
// Value not found
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Value: '%s' Is Required In Table: '%s'\n"), szValue, szTable);
Display(szError);
fValid = FALSE;
}
else if (iStat != ERROR_SUCCESS)
CheckMsi(iStat, TEXT("TableViewFetch"));
}

return fValid;

}


BOOL ValidateInstallSequence(MSIHANDLE hDatabase)
/*----------------------------------------------------------------------------
ValidateInstallSequence -- validates the order of the actions in the
InstallSequence table to ensure that they are allowed by the _Sequence table.
The _Sequence table is required for this validation.
------------------------------------------------------------------------------*/
{
BOOL fValid = TRUE;

PMSIHANDLE hviewInstallTable = 0;
PMSIHANDLE hviewSeqQueryNull = 0;
PMSIHANDLE hviewSeqQueryNotNull = 0;
PMSIHANDLE hviewSeqUpdate = 0;
PMSIHANDLE hviewSeqAddColumn = 0;
PMSIHANDLE hviewSeqMarkerInit = 0;
PMSIHANDLE hrecSeqUpdateExecute = 0;
PMSIHANDLE hrecQueryExecute = 0;
PMSIHANDLE hrecInstallFetch = 0;
PMSIHANDLE hrecQueryNullFetch = 0;
PMSIHANDLE hrecQueryNotNullFetch= 0;

// Create the temporary marking column for the _Sequence table (this will store the sequence #s of the Dependent Actions)
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableAddCol, &hviewSeqAddColumn), TEXT("_SequenceTableAddColOpenView"));
CheckMsi(MsiViewExecute(hviewSeqAddColumn, 0), TEXT("_SequenceTableAddColExecute"));
CheckMsi(MsiViewClose(hviewSeqAddColumn), TEXT("_SequenceTableAddColClose"));

// Initialize the temporary marking column to zero
//!! NO INSTALL SEQUENCE ACTIONS CAN HAVE A ZERO SEQUENCE # AS ZERO IS CONSIDERED "NULL"
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqMarkerInit, &hviewSeqMarkerInit), TEXT("_SequenceTableMarkerInitOpenView"));
CheckMsi(MsiViewExecute(hviewSeqMarkerInit, 0), TEXT("_SequenceTableMarkerInitExecute"));
CheckMsi(MsiViewClose(hviewSeqMarkerInit), TEXT("_SequenceTableMarkerInitClose"));

// Open view on InstallSequence table and order by the Sequence #
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallSeqTable, &hviewInstallTable), TEXT("InstallSequenceTableOpenView"));
CheckMsi(MsiViewExecute(hviewInstallTable, 0), TEXT("InstallSequenceTableExecute"));

// Open the two query views on _Sequence table for determining the validity of the actions
// Create execution record
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableQueryNull, &hviewSeqQueryNull), TEXT("SequenceTableQueryNullOpenView"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableQueryNotNull, &hviewSeqQueryNotNull), TEXT("_SequenceTableQueryNotNullOpenView"));
hrecQueryExecute = MsiCreateRecord(1); // for action
CheckMsi(hrecQueryExecute == 0, TEXT("QueryExecuteCreateRecord"));
hrecSeqUpdateExecute = MsiCreateRecord(1); // for action
CheckMsi(hrecSeqUpdateExecute == 0, TEXT("UpdateExecuteCreateRecord"));

// Start fetching actions from the InstallSequence table
UINT iStat1 = ERROR_SUCCESS;
UINT iStat2 = ERROR_SUCCESS;
TCHAR szSQLUpdateQuery[4096] = {0};
TCHAR szAction[100] = {0};
int iSequence = 0;
for (;;)
{
iStat1 = MsiViewFetch(hviewInstallTable, &hrecInstallFetch);
if (iStat1 == ERROR_NO_MORE_ITEMS || !hrecInstallFetch)
break;
CheckMsi(iStat1, TEXT("InstallTableFetch"));
DWORD cbSize = sizeof(szAction)/sizeof(TCHAR);

// Obtain name of action and Sequence # of action in InstallSequence table
CheckMsi(MsiRecordGetString(hrecInstallFetch, 1, szAction, &cbSize), TEXT("InstallFetchRecordGetString"));
iSequence = MsiRecordGetInteger(hrecInstallFetch, 2);
CheckMsi(iSequence == MSI_NULL_INTEGER, TEXT("InstallFetchRecordGetInteger"));

// Prepare execution records
CheckMsi(MsiRecordSetString(hrecQueryExecute, 1, szAction), TEXT("_SequenceQueryExecuteRecordSetString"));
CheckMsi(MsiRecordSetString(hrecSeqUpdateExecute, 1, szAction), TEXT("_SequenceUpdateExecuteRecordSetString"));

// Execute _Sequence query table views
CheckMsi(MsiViewExecute(hviewSeqQueryNull, hrecQueryExecute), TEXT("_SequenceQueryNullExecute"));
CheckMsi(MsiViewExecute(hviewSeqQueryNotNull, hrecQueryExecute), TEXT("_SequenceQueryNotNullExecute"));

// Fetch from _Sequence table. If resultant set, then ERROR
// Following are the possibilities and whether permitted:
// Action After Dependent Where Dependent Is Required And Temp Sequence Column Is Zero --> ERROR
// Action After Dependent Where Dependent Is Required And Temp Sequence Column Is Greater Than Zero --> CORRECT
// Action After Dependent Where Dependent Is Optional And Temp Sequence Column Is Zero --> CORRECT
// Action After Dependent Where Dependent Is Optional And Temp Sequence Column Is Greater Than Zero --> CORRECT
// Action Before Dependent Where Dependent Is Optional Or Required And Temp Sequence Column Is Zero --> CORRECT
// Action Before Dependent Where Dependent Is Optional Or Requred And Temp Sequence Column Is Greater Than Zero --> ERROR

// ** Only issue is when Action Is After Optional Dependent And Temp Sequence Column Is Zero because we
// ** have no way of knowing whether the action will be later (in which case it would be invalid. This is
// ** ensured to be successful though by proper authoring of the _Sequence table. If an Action comes after
// ** the Optional Dependent Action, then the _Sequence table must also be authored with the Dependent Action
// ** listed as coming before that Action (so if we come later, and find a result set, we flag this case).

// If return is not equal to ERROR_NO_MORE_ITEMS, then ERROR and Output Action
//!! Any more info to output ??
iStat1 = MsiViewFetch(hviewSeqQueryNull, &hrecQueryNullFetch);
iStat2 = MsiViewFetch(hviewSeqQueryNotNull, &hrecQueryNotNullFetch);

if (iStat1 != ERROR_NO_MORE_ITEMS || iStat2 != ERROR_NO_MORE_ITEMS)
{
TCHAR szError[1024] = {0};
TCHAR szDependent[100] = {0};
DWORD cb = sizeof(szDependent)/sizeof(TCHAR);
if (iStat1 != ERROR_NO_MORE_ITEMS)
CheckMsi(MsiRecordGetString(hrecQueryNullFetch, 1, szDependent, &cb), TEXT("MsiRecordGetString"));

else 
CheckMsi(MsiRecordGetString(hrecQueryNotNullFetch, 1, szDependent, &cb), TEXT("MsiRecordGetString"));
wsprintf(szError, TEXT("ERROR: %s Action Is Sequenced Incorrectly (Dependent=%s)\n"), szAction, szDependent);
Display(szError);
fValid = FALSE;
}

// Update _Sequence table temporary Sequence column (that we created) with the install sequence number
// The Sequence column stores the sequence number of the Dependent Actions, so we are updating every
// row where the action in the Dependent column equals the current action. In the query view, we only
// check to insure that this column is zero or greater than zero (so we don't care too much about the value)
// Build the query: UPDATE `_Sequence` SET `Marker`=iSequence WHERE `Dependent`=szAction
wsprintf(szSQLUpdateQuery, TEXT("UPDATE `_Sequence` SET `Marker`=%d WHERE `Dependent`=?"), iSequence);
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLUpdateQuery, &hviewSeqUpdate), TEXT("_SequenceTableUpdateOpenView"));
CheckMsi(MsiViewExecute(hviewSeqUpdate, hrecSeqUpdateExecute), TEXT("_SequenceUpdateExectue"));

// Close the _Sequence table views so we can re-execute
CheckMsi(MsiViewClose(hviewSeqUpdate), TEXT("_SequenceUpdateViewClose"));
CheckMsi(MsiViewClose(hviewSeqQueryNull), TEXT("_SequenceQueryNullViewClose"));
CheckMsi(MsiViewClose(hviewSeqQueryNotNull), TEXT("_SequenceQueryNotNullViewClose"));
}

// Close the InstallSequence table view
CheckMsi(MsiViewClose(hviewInstallTable), TEXT("InstallSequenceTableViewClose"));

return fValid;
}


BOOL Validate(MSIHANDLE hDatabase)
/*-----------------------------------------------------------------------------------
Validate -- Routine to validate database. Prints out invalid data if any.
Arguments:
hDatabase -- handle to database
iValid -- integer for storing whether database is valid
Returns:
BOOL status -- TRUE (all valid), FALSE (invalid data found)
-------------------------------------------------------------------------------------*/
{
// _Tables (Table Catalog)
PMSIHANDLE hTableCatalogView;
PMSIHANDLE hTableCatalogRecord;
// Table To Validate
PMSIHANDLE hValidationView;
PMSIHANDLE hValidationRecord;
// Record for Primary Keys
PMSIHANDLE hKeyRecord;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLTableCatalog, &hTableCatalogView),TEXT("OpenTableCatalogView"));
CheckMsi(MsiViewExecute(hTableCatalogView, 0), TEXT("Execute Table Catalog View"));
TCHAR szSQL[256];
TCHAR szTableName[32];
TCHAR szColumnData[255];
TCHAR szColumnName[32];
DWORD cchTableName = sizeof(szTableName)/sizeof(TCHAR);
DWORD cchColumnName = sizeof(szColumnName)/sizeof(TCHAR);
DWORD cchColumnData = sizeof(szColumnData)/sizeof(TCHAR);

BOOL fDataValid = TRUE; // initially valid
DWORD cchTableBuf = cchTableName;
DWORD cchDataBuf = cchColumnData;
DWORD cchBuf = cchColumnName;
UINT uiRet = 0;
for (;;)
{

uiRet = MsiViewFetch(hTableCatalogView, &hTableCatalogRecord);
if (uiRet == ERROR_NO_MORE_ITEMS)
break;
CheckMsi(uiRet, TEXT("Fetch Table Catalog Record"));
if (!hTableCatalogRecord)
break;
cchTableBuf = cchTableName; // on return size of string written
CheckMsi(MsiRecordGetString(hTableCatalogRecord, 1, szTableName, &cchTableBuf), TEXT("Get Table Name From Fetched Record"));
MSICONDITION ice = MsiDatabaseIsTablePersistent(hDatabase, szTableName);
if (ice == MSICONDITION_FALSE)
continue;
CheckMsi(ice != MSICONDITION_TRUE, TEXT("IsTablePersistent"));
wsprintf(szSQL, TEXT("%s`%s`"), szSQLTable, szTableName);
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQL, &hValidationView),TEXT("OpenView"));
CheckMsi(MsiViewExecute(hValidationView, 0), TEXT("Execute View"));
for (;;)
{
uiRet = MsiViewFetch(hValidationView, &hValidationRecord);
if (uiRet == ERROR_NO_MORE_ITEMS)
break;
CheckMsi(uiRet, TEXT("Fetch record"));
if (!hValidationRecord)
break;
if (MsiViewModify(hValidationView, MSIMODIFY_VALIDATE, hValidationRecord) != ERROR_SUCCESS)
{
fDataValid = FALSE;
cchTableBuf = cchTableName;
cchDataBuf = cchColumnData;
cchBuf = cchColumnName;

MSIDBERROR eReturn;
while ((eReturn = MsiViewGetError(hValidationView, szColumnName, &cchBuf)) != MSIDBERROR_NOERROR)
{
if (eReturn == MSIDBERROR_FUNCTIONERROR || eReturn == MSIDBERROR_MOREDATA || eReturn == MSIDBERROR_INVALIDARG)
{
_tprintf(TEXT("\nFunction Error"));
break;
}

int iResId;
int iValue;
switch (eReturn)
{
case MSIDBERROR_NOERROR: iResId = IDS_NoError; break;
case MSIDBERROR_DUPLICATEKEY: iResId = IDS_DuplicateKey; break;
case MSIDBERROR_REQUIRED: iResId = IDS_Required; break;
case MSIDBERROR_BADLINK: iResId = IDS_BadLink; break;
case MSIDBERROR_OVERFLOW: iResId = IDS_Overflow; break;
case MSIDBERROR_UNDERFLOW: iResId = IDS_Underflow; break;
case MSIDBERROR_NOTINSET: iResId = IDS_NotInSet; break;
case MSIDBERROR_BADVERSION: iResId = IDS_BadVersion; break;
case MSIDBERROR_BADCASE: iResId = IDS_BadCase; break;
case MSIDBERROR_BADGUID: iResId = IDS_BadGuid; break;
case MSIDBERROR_BADWILDCARD: iResId = IDS_BadWildCard; break;
case MSIDBERROR_BADIDENTIFIER: iResId = IDS_BadIdentifier; break;
case MSIDBERROR_BADLANGUAGE: iResId = IDS_BadLanguage; break;
case MSIDBERROR_BADFILENAME: iResId = IDS_BadFileName; break;
case MSIDBERROR_BADPATH: iResId = IDS_BadPath; break;
case MSIDBERROR_BADCONDITION: iResId = IDS_BadCondition; break;
case MSIDBERROR_BADFORMATTED: iResId = IDS_BadFormatted; break;
case MSIDBERROR_BADTEMPLATE: iResId = IDS_BadTemplate; break;
case MSIDBERROR_BADDEFAULTDIR: iResId = IDS_BadDefaultDir; break;
case MSIDBERROR_BADREGPATH: iResId = IDS_BadRegPath; break;
case MSIDBERROR_BADCUSTOMSOURCE: iResId = IDS_BadCustomSource; break;
case MSIDBERROR_BADPROPERTY: iResId = IDS_BadProperty; break;
case MSIDBERROR_MISSINGDATA: iResId = IDS_MissingData; break;
case MSIDBERROR_BADCATEGORY: iResId = IDS_BadCategory; break;
case MSIDBERROR_BADKEYTABLE: iResId = IDS_BadKeyTable; break;
case MSIDBERROR_BADMAXMINVALUES: iResId = IDS_BadMaxMinValues; break;
case MSIDBERROR_BADCABINET: iResId = IDS_BadCabinet; break;
case MSIDBERROR_BADSHORTCUT: iResId = IDS_BadShortcut; break;
case MSIDBERROR_STRINGOVERFLOW: iResId = IDS_StringOverflow; break;
case MSIDBERROR_BADLOCALIZEATTRIB: iResId = IDS_BadLocalizeAttrib;break;
default: iResId = IDS_UndefinedError; break;
};

// Print table
_tprintf(TEXT("\n Error: %s\t"), szTableName);

// Get Row
CheckMsi(MsiDatabaseGetPrimaryKeys(hDatabase, szTableName, &hKeyRecord), TEXT("Get Primary Keys"));
unsigned int iNumFields = MsiRecordGetFieldCount(hKeyRecord);
if (MsiRecordGetString(hValidationRecord, 1, szColumnData, &cchDataBuf) != ERROR_SUCCESS)
{
iValue = MsiRecordGetInteger(hValidationRecord, 1);
_tprintf(TEXT("%d"), iValue);
}
else
_tprintf(TEXT("%s"), szColumnData);
cchDataBuf = cchColumnData;
for (int i = 2; i <= iNumFields; i++)
{
_tprintf(TEXT("."));
cchDataBuf = cchColumnData;
if (MsiRecordGetString(hValidationRecord, i, szColumnData, &cchDataBuf) != ERROR_SUCCESS)
{
iValue = MsiRecordGetInteger(hValidationRecord, 1);
_tprintf(TEXT("%d"), iValue);
}
else
_tprintf(TEXT("%s"), szColumnData);
}
// Print name of column and enum value
TCHAR szMsgBuf[80];
const TCHAR* szMessage = (TCHAR*)iResId;
const TCHAR** pszMsg;
pszMsg = &szMessage;
::LoadString(0, *(unsigned*)pszMsg, szMsgBuf, sizeof(szMsgBuf)/sizeof(TCHAR));
*pszMsg = szMsgBuf;
_tprintf(TEXT("\t%s\t%s"), szColumnName, szMsgBuf);
cchBuf = cchColumnName; // on return size of string written
cchDataBuf = cchColumnData;
}
cchBuf = cchColumnName; // on return size of string written
}
}
CheckMsi(MsiViewClose(hValidationView), TEXT("Close view"));
}
CheckMsi(MsiViewClose(hTableCatalogView), TEXT("Close Table Catalog View"));
return fDataValid;
}

void Display(LPCTSTR szMessage)
{
if (szMessage)
{
int cbOut = _tcsclen(szMessage);;
if (g_hStdOut)
{
#ifdef UNICODE
char rgchTemp[cchDisplayBuf];
if (GetFileType(g_hStdOut) == FILE_TYPE_CHAR)
{
WideCharToMultiByte(CP_ACP, 0, szMessage, cbOut, rgchTemp, sizeof(rgchTemp), 0, 0);
szMessage = (LPCWSTR)rgchTemp;
}
else
cbOut *= 2; // write Unicode if not console device
#endif
DWORD cbWritten;
WriteFile(g_hStdOut, szMessage, cbOut, &cbWritten, 0);
}
else
MessageBox(0, szMessage, GetCommandLine(), MB_OK);
}
}




#else // RC_INVOKED, end of source code, start of resources
// resource definition go here

STRINGTABLE DISCARDABLE
{
IDS_NoError, "No Error"
IDS_DuplicateKey, "Duplicate primary key"
IDS_Required, "Not a nullable column"
IDS_BadLink, "Not a valid foreign key"
IDS_Overflow, "Value exceeds MaxValue"
IDS_Underflow, "Value below MinValue"
IDS_NotInSet, "Value not a member of the set"
IDS_BadVersion, "Invalid version string"
IDS_BadCase, "Must be all upper or all lower case"
IDS_BadGuid, "Invalid GUID string"
IDS_BadWildCard, "Invalid wildcard filename or usage of wildcards"
IDS_BadIdentifier, "Invalid identifier"
IDS_BadLanguage, "Invalid Language Id"
IDS_BadFileName, "Invalid Filename"
IDS_BadPath, "Invalid full path"
IDS_BadCondition, "Bad conditional string"
IDS_BadFormatted, "Invalid format string"
IDS_BadTemplate, "Invalid template string"
IDS_BadDefaultDir, "Invalid DefaultDir string"
IDS_BadRegPath, "Invalid registry path"
IDS_BadCustomSource, "Bad CustomSource data"
IDS_BadProperty, "Bad property"
IDS_MissingData, "Missing data in _Validation table or old Database"
IDS_BadCabinet, "Bad cabinet syntax/name"
IDS_BadCategory, "_Validation table: Invalid category string"
IDS_BadKeyTable, "_Validation table: Data in KeyTable col is bad"
IDS_BadMaxMinValues, "_Validation table: value in MaxValue col < that in MinValue col"
IDS_BadShortcut, "Bad shortcut target"
IDS_StringOverflow, "String overflow: Length greater than that allowed by column definition"
IDS_MissingEntry, "Column is required by _Validation table"
IDS_UndefinedError, "Undefined Error"
IDS_BadLocalizeAttrib,"Primary Key columns cannot be set to be localized"
}

#endif // RC_INVOKED
#if 0
!endif // makefile terminator
#endif