windows-nt/Source/XPSP1/NT/base/ntsetup/win95upg/undo/dll/alert.c
2020-09-26 16:20:57 +08:00

648 lines
17 KiB
C

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
alert.c
Abstract:
Implements the code to alert the user to problems that might be
left on the system after uninstall.
Author:
Jim Schmidt (jimschm) 07-Mar-2001
Revision History:
--*/
#include "pch.h"
#include "undop.h"
#include "resource.h"
typedef struct {
ULONGLONG Checksum;
BOOL InOldList;
BOOL InNewList;
BOOL ChangedFlag;
BOOL Duplicate;
UINT OldReferences;
UINT NewReferences;
WCHAR DisplayString[];
} APPLISTITEM, *PAPPLISTITEM;
typedef enum {
OLD_LIST,
NEW_LIST
} APPLIST;
BOOL
pAppendAppToGrowList (
IN OUT PGROWLIST List,
IN PCWSTR DisplayString,
IN ULONGLONG Checksum,
IN APPLIST WhichList
)
/*++
Routine Description:
pAppendAppToGrowList manages a private list of installed apps. The list
contains information about the app, such as if it was installed originally,
if it is installed now, what its name is, and if it has changed.
Arguments:
List - Specifies the grow list containing items; receives the updated list
DisplayString - Specifies the Add/Remove Programs display string
Checksum - Specifies the checksum of the Add/Remove Programs config data
WhichList - Specifies OLD_LIST for an app that was originally on the
Add/Remove Programs list, or NEW_LIST for an app that is now on the
ARP list.
Return Value:
TRUE - Success
FALSE - Memory alloc failure (might not even be possible; see mem fns)
--*/
{
PAPPLISTITEM appListItem;
UINT itemSize;
PBYTE result;
UINT count;
UINT u;
//
// Search for an existing identical list item, and update it if found
//
count = GrowListGetSize (List);
for (u = 0 ; u < count ; u++) {
appListItem = (PAPPLISTITEM) GrowListGetItem (List, u);
if (StringIMatchW (DisplayString, appListItem->DisplayString)) {
if (appListItem->Checksum == Checksum) {
break;
}
}
}
if (u < count) {
if (WhichList == OLD_LIST) {
appListItem->OldReferences += 1;
appListItem->InOldList = TRUE;
} else {
appListItem->NewReferences += 1;
appListItem->InNewList = TRUE;
}
return TRUE;
}
//
// This item is not on the list; add it now. First construct a structure
// in a temporary buffer, then put it in the list.
//
itemSize = SizeOfStringW (DisplayString) + sizeof (APPLISTITEM);
appListItem = (PAPPLISTITEM) MemAllocZeroed (itemSize);
if (!appListItem) {
return FALSE;
}
appListItem->Checksum = Checksum;
if (WhichList == OLD_LIST) {
appListItem->OldReferences = 1;
appListItem->InOldList = TRUE;
} else {
appListItem->NewReferences = 1;
appListItem->InNewList = TRUE;
}
StringCopyW (appListItem->DisplayString, DisplayString);
result = GrowListAppend (List, (PBYTE) appListItem, itemSize);
FreeMem (appListItem);
return result != NULL;
}
VOID
pIdentifyDuplicates (
IN OUT PGROWLIST List
)
/*++
Routine Description:
pIdentifyDuplicates scans the apps in the specified list, and merges them so
that duplicates are ignored. Duplicates are determined by comparing the
application title name only. When they are found, the flags are merged into
the first instance.
When merging the first instance with a duplicate, the following combinations
are possible:
unchanged = InOldList && InNewList && !ChangedFlag
new = !InOldList && InNewList (force ChangedFlag=TRUE)
removed = InOldList && !InNewList (force ChangedFlag=TRUE)
changed = InOldList && InNewList && ChangedFlag
| Dup Inst
1st Inst | unchanged | new | removed | changed
-----------------------------------------------------------------------
unchanged | UNCHANGED | changed | changed | changed
-----------------------------------------------------------------------
new | changed | NEW | changed | changed
-----------------------------------------------------------------------
removed | changed | changed | REMOVED | changed
-----------------------------------------------------------------------
changed | changed | changed | changed | CHANGED
-----------------------------------------------------------------------
Any time there is a conflict between two identically named entries, we have
to assume "changed" because we cannot tell exactly what happened.
Arguments:
List - Specifies the list of apps. Receives updated flags.
Return Value:
None.
--*/
{
UINT count;
UINT u;
UINT v;
PAPPLISTITEM appListItem;
PAPPLISTITEM lookAheadItem;
count = GrowListGetSize (List);
for (u = 0 ; u < count ; u++) {
appListItem = (PAPPLISTITEM) GrowListGetItem (List, u);
if (appListItem->Duplicate) {
continue;
}
if (appListItem->InOldList != appListItem->InNewList) {
appListItem->ChangedFlag = TRUE;
}
for (v = u + 1 ; v < count ; v++) {
lookAheadItem = (PAPPLISTITEM) GrowListGetItem (List, v);
if (lookAheadItem->Duplicate) {
continue;
}
if (StringIMatchW (appListItem->DisplayString, lookAheadItem->DisplayString)) {
lookAheadItem->Duplicate = TRUE;
appListItem->InOldList |= lookAheadItem->InOldList;
appListItem->InNewList |= lookAheadItem->InNewList;
appListItem->ChangedFlag |= lookAheadItem->ChangedFlag;
if (lookAheadItem->InOldList != lookAheadItem->InNewList) {
appListItem->ChangedFlag = TRUE;
}
}
}
}
}
INT_PTR
CALLBACK
pDisplayProgramsProc (
IN HWND hwndDlg,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
PCWSTR text;
switch (uMsg) {
case WM_INITDIALOG:
text = (PCWSTR) lParam;
MYASSERT (text);
SetDlgItemTextW (hwndDlg, IDC_EDIT1, text);
return TRUE;
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDOK:
EndDialog (hwndDlg, IDOK);
break;
case IDCANCEL:
EndDialog (hwndDlg, IDCANCEL);
break;
}
}
return FALSE;
}
BOOL
pDisplayProgramsDlg (
IN HWND UiParent,
IN PCWSTR ReportText
)
{
INT_PTR result;
result = DialogBoxParam (
g_hInst,
MAKEINTRESOURCE(IDD_APP_CHANGES),
UiParent,
pDisplayProgramsProc,
(LPARAM) ReportText
);
return result == IDOK;
}
BOOL
pProvideAppInstallAlert (
IN HWND UiParent
)
/*++
Routine Description:
pProvideAppInstallAlert generates a dialog whenever the Add/Remove Programs
list is different from what exists at the time of the upgrade. The user has
the ability to quit uninstall.
Arguments:
UiParent - Specifies the HWND of the parent window, typically the desktop
Return Value:
TRUE to continue with uninstall
FALSE to quit uninstall
--*/
{
GROWLIST appList = GROWLIST_INIT;
GROWBUFFER installedApps = GROWBUF_INIT;
GROWBUFFER newBuf = GROWBUF_INIT;
GROWBUFFER changedBuf = GROWBUF_INIT;
GROWBUFFER delBuf = GROWBUF_INIT;
BOOL result = FALSE;
GROWBUFFER completeText = GROWBUF_INIT;
WCHAR titleBuffer[256];
PINSTALLEDAPPW installedAppList;
UINT appCount;
UINT count;
UINT u;
PAPPLISTITEM appListItem;
HKEY key = NULL;
PBYTE data;
PCWSTR nextStr;
ULONGLONG *ullPtr;
BOOL failed;
UINT size;
PBYTE endOfLastString;
//
// Provide an alert whenever entries in Add/Remove Programs have changed.
// Get the original list from the registry. Then compare that list to what
// is presently installed.
//
__try {
//
// Add the apps recorded in the registry
//
key = OpenRegKeyStr (S_REGKEY_WIN_SETUP);
if (!key) {
DEBUGMSG ((DBG_ERROR, "Can't open %s", S_REGKEY_WIN_SETUP));
__leave; // fail uninstall; this should never happen
}
if (!GetRegValueTypeAndSize (key, S_REG_KEY_UNDO_APP_LIST, NULL, &size)) {
DEBUGMSG ((DBG_ERROR, "Can't query app list in %s", S_REGKEY_WIN_SETUP));
result = TRUE;
__leave; // continue with uninstall skipping the app alert
} else {
data = GetRegValueBinary (key, S_REG_KEY_UNDO_APP_LIST);
if (!data) {
result = TRUE;
__leave; // continue with uninstall skipping the app alert
}
//
// Compute the address of the first byte beyond the nul terminator for
// the last printable display name string, so we can protect ourselves
// from bad registry data.
//
endOfLastString = data + size - sizeof (ULONGLONG) - sizeof (WCHAR);
}
__try {
//
// Read in the app list stored in our registry blob
//
failed = FALSE;
nextStr = (PCWSTR) data;
while (*nextStr) {
// this might throw an exception:
ullPtr = (ULONGLONG *) (GetEndOfStringW (nextStr) + 1);
//
// ensure the checksum pointer is not beyond what we expect to be
// the first byte past the last non-empty display name string
//
if ((PBYTE) ullPtr > endOfLastString) {
failed = TRUE;
break;
}
DEBUGMSGW ((DBG_NAUSEA, "Original app: %s", nextStr));
pAppendAppToGrowList (
&appList,
nextStr,
*ullPtr,
OLD_LIST
);
nextStr = (PCWSTR) (ullPtr + 1);
}
}
__except (TRUE) {
failed = TRUE;
}
FreeMem (data);
if (failed) {
DEBUGMSG ((DBG_ERROR, "App key in %s is invalid", S_REGKEY_WIN_SETUP));
result = TRUE;
__leave; // continue with uninstall skipping the app alert
}
//
// Add all the *current* apps to the list
//
CoInitialize (NULL);
installedAppList = GetInstalledAppsW (&installedApps, &appCount);
if (installedAppList) {
for (u = 0 ; u < appCount ; u++) {
DEBUGMSGW ((DBG_NAUSEA, "Identified %s", installedAppList->DisplayName));
pAppendAppToGrowList (
&appList,
installedAppList->DisplayName,
installedAppList->Checksum,
NEW_LIST
);
installedAppList++;
}
} else {
result = TRUE;
__leave; // continue with uninstall skipping the app alert
}
//
// Compute the overlap of the original and current apps
//
pIdentifyDuplicates (&appList);
//
// Produce a formatted list for each of the three possible cases
// (new, remove, change)
//
count = GrowListGetSize (&appList);
for (u = 0 ; u < count ; u++) {
appListItem = (PAPPLISTITEM) GrowListGetItem (&appList, u);
if (appListItem->Duplicate) {
continue;
}
DEBUGMSGW ((DBG_NAUSEA, "Processing %s", appListItem->DisplayString));
if (appListItem->InOldList && appListItem->InNewList) {
if (appListItem->ChangedFlag ||
(appListItem->OldReferences != appListItem->NewReferences)
) {
DEBUGMSG_IF ((
appListItem->ChangedFlag,
DBG_VERBOSE,
"%ws has change flag",
appListItem->DisplayString
));
DEBUGMSG_IF ((
appListItem->OldReferences != appListItem->NewReferences,
DBG_VERBOSE,
"%ws has different ref count (old=%u vs new=%u)",
appListItem->DisplayString,
appListItem->OldReferences,
appListItem->NewReferences
));
GrowBufAppendStringW (&changedBuf, L" ");
GrowBufAppendStringW (&changedBuf, appListItem->DisplayString);
GrowBufAppendStringW (&changedBuf, L"\r\n");
} else {
DEBUGMSG ((DBG_VERBOSE, "%ws has not changed", appListItem->DisplayString));
}
} else if (appListItem->InOldList) {
DEBUGMSG ((DBG_VERBOSE, "%ws was removed", appListItem->DisplayString));
GrowBufAppendStringW (&delBuf, L" ");
GrowBufAppendStringW (&delBuf, appListItem->DisplayString);
GrowBufAppendStringW (&delBuf, L"\r\n");
} else if (appListItem->InNewList) {
DEBUGMSG ((DBG_VERBOSE, "%ws was added", appListItem->DisplayString));
GrowBufAppendStringW (&newBuf, L" ");
GrowBufAppendStringW (&newBuf, appListItem->DisplayString);
GrowBufAppendStringW (&newBuf, L"\r\n");
} else {
MYASSERT (FALSE);
}
}
//
// Build the report text in a single buffer
//
if (newBuf.End) {
//
// Append software that is newly installed
//
__try {
if (!LoadStringW (g_hInst, IDS_NEW_PROGRAMS, titleBuffer, ARRAYSIZE(titleBuffer))) {
DEBUGMSG ((DBG_ERROR, "Can't load New Programs heading text"));
__leave;
}
GrowBufAppendStringW (&completeText, titleBuffer);
GrowBufAppendStringW (&completeText, L"\r\n");
GrowBufAppendStringW (&completeText, (PCWSTR) newBuf.Buf);
}
__finally {
}
}
if (delBuf.End) {
//
// Append software that was removed
//
__try {
if (!LoadStringW (g_hInst, IDS_DELETED_PROGRAMS, titleBuffer, ARRAYSIZE(titleBuffer))) {
DEBUGMSG ((DBG_ERROR, "Can't load Deleted Programs heading text"));
__leave;
}
if (completeText.End) {
GrowBufAppendStringW (&completeText, L"\r\n");
}
GrowBufAppendStringW (&completeText, titleBuffer);
GrowBufAppendStringW (&completeText, L"\r\n");
GrowBufAppendStringW (&completeText, (PCWSTR) delBuf.Buf);
}
__finally {
}
}
if (changedBuf.End) {
//
// Append software that was altered
//
__try {
if (!LoadStringW (g_hInst, IDS_CHANGED_PROGRAMS, titleBuffer, ARRAYSIZE(titleBuffer))) {
DEBUGMSG ((DBG_ERROR, "Can't load Changed Programs heading text"));
__leave;
}
if (completeText.End) {
GrowBufAppendStringW (&completeText, L"\r\n");
}
GrowBufAppendStringW (&completeText, titleBuffer);
GrowBufAppendStringW (&completeText, L"\r\n");
GrowBufAppendStringW (&completeText, (PCWSTR) changedBuf.Buf);
}
__finally {
}
}
//
// Display UI
//
if (completeText.End) {
result = pDisplayProgramsDlg (UiParent, (PCWSTR) completeText.Buf);
} else {
DEBUGMSG ((DBG_VERBOSE, "No app conflicts; continuing without UI alert"));
result = TRUE;
}
}
__finally {
//
// Done
//
FreeGrowBuffer (&newBuf);
FreeGrowBuffer (&changedBuf);
FreeGrowBuffer (&delBuf);
FreeGrowBuffer (&completeText);
FreeGrowBuffer (&installedApps);
FreeGrowList (&appList);
CloseRegKey (key);
}
return result;
}
BOOL
ProvideUiAlerts (
IN HWND ParentWindow
)
/*++
Routine Description:
ProvideUiAlerts executes the functions that produce UI after the user has
chosen to uninstall the current operating system. The goal is to warn the
user about problems known to exist after the uninstall is complete.
This function is called before any changes are made to the system.
Arguments:
ParentWindow - Specifies the HWND to the parent for the UI, normally the
desktop window
Return Value:
TRUE - Continue with uninstall
FALSE - Quit uninstall
--*/
{
DeferredInit();
//
// Add other UI alerts here
//
return pProvideAppInstallAlert (ParentWindow);
}