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