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.
1682 lines
40 KiB
1682 lines
40 KiB
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
appdiff.c
|
|
|
|
Abstract:
|
|
|
|
Implements a stub tool that is designed to run with Win9x-side
|
|
upgrade code.
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 26-Feb-1998
|
|
|
|
Revision History:
|
|
|
|
<alias> <date> <comments>
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
|
|
#define S_FILES TEXT("Files")
|
|
#define S_REG TEXT("Reg")
|
|
#define S_INIFILES TEXT("IniFiles")
|
|
#define S_EXCLUDE TEXT("Exclude")
|
|
#define S_PATHS TEXT("Paths")
|
|
#define S_REGISTRY TEXT("Registry")
|
|
#define S_SUBSTITUTIONS TEXT("Substitutions")
|
|
#define S_SRC TEXT("Src")
|
|
#define S_DEST TEXT("Dest")
|
|
#define S_ADDED TEXT("Added")
|
|
#define S_CHANGED TEXT("Changed")
|
|
#define S_ZERO TEXT("0")
|
|
|
|
typedef struct {
|
|
BOOL SnapMode;
|
|
BOOL DiffMode;
|
|
BOOL CheckBits;
|
|
PCTSTR SnapFile;
|
|
PCTSTR AppFile;
|
|
PCTSTR Name;
|
|
PCTSTR OutputFile;
|
|
PCTSTR RegRoot;
|
|
PCTSTR FileSysRoot;
|
|
BOOL UseAppDiffInf;
|
|
BOOL NoRoots;
|
|
BOOL QuietMode;
|
|
} OPTIONS, *POPTIONS;
|
|
|
|
BOOL g_Quiet;
|
|
BOOL g_Thorough;
|
|
|
|
|
|
typedef struct {
|
|
DWORD dwFileAttributes;
|
|
FILETIME ftCreationTime;
|
|
FILETIME ftLastWriteTime;
|
|
DWORD nFileSizeHigh;
|
|
DWORD nFileSizeLow;
|
|
} FILEINFO, *PFILEINFO;
|
|
|
|
BOOL
|
|
DoSnapMode (
|
|
POPTIONS Options
|
|
);
|
|
|
|
BOOL
|
|
DoDiffMode (
|
|
POPTIONS Options
|
|
);
|
|
|
|
HANDLE g_hHeap;
|
|
HINSTANCE g_hInst;
|
|
|
|
BOOL
|
|
WINAPI
|
|
MigUtil_Entry (
|
|
HINSTANCE hInstance,
|
|
DWORD dwReason,
|
|
LPVOID lpReserved
|
|
);
|
|
|
|
BOOL
|
|
WINAPI
|
|
MemDb_Entry (
|
|
HINSTANCE hInstance,
|
|
DWORD dwReason,
|
|
LPVOID lpReserved
|
|
);
|
|
|
|
|
|
|
|
BOOL
|
|
Init (
|
|
VOID
|
|
)
|
|
{
|
|
HINSTANCE hInstance;
|
|
DWORD dwReason;
|
|
PVOID lpReserved;
|
|
|
|
//
|
|
// Simulate DllMain
|
|
//
|
|
|
|
hInstance = GetModuleHandle (NULL);
|
|
dwReason = DLL_PROCESS_ATTACH;
|
|
lpReserved = NULL;
|
|
|
|
g_hInst = hInstance;
|
|
g_hHeap = GetProcessHeap();
|
|
|
|
MigUtil_Entry (
|
|
hInstance,
|
|
dwReason,
|
|
lpReserved
|
|
);
|
|
|
|
MemDb_Entry (
|
|
hInstance,
|
|
dwReason,
|
|
lpReserved
|
|
);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
Terminate (
|
|
VOID
|
|
)
|
|
{
|
|
HINSTANCE hInstance;
|
|
DWORD dwReason;
|
|
PVOID lpReserved;
|
|
|
|
//
|
|
// Simulate DllMain
|
|
//
|
|
|
|
hInstance = GetModuleHandle (NULL);
|
|
dwReason = DLL_PROCESS_DETACH;
|
|
lpReserved = NULL;
|
|
|
|
MemDb_Entry (
|
|
hInstance,
|
|
dwReason,
|
|
lpReserved
|
|
);
|
|
|
|
MigUtil_Entry (
|
|
hInstance,
|
|
dwReason,
|
|
lpReserved
|
|
);
|
|
}
|
|
|
|
|
|
VOID
|
|
HelpAndExit (
|
|
VOID
|
|
)
|
|
{
|
|
printf ("Command line syntax:\n\n"
|
|
"appdiff -s[:snapfile] [-r:<regroot>] [-f:<fileroot>]\n"
|
|
"appdiff -d[:snapfile] [-a:appfilelist] [-n:name] [-o:outfile]\n"
|
|
"appdiff -s[:snapfile] -d [-n:name] [-o:outfile] [-r:<regroot>]\n"
|
|
" [-f:<fileroot>]\n"
|
|
"\n"
|
|
"-s Specifies snapshot mode, where snapfile is the name of\n"
|
|
" the memdb output file, and is snap.dat by default.\n"
|
|
"\n"
|
|
"-d Specifies diff mode, where snapfile is the name of a\n"
|
|
" previously generated snapshot file, and is snap.dat by\n"
|
|
" default.\n"
|
|
"\n"
|
|
"-a Specifies the application file list, as generated by\n"
|
|
" migfiles.exe.\n"
|
|
"\n"
|
|
"-n Specifies the application section name, and the default\n"
|
|
" is Application.\n"
|
|
"\n"
|
|
"-o Specifies the name of the output INF fragment, and the\n"
|
|
" default is output.inf\n"
|
|
"\n"
|
|
"-r Specifies a registry root to compare. If specified,\n"
|
|
" only the registry is scanned, unless -f is also specified.\n"
|
|
"\n"
|
|
"-f Specifies a file system root to compare. If specified,\n"
|
|
" only the file system is scanned, unless -r is also\n"
|
|
" specified.\n"
|
|
"\n"
|
|
"Additional Options:"
|
|
"\n"
|
|
"-q Quiet mode -- disables stderr output.\n"
|
|
"-u Use appdiff.inf and output.inf (for generation of uninstall\n"
|
|
" sections)\n"
|
|
"-t Thorough checks (computes checksums for all data)\n"
|
|
"\n"
|
|
"APPDIFF.INF specifies the registry and file system roots to scan on\n"
|
|
"a per-app basis, and is used to generate uninstall sections for\n"
|
|
"migdb.inf. See \\\\jimschm-dev\\team\\tools\\appdiff.inf for info.\n"
|
|
"\n"
|
|
"OUTPUT.INF is generated by this tool, and is designed to be cut &\n"
|
|
"pasted into migdb.inf.\n"
|
|
);
|
|
|
|
exit(0);
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
pParseCommandLine (
|
|
IN INT ArgCount,
|
|
IN PTSTR ArgArray[],
|
|
OUT POPTIONS Options
|
|
)
|
|
{
|
|
INT i;
|
|
|
|
ZeroMemory (Options, sizeof (OPTIONS));
|
|
|
|
Options->NoRoots = TRUE;
|
|
|
|
for (i = 0 ; i < ArgCount ; i++) {
|
|
|
|
if (ArgArray[i][0] == TEXT('-') || ArgArray[i][0] == TEXT('/')) {
|
|
|
|
switch (_totlower (ArgArray[i][1])) {
|
|
|
|
case TEXT('s'):
|
|
Options->SnapMode = TRUE;
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
if (Options->SnapFile) {
|
|
return FALSE;
|
|
}
|
|
|
|
Options->SnapFile = &ArgArray[i][3];
|
|
|
|
if (Options->SnapFile[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('t'):
|
|
if (g_Thorough) {
|
|
return FALSE;
|
|
}
|
|
|
|
Options->CheckBits = TRUE;
|
|
g_Thorough = TRUE;
|
|
|
|
break;
|
|
|
|
case TEXT('d'):
|
|
Options->DiffMode = TRUE;
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
if (Options->SnapFile) {
|
|
return FALSE;
|
|
}
|
|
|
|
Options->SnapFile = &ArgArray[i][3];
|
|
|
|
if (Options->SnapFile[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('r'):
|
|
Options->NoRoots = FALSE;
|
|
|
|
if (Options->RegRoot) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
Options->RegRoot = &ArgArray[i][3];
|
|
} else if (i + 1 < ArgCount) {
|
|
i++;
|
|
Options->RegRoot = ArgArray[i];
|
|
} else {
|
|
Options->RegRoot = &ArgArray[i][2];
|
|
}
|
|
|
|
if (Options->RegRoot[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('f'):
|
|
Options->NoRoots = FALSE;
|
|
|
|
if (Options->FileSysRoot) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
Options->FileSysRoot = &ArgArray[i][3];
|
|
} else if (i + 1 < ArgCount) {
|
|
i++;
|
|
Options->FileSysRoot = ArgArray[i];
|
|
} else {
|
|
Options->FileSysRoot = &ArgArray[i][2];
|
|
}
|
|
|
|
if (Options->FileSysRoot[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('q'):
|
|
if (g_Quiet) {
|
|
return FALSE;
|
|
}
|
|
|
|
Options->QuietMode = TRUE;
|
|
g_Quiet = TRUE;
|
|
break;
|
|
|
|
case TEXT('u'):
|
|
if (Options->UseAppDiffInf) {
|
|
return FALSE;
|
|
}
|
|
|
|
Options->UseAppDiffInf = TRUE;
|
|
break;
|
|
|
|
case TEXT('a'):
|
|
if (Options->AppFile) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
Options->AppFile = &ArgArray[i][3];
|
|
} else if (i + 1 < ArgCount) {
|
|
i++;
|
|
Options->AppFile = ArgArray[i];
|
|
} else {
|
|
Options->AppFile = &ArgArray[i][2];
|
|
}
|
|
|
|
if (Options->AppFile[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('n'):
|
|
if (Options->Name) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
Options->Name = &ArgArray[i][3];
|
|
} else if (i + 1 < ArgCount) {
|
|
i++;
|
|
Options->Name = ArgArray[i];
|
|
} else {
|
|
Options->Name = &ArgArray[i][2];
|
|
}
|
|
|
|
if (Options->Name[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEXT('o'):
|
|
if (Options->OutputFile) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ArgArray[i][2] == TEXT(':')) {
|
|
Options->OutputFile = &ArgArray[i][3];
|
|
} else if (i + 1 < ArgCount) {
|
|
i++;
|
|
Options->OutputFile = ArgArray[i];
|
|
} else {
|
|
Options->OutputFile = &ArgArray[i][2];
|
|
}
|
|
|
|
if (Options->OutputFile[0] == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!Options->SnapMode && !Options->DiffMode) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!Options->SnapFile) {
|
|
Options->SnapFile = TEXT("snap.dat");
|
|
}
|
|
|
|
if (!Options->OutputFile && Options->UseAppDiffInf) {
|
|
Options->OutputFile = TEXT("output.inf");
|
|
}
|
|
|
|
if (!Options->Name) {
|
|
Options->Name = TEXT("Application");
|
|
}
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT("Snap file: %s\n"), Options->SnapFile);
|
|
|
|
if (Options->OutputFile) {
|
|
_ftprintf (stderr, TEXT("Output file: %s\n"), Options->OutputFile);
|
|
}
|
|
|
|
_ftprintf (stderr, TEXT("Thorough checks: %s\n"), Options->CheckBits ? "ENABLED" : "DISABLED");
|
|
|
|
_ftprintf (stderr, TEXT("Application Name: %s\n\n"), Options->Name);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
INT
|
|
__cdecl
|
|
_tmain (
|
|
INT argc,
|
|
TCHAR *argv[]
|
|
)
|
|
{
|
|
OPTIONS Options;
|
|
|
|
if (!pParseCommandLine (argc - 1, &argv[1], &Options)) {
|
|
HelpAndExit();
|
|
}
|
|
|
|
if (!Init()) {
|
|
printf ("Unable to initialize!\n");
|
|
return 255;
|
|
}
|
|
|
|
//
|
|
// Snap Mode: Gather the directory, registry and INI files
|
|
//
|
|
|
|
if (Options.SnapMode) {
|
|
DoSnapMode (&Options);
|
|
}
|
|
|
|
//
|
|
// Diff Mode: Gather another snapshot, then compare against
|
|
// original
|
|
//
|
|
|
|
if (Options.DiffMode) {
|
|
if (Options.SnapMode) {
|
|
_ftprintf (stderr, TEXT("Do your thing, then hit Enter.\n"));
|
|
getchar();
|
|
_ftprintf (stderr, TEXT("\n"));
|
|
}
|
|
|
|
DoDiffMode (&Options);
|
|
}
|
|
|
|
Terminate();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pCompareData (
|
|
IN PCBYTE Src,
|
|
IN PCBYTE Dest,
|
|
IN UINT Size
|
|
)
|
|
{
|
|
PCWSTR p, q;
|
|
|
|
if (Size >= sizeof (WCHAR)) {
|
|
|
|
p = (PCWSTR) (Src + Size - sizeof (WCHAR));
|
|
q = (PCWSTR) (Dest + Size - sizeof (WCHAR));
|
|
|
|
if (*p == 0 && *q == 0) {
|
|
if (StringIMatchW ((PCWSTR) Src, (PCWSTR) Dest)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return memcmp (Src, Dest, Size) == 0;
|
|
}
|
|
|
|
|
|
VOID
|
|
pSetMemDbKey (
|
|
IN BOOL DiffMode,
|
|
IN PCTSTR Group,
|
|
IN PCTSTR Key,
|
|
IN PBYTE Data,
|
|
IN DWORD DataSize
|
|
)
|
|
{
|
|
PCBYTE OrgData;
|
|
DWORD OrgSize;
|
|
TCHAR Node[MEMDB_MAX];
|
|
|
|
wsprintf (Node, TEXT("%s\\%s"), S_EXCLUDE, Key);
|
|
|
|
if (MemDbGetValue (Node, NULL)) {
|
|
return;
|
|
}
|
|
|
|
if (!DiffMode) {
|
|
|
|
MemDbSetBinaryValueEx (
|
|
S_ZERO,
|
|
Group,
|
|
Key,
|
|
Data,
|
|
DataSize,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
else {
|
|
//
|
|
// Compare against original data
|
|
//
|
|
|
|
wsprintf (Node, TEXT("0\\%s\\%s"), Group, Key);
|
|
OrgData = MemDbGetBinaryValue (Node, &OrgSize);
|
|
|
|
if (!OrgData) {
|
|
//
|
|
// Data has been added
|
|
//
|
|
|
|
wsprintf (Node, TEXT("%s\\%s\\%s"), S_ADDED, Group, Key);
|
|
MemDbSetValue (Node, 0);
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
// Delete memdb key, so remaining items will provide list of data
|
|
// that was deleted.
|
|
//
|
|
|
|
if (OrgSize != DataSize || !pCompareData (OrgData, Data, DataSize)) {
|
|
//
|
|
// Data has changed
|
|
//
|
|
|
|
MemDbDeleteValue (Node);
|
|
|
|
wsprintf (Node, TEXT("%s\\%s\\%s"), S_CHANGED, Group, Key);
|
|
MemDbSetValue (Node, 0);
|
|
} else {
|
|
//
|
|
// Data has not changed
|
|
//
|
|
|
|
MemDbDeleteValue (Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
pConvertWin32FindData (
|
|
IN PWIN32_FIND_DATA Data,
|
|
OUT PFILEINFO Info
|
|
)
|
|
{
|
|
Info->dwFileAttributes = Data->dwFileAttributes;
|
|
Info->ftCreationTime = Data->ftCreationTime;
|
|
Info->ftLastWriteTime = Data->ftLastWriteTime;
|
|
Info->nFileSizeHigh = Data->nFileSizeHigh;
|
|
Info->nFileSizeLow = Data->nFileSizeLow;
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
pSetRegDataAndFreePtrs (
|
|
BOOL DiffMode,
|
|
PBYTE Data, OPTIONAL
|
|
PBYTE Data2, OPTIONAL
|
|
DWORD Size,
|
|
PCTSTR Key,
|
|
PCTSTR Value OPTIONAL
|
|
)
|
|
{
|
|
PCTSTR Node;
|
|
|
|
if (Data2) {
|
|
Node = CreateEncodedRegistryString (Key, Value);
|
|
|
|
pSetMemDbKey (
|
|
DiffMode,
|
|
S_REG,
|
|
Node,
|
|
Data2,
|
|
Size
|
|
);
|
|
|
|
FreeEncodedRegistryString (Node);
|
|
|
|
if (Data) {
|
|
MemFree (g_hHeap, 0, Data);
|
|
}
|
|
|
|
MemFree (g_hHeap, 0, Data2);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
pRegSnap (
|
|
BOOL DiffMode,
|
|
PCTSTR Root
|
|
)
|
|
|
|
{
|
|
REGTREE_ENUM Reg;
|
|
REGVALUE_ENUM RegVal;
|
|
PBYTE Data;
|
|
PBYTE Data2;
|
|
DWORD Size = 0;
|
|
TCHAR SkipTree[MEMDB_MAX];
|
|
TCHAR TempNode[MEMDB_MAX];
|
|
UINT SkipTreeBytes = 0;
|
|
|
|
SkipTree[0] = 0;
|
|
|
|
wsprintf (TempNode, TEXT("%s\\%s"), S_EXCLUDE, Root);
|
|
if (MemDbGetValue (TempNode, NULL)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT("Taking snapshot of %s\n"), Root);
|
|
}
|
|
|
|
if (EnumFirstRegKeyInTree (&Reg, Root)) {
|
|
do {
|
|
//
|
|
// Key/key tree exclude processing
|
|
//
|
|
|
|
if (SkipTree[0]) {
|
|
if (StringIMatchByteCount (SkipTree, Reg.FullKeyName, SkipTreeBytes)) {
|
|
continue;
|
|
}
|
|
|
|
SkipTree[0] = 0;
|
|
}
|
|
|
|
wsprintf (TempNode, TEXT("%s\\%s"), S_EXCLUDE, Reg.FullKeyName);
|
|
if (MemDbGetValue (TempNode, NULL)) {
|
|
StringCopy (SkipTree, Reg.FullKeyName);
|
|
SkipTreeBytes = ByteCount (SkipTree);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Non-excluded key
|
|
//
|
|
|
|
Data = NULL;
|
|
|
|
if (EnumFirstRegValue (&RegVal, Reg.CurrentKey->KeyHandle)) {
|
|
do {
|
|
Data = GetRegValueData (RegVal.KeyHandle, RegVal.ValueName);
|
|
Data2 = NULL;
|
|
|
|
if (Data) {
|
|
Size = RegVal.DataSize + sizeof (DWORD);
|
|
Data2 = MemAlloc (g_hHeap, 0, Size);
|
|
MYASSERT (Data2);
|
|
|
|
CopyMemory ((PDWORD) Data2 + 1, Data, RegVal.DataSize);
|
|
*((PDWORD) Data2) = RegVal.Type;
|
|
}
|
|
|
|
pSetRegDataAndFreePtrs (DiffMode, Data, Data2, Size, Reg.FullKeyName, RegVal.ValueName);
|
|
|
|
} while (EnumNextRegValue (&RegVal));
|
|
|
|
} else {
|
|
Size = sizeof (DWORD);
|
|
Data2 = MemAlloc (g_hHeap, 0, Size);
|
|
MYASSERT (Data2);
|
|
*((PDWORD) Data2) = 0xffffffff;
|
|
|
|
pSetRegDataAndFreePtrs (DiffMode, Data, Data2, Size, Reg.FullKeyName, RegVal.ValueName);
|
|
}
|
|
|
|
} while (EnumNextRegKeyInTree (&Reg));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pComputeChecksum (
|
|
PCTSTR FullPath
|
|
)
|
|
{
|
|
HANDLE File;
|
|
HANDLE Map;
|
|
PBYTE Data;
|
|
UINT Size;
|
|
UINT u;
|
|
DWORD Checksum = 0;
|
|
|
|
Data = MapFileIntoMemory (FullPath, &File, &Map);
|
|
if (!Data) {
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
Size = GetFileSize (File, NULL);
|
|
|
|
for (u = 0 ; u < Size ; u++) {
|
|
Checksum = _rotl (Checksum, 3);
|
|
Checksum ^= Data[u];
|
|
}
|
|
|
|
UnmapFile (Data, Map, File);
|
|
|
|
return Checksum;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pDirAndIniSnap (
|
|
BOOL DiffMode,
|
|
PCTSTR Root
|
|
)
|
|
{
|
|
TREE_ENUM Dir;
|
|
PCTSTR p, q, r;
|
|
TCHAR SectionNames[32768];
|
|
TCHAR KeyNames[32768];
|
|
TCHAR KeyValue[4096];
|
|
TCHAR Node[MEMDB_MAX];
|
|
TCHAR ExcludeNode[MEMDB_MAX];
|
|
UINT Count;
|
|
FILEINFO fi;
|
|
TCHAR SkipTree[MEMDB_MAX];
|
|
UINT SkipTreeBytes = 0;
|
|
DWORD Checksum;
|
|
|
|
SkipTree[0] = 0;
|
|
|
|
wsprintf (ExcludeNode, TEXT("%s\\%s"), S_EXCLUDE, Root);
|
|
if (MemDbGetValue (ExcludeNode, NULL)) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Take a snapshot of all dirs in drive specified by Root
|
|
//
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT("Taking snapshot of %s\n"), Root);
|
|
}
|
|
|
|
if (EnumFirstFileInTree (&Dir, Root, NULL, TRUE)) {
|
|
do {
|
|
//
|
|
// Exclude processing
|
|
//
|
|
|
|
if (SkipTree[0]) {
|
|
if (StringIMatchByteCount (SkipTree, Dir.FullPath, SkipTreeBytes)) {
|
|
continue;
|
|
}
|
|
|
|
SkipTree[0] = 0;
|
|
}
|
|
|
|
if (Dir.Directory) {
|
|
wsprintf (ExcludeNode, TEXT("%s\\%s"), S_EXCLUDE, Dir.FullPath);
|
|
if (MemDbGetValue (ExcludeNode, NULL)) {
|
|
StringCopy (SkipTree, Dir.FullPath);
|
|
AppendWack (SkipTree);
|
|
SkipTreeBytes = ByteCount (SkipTree);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Non-excluded file
|
|
//
|
|
|
|
if (g_Thorough) {
|
|
Checksum = pComputeChecksum (Dir.FullPath);
|
|
|
|
pSetMemDbKey (
|
|
DiffMode,
|
|
S_FILES,
|
|
Dir.FullPath,
|
|
(PBYTE) &Checksum,
|
|
sizeof (Checksum)
|
|
);
|
|
|
|
} else {
|
|
pConvertWin32FindData (Dir.FindData, &fi);
|
|
|
|
pSetMemDbKey (
|
|
DiffMode,
|
|
S_FILES,
|
|
Dir.FullPath,
|
|
(PBYTE) &fi,
|
|
sizeof (FILEINFO)
|
|
);
|
|
}
|
|
|
|
p = _tcsrchr (Dir.Name, TEXT('.'));
|
|
if (p) {
|
|
p = _tcsinc (p);
|
|
if (StringIMatch (p, TEXT("INI"))) {
|
|
//
|
|
// Found INI file, take a snapshot of it
|
|
//
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT(" Taking snapshot of %s\n"), Dir.FullPath);
|
|
}
|
|
|
|
Count = GetPrivateProfileString (NULL, NULL, TEXT("\0"), SectionNames, 32768, Dir.FullPath);
|
|
SectionNames[Count] = 0;
|
|
SectionNames[Count + 1] = 0;
|
|
|
|
p = SectionNames;
|
|
while (*p) {
|
|
//
|
|
// Filter out dup sections
|
|
//
|
|
|
|
r = SectionNames;
|
|
while (r < p) {
|
|
if (StringIMatch (p, r)) {
|
|
break;
|
|
}
|
|
|
|
r = GetEndOfString (r) + 1;
|
|
}
|
|
|
|
if (r < p) {
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT(" ***Duplicate section ignored: [%s]\n"), p);
|
|
}
|
|
|
|
p = GetEndOfString (p) + 1;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Process each key in the section
|
|
//
|
|
|
|
Count = GetPrivateProfileString (
|
|
p,
|
|
NULL,
|
|
TEXT("\0"),
|
|
KeyNames,
|
|
32768,
|
|
Dir.FullPath
|
|
);
|
|
KeyNames[Count] = 0;
|
|
KeyNames[Count + 1] = 0;
|
|
|
|
q = KeyNames;
|
|
|
|
while (*q) {
|
|
//
|
|
// Ignore duplicate value names
|
|
//
|
|
|
|
r = KeyNames;
|
|
while (r < q) {
|
|
if (StringIMatch (q, r)) {
|
|
break;
|
|
}
|
|
|
|
r = GetEndOfString (r) + 1;
|
|
}
|
|
|
|
if (r < q) {
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT(" ***Duplicate key ignored: [%s] %s\n"), p, q);
|
|
}
|
|
|
|
q = GetEndOfString (q) + 1;
|
|
continue;
|
|
}
|
|
|
|
GetPrivateProfileString (
|
|
p,
|
|
q,
|
|
TEXT(""),
|
|
KeyValue,
|
|
4096,
|
|
Dir.FullPath
|
|
);
|
|
|
|
wsprintf (Node, TEXT("%s\\[%s]\\%s"), Dir.FullPath, p, q);
|
|
pSetMemDbKey (
|
|
DiffMode,
|
|
S_INIFILES,
|
|
Node,
|
|
(PBYTE) KeyValue,
|
|
ByteCount (KeyValue) + sizeof (TCHAR)
|
|
);
|
|
|
|
q = GetEndOfString (q) + 1;
|
|
}
|
|
|
|
p = GetEndOfString (p) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (EnumNextFileInTree (&Dir));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pCreateSubst (
|
|
IN PCTSTR Src,
|
|
IN PCTSTR Dest
|
|
)
|
|
{
|
|
DWORD Offset;
|
|
|
|
MemDbSetValueEx (S_SUBSTITUTIONS, S_DEST, Dest, NULL, 0, &Offset);
|
|
MemDbSetValueEx (S_SUBSTITUTIONS, S_SRC, Src, NULL, Offset, NULL);
|
|
}
|
|
|
|
BOOL
|
|
pTakeSnapshot (
|
|
POPTIONS Options,
|
|
BOOL DiffMode
|
|
)
|
|
{
|
|
HINF Inf;
|
|
INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
|
|
TCHAR Path[MAX_TCHAR_PATH];
|
|
PTSTR p, q;
|
|
TCHAR Section[256];
|
|
UINT Dirs = 0;
|
|
UINT RegRoots = 0;
|
|
TCHAR WinDir[MAX_TCHAR_PATH];
|
|
TCHAR SystemDir[MAX_TCHAR_PATH];
|
|
TCHAR System32Dir[MAX_TCHAR_PATH];
|
|
TCHAR SystemDrive[8];
|
|
TCHAR ProgramFilesDir[MAX_TCHAR_PATH];
|
|
|
|
GetWindowsDirectory (WinDir, MAX_TCHAR_PATH);
|
|
StringCopy (SystemDir, WinDir);
|
|
StringCopy (AppendWack (SystemDir), TEXT("system"));
|
|
StringCopy (System32Dir, SystemDir);
|
|
StringCat (System32Dir, TEXT("32"));
|
|
SystemDrive[0] = SystemDir[0];
|
|
SystemDrive[1] = TEXT(':');
|
|
SystemDrive[2] = 0;
|
|
StringCopy (ProgramFilesDir, SystemDrive);
|
|
StringCopy (AppendWack (ProgramFilesDir), TEXT("Program Files"));
|
|
|
|
pCreateSubst (WinDir, TEXT("%%WINDIR%%"));
|
|
pCreateSubst (SystemDir, TEXT("%%SYSTEMDIR%%"));
|
|
pCreateSubst (System32Dir, TEXT("%%SYSTEM32DIR%%"));
|
|
pCreateSubst (SystemDrive, TEXT("%%SYSTEMDRIVE%%"));
|
|
pCreateSubst (ProgramFilesDir, TEXT("%%PROGRAMFILES%%"));
|
|
|
|
|
|
if (Options->UseAppDiffInf) {
|
|
GetModuleFileName (NULL, Path, MAX_TCHAR_PATH);
|
|
p = _tcsrchr (Path, TEXT('\\'));
|
|
MYASSERT (p);
|
|
StringCopy (p + 1, TEXT("appdiff.inf"));
|
|
|
|
Inf = InfOpenInfFile (Path);
|
|
} else {
|
|
Inf = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (Inf == INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Take snapshot of file system and INI files
|
|
//
|
|
|
|
if (Options->FileSysRoot) {
|
|
pDirAndIniSnap (DiffMode, Options->FileSysRoot);
|
|
} else if (Options->NoRoots) {
|
|
pDirAndIniSnap (DiffMode, TEXT("C:\\"));
|
|
}
|
|
|
|
//
|
|
// Take snapshot of registry
|
|
//
|
|
|
|
if (Options->RegRoot) {
|
|
pRegSnap (DiffMode, Options->RegRoot);
|
|
} else if (Options->NoRoots) {
|
|
pRegSnap (DiffMode, TEXT("HKLM"));
|
|
pRegSnap (DiffMode, TEXT("HKU"));
|
|
}
|
|
}
|
|
|
|
else {
|
|
//
|
|
// Fill in the [Exclude] section
|
|
//
|
|
|
|
if (InfFindFirstLine (Inf, S_EXCLUDE, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
|
|
if (Options->Name) {
|
|
wsprintf (Section, TEXT("%s.%s"), Options->Name, S_EXCLUDE);
|
|
|
|
if (InfFindFirstLine (Inf, Section, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
|
|
//
|
|
// Fill in the [Substitutions] section
|
|
//
|
|
|
|
if (InfFindFirstLine (Inf, S_SUBSTITUTIONS, NULL, &is)) {
|
|
do {
|
|
p = InfGetStringField (&is, 0);
|
|
q = InfGetStringField (&is, 1);
|
|
|
|
pCreateSubst (p, q);
|
|
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
|
|
if (Options->Name) {
|
|
wsprintf (Section, TEXT("%s.%s"), Options->Name, S_EXCLUDE);
|
|
|
|
if (InfFindFirstLine (Inf, Section, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
|
|
//
|
|
// Enumerate the [Paths] section, use c:\ by default
|
|
//
|
|
|
|
if (InfFindFirstLine (Inf, S_PATHS, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
pDirAndIniSnap (DiffMode, p);
|
|
Dirs++;
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
|
|
if (Options->Name) {
|
|
wsprintf (Section, TEXT("%s.%s"), Options->Name, S_PATHS);
|
|
|
|
if (InfFindFirstLine (Inf, Section, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
pDirAndIniSnap (DiffMode, p);
|
|
Dirs++;
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
|
|
if (!Dirs) {
|
|
pDirAndIniSnap (DiffMode, TEXT("C:\\"));
|
|
}
|
|
|
|
//
|
|
// Enumerate the [Registry] section, use HKLM and HKU by default
|
|
//
|
|
|
|
if (InfFindFirstLine (Inf, S_REGISTRY, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
pRegSnap (DiffMode, p);
|
|
RegRoots++;
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
|
|
if (Options->Name) {
|
|
wsprintf (Section, TEXT("%s.%s"), Options->Name, S_REGISTRY);
|
|
|
|
if (InfFindFirstLine (Inf, Section, NULL, &is)) {
|
|
do {
|
|
p = InfGetLineText (&is);
|
|
pRegSnap (DiffMode, p);
|
|
RegRoots++;
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfResetInfStruct (&is);
|
|
}
|
|
}
|
|
|
|
if (!RegRoots) {
|
|
pRegSnap (DiffMode, TEXT("HKLM"));
|
|
pRegSnap (DiffMode, TEXT("HKU"));
|
|
}
|
|
|
|
InfCloseInfFile (Inf);
|
|
}
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
pPerformSubstitution (
|
|
PGROWLIST EnvVars,
|
|
PCTSTR OrgStr
|
|
)
|
|
{
|
|
PCTSTR PathStr;
|
|
PCTSTR NewPathString;
|
|
UINT Count;
|
|
UINT u;
|
|
PCTSTR Src;
|
|
PCTSTR Dest;
|
|
|
|
Count = GrowListGetSize (EnvVars);
|
|
|
|
PathStr = DuplicatePathString (OrgStr, 0);
|
|
MYASSERT (PathStr);
|
|
|
|
for (u = 0 ; u < Count ; u += 2) {
|
|
Src = GrowListGetString (EnvVars, u);
|
|
Dest = GrowListGetString (EnvVars, u + 1);
|
|
|
|
NewPathString = StringSearchAndReplace (PathStr, Src, Dest);
|
|
if (NewPathString) {
|
|
FreePathString (PathStr);
|
|
PathStr = NewPathString;
|
|
}
|
|
}
|
|
|
|
return PathStr;
|
|
}
|
|
|
|
|
|
VOID
|
|
pCreateEnvVars (
|
|
PGROWLIST EnvVars
|
|
)
|
|
{
|
|
MEMDB_ENUM e;
|
|
TCHAR Dest[MEMDB_MAX];
|
|
UINT Count;
|
|
UINT u;
|
|
UINT Len;
|
|
|
|
//
|
|
// Enumerate source strings
|
|
//
|
|
|
|
Count = 0;
|
|
|
|
if (MemDbGetValueEx (&e, S_SUBSTITUTIONS, S_SRC, NULL)) {
|
|
do {
|
|
MemDbBuildKeyFromOffset (e.dwValue, Dest, 2, NULL);
|
|
|
|
Len = ByteCount (e.szName);
|
|
|
|
for (u = 0 ; u < Count ; u += 2) {
|
|
if (ByteCount (GrowListGetString (EnvVars, u)) < Len) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (u < Count) {
|
|
GrowListInsertString (EnvVars, u, e.szName);
|
|
GrowListInsertString (EnvVars, u + 1, Dest);
|
|
} else {
|
|
GrowListAppendString (EnvVars, e.szName);
|
|
GrowListAppendString (EnvVars, Dest);
|
|
}
|
|
|
|
Count += 2;
|
|
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pDecodeRegStr (
|
|
IN PCTSTR RegStr,
|
|
OUT PTSTR Key,
|
|
OUT PCTSTR *ValuePtr
|
|
)
|
|
{
|
|
PTSTR p;
|
|
PCTSTR Val = NULL;
|
|
|
|
StringCopy (Key, RegStr);
|
|
|
|
p = _tcschr (Key, TEXT('['));
|
|
if (p) {
|
|
Val = _tcsinc (p);
|
|
p = _tcsdec2 (Key, p);
|
|
while (p) {
|
|
if (_tcsnextc (p) != TEXT(' ')) {
|
|
break;
|
|
}
|
|
|
|
p = _tcsdec2 (Key, p);
|
|
}
|
|
|
|
*p = 0;
|
|
}
|
|
|
|
*ValuePtr = Val;
|
|
}
|
|
|
|
BOOL
|
|
pAreAllValuesInMemDb (
|
|
IN PCTSTR RegStr,
|
|
IN BOOL Encoded,
|
|
IN HKEY KeyHandle OPTIONAL
|
|
)
|
|
{
|
|
BOOL WeOpen = FALSE;
|
|
REGVALUE_ENUM e;
|
|
TCHAR Key[MAX_REGISTRY_KEY];
|
|
PCTSTR Value;
|
|
BOOL b = TRUE;
|
|
|
|
//
|
|
// If encoded, decode first.
|
|
//
|
|
|
|
if (Encoded) {
|
|
pDecodeRegStr (RegStr, Key, &Value);
|
|
} else {
|
|
StringCopy (Key, RegStr);
|
|
Value = NULL;
|
|
}
|
|
|
|
//
|
|
// If key not open, open now
|
|
//
|
|
|
|
if (!KeyHandle) {
|
|
KeyHandle = OpenRegKeyStr (Key);
|
|
WeOpen = TRUE;
|
|
|
|
if (!KeyHandle) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if there is at least one value remaining, fail
|
|
//
|
|
|
|
b = !EnumFirstRegValue (&e, KeyHandle);
|
|
|
|
if (WeOpen) {
|
|
CloseRegKey (KeyHandle);
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsEntireSubKeyGone (
|
|
IN PCTSTR RegStr,
|
|
IN BOOL Encoded
|
|
)
|
|
{
|
|
TCHAR Key[MAX_REGISTRY_KEY];
|
|
PCTSTR Value;
|
|
HKEY KeyHandle;
|
|
|
|
//
|
|
// If encoded, decode now
|
|
//
|
|
|
|
if (Encoded) {
|
|
pDecodeRegStr (RegStr, Key, &Value);
|
|
} else {
|
|
StringCopy (Key, RegStr);
|
|
Value = NULL;
|
|
}
|
|
|
|
//
|
|
// Open key
|
|
//
|
|
|
|
KeyHandle = OpenRegKeyStr (Key);
|
|
if (!KeyHandle) {
|
|
return TRUE;
|
|
}
|
|
|
|
CloseRegKey (KeyHandle);
|
|
return FALSE;
|
|
}
|
|
|
|
VOID
|
|
pAppendThingsToDelete (
|
|
POPTIONS Options,
|
|
HANDLE File
|
|
)
|
|
{
|
|
MEMDB_ENUM e;
|
|
PCTSTR p;
|
|
GROWLIST EnvVars = GROWLIST_INIT;
|
|
TCHAR SkipKey[MEMDB_MAX];
|
|
UINT SkipKeyBytes = 0;
|
|
BOOL RegFlag;
|
|
BOOL AppendStar;
|
|
BOOL RemoveVal;
|
|
PCTSTR OutLine;
|
|
TCHAR KeyBuf[MAX_REGISTRY_KEY];
|
|
PCTSTR DontCare;
|
|
|
|
SkipKey[0] = 0;
|
|
|
|
//
|
|
// Generate substitution mapping
|
|
//
|
|
|
|
pCreateEnvVars (&EnvVars);
|
|
|
|
//
|
|
// Write section name
|
|
//
|
|
|
|
if (!Options->Name) {
|
|
return;
|
|
}
|
|
|
|
WriteFileString (File, TEXT("["));
|
|
WriteFileString (File, Options->Name);
|
|
WriteFileString (File, TEXT("]\r\n"));
|
|
|
|
//
|
|
// Write all the things in the deleted key
|
|
//
|
|
|
|
if (MemDbGetValueEx (&e, S_ZERO, NULL, NULL)) {
|
|
do {
|
|
p = _tcschr (e.szName, TEXT('\\'));
|
|
MYASSERT (p);
|
|
|
|
if (StringIMatchAB (S_REG, e.szName, p)) {
|
|
RegFlag = TRUE;
|
|
} else {
|
|
RegFlag = FALSE;
|
|
}
|
|
|
|
//
|
|
// Skip if this node is a subkey of a deleted key
|
|
//
|
|
|
|
p = _tcsinc (p);
|
|
|
|
if (SkipKey[0]) {
|
|
if (StringIMatchByteCount (SkipKey, p, SkipKeyBytes)) {
|
|
continue;
|
|
}
|
|
|
|
SkipKey[0] = 0;
|
|
}
|
|
|
|
RemoveVal = FALSE;
|
|
AppendStar = FALSE;
|
|
|
|
OutLine = p;
|
|
|
|
if (RegFlag) {
|
|
//
|
|
// If this is a registry key, and everything in
|
|
// the registry key has been deleted, then
|
|
// just write the one key with a star after it.
|
|
//
|
|
|
|
if (pIsEntireSubKeyGone (p, TRUE)) {
|
|
RemoveVal = TRUE;
|
|
AppendStar = TRUE;
|
|
}
|
|
|
|
//
|
|
// If it's a registry key, and all the subvalues
|
|
// are deleted, then just write the one key, but
|
|
// without a star.
|
|
//
|
|
|
|
else if (pAreAllValuesInMemDb (p, TRUE, NULL)) {
|
|
RemoveVal = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The value spec needs to be removed from the reg key
|
|
//
|
|
|
|
if (RemoveVal) {
|
|
pDecodeRegStr (p, KeyBuf, &DontCare);
|
|
OutLine = CreateEncodedRegistryString (KeyBuf, NULL);
|
|
|
|
//
|
|
// Workaround: CreateEncodedRegistryString always appends
|
|
// an asterisk, and we want to control when the asterisk
|
|
// appears.
|
|
//
|
|
|
|
p = _tcsrchr (OutLine, TEXT('*'));
|
|
if (p && p[1] == 0) {
|
|
p = _tcsdec2 (OutLine, p);
|
|
if (p) {
|
|
*((PTSTR) p) = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this entire key is going to be deleted, then
|
|
// turn on SkipKey so the memdb nodes will be skipped.
|
|
//
|
|
|
|
if (AppendStar && SkipKey[0] == 0) {
|
|
StringCopy (SkipKey, OutLine);
|
|
AppendWack (SkipKey);
|
|
SkipKeyBytes = ByteCount (SkipKey);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Perform substitution on the string
|
|
//
|
|
|
|
p = pPerformSubstitution (&EnvVars, OutLine);
|
|
MYASSERT (p);
|
|
|
|
if (RemoveVal) {
|
|
FreeEncodedRegistryString (OutLine);
|
|
}
|
|
|
|
//
|
|
// Write the file/reg key to the file
|
|
//
|
|
|
|
WriteFileString (File, p);
|
|
|
|
if (AppendStar) {
|
|
WriteFileString (File, TEXT("\\*"));
|
|
}
|
|
|
|
WriteFileString (File, TEXT("\r\n"));
|
|
|
|
FreePathString (p);
|
|
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
|
|
//
|
|
// Write blank line at the end
|
|
//
|
|
|
|
WriteFileString (File, TEXT("\r\n"));
|
|
|
|
FreeGrowList (&EnvVars);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pDumpDiffs (
|
|
VOID
|
|
)
|
|
{
|
|
MEMDB_ENUM e;
|
|
BOOL Changes = FALSE;
|
|
|
|
if (MemDbGetValueEx (&e, S_ZERO, NULL, NULL)) {
|
|
_tprintf (TEXT("Deleted Items:\n"));
|
|
Changes = TRUE;
|
|
|
|
do {
|
|
_tprintf (TEXT(" %s\n"), e.szName);
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
|
|
if (MemDbGetValueEx (&e, S_ADDED, NULL, NULL)) {
|
|
_tprintf (TEXT("Added Items:\n"));
|
|
|
|
do {
|
|
_tprintf (TEXT(" %s\n"), e.szName);
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
|
|
if (MemDbGetValueEx (&e, S_CHANGED, NULL, NULL)) {
|
|
_tprintf (TEXT("Changed Items:\n"));
|
|
|
|
do {
|
|
_tprintf (TEXT(" %s\n"), e.szName);
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
|
|
return Changes;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pGenerateInf (
|
|
POPTIONS Options
|
|
)
|
|
{
|
|
HANDLE File;
|
|
BOOL DelChanges;
|
|
|
|
//
|
|
// Dump changes to stdout
|
|
//
|
|
|
|
DelChanges = pDumpDiffs();
|
|
|
|
if (Options->OutputFile) {
|
|
//
|
|
// Write a section to our output file
|
|
//
|
|
|
|
File = CreateFile (
|
|
Options->OutputFile,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (File == INVALID_HANDLE_VALUE) {
|
|
_ftprintf (stderr, TEXT("Cannot generate %s, error %u\n"), Options->OutputFile, GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
if (DelChanges) {
|
|
pAppendThingsToDelete (Options, File);
|
|
}
|
|
|
|
CloseHandle (File);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
DoSnapMode (
|
|
POPTIONS Options
|
|
)
|
|
{
|
|
DWORD Start;
|
|
|
|
Start = GetTickCount();
|
|
|
|
if (!pTakeSnapshot (Options, FALSE)) {
|
|
return FALSE;
|
|
}
|
|
|
|
MemDbSave (Options->SnapFile);
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT("Run time: %u seconds\n"), (GetTickCount() - Start) / 1000);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
DoDiffMode (
|
|
POPTIONS Options
|
|
)
|
|
{
|
|
DWORD Start;
|
|
|
|
Start = GetTickCount();
|
|
|
|
if (GetFileAttributes (Options->SnapFile) == 0xffffffff) {
|
|
_ftprintf (stderr, TEXT("Bogus file arg: %s\n"), Options->SnapFile);
|
|
}
|
|
|
|
if (!Options->SnapMode) {
|
|
MemDbLoad (Options->SnapFile);
|
|
}
|
|
|
|
if (!pTakeSnapshot (Options, TRUE)) {
|
|
return FALSE;
|
|
}
|
|
|
|
pGenerateInf (Options);
|
|
|
|
if (!g_Quiet) {
|
|
_ftprintf (stderr, TEXT("Run time: %u seconds\n"), (GetTickCount() - Start) / 1000);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|