3257 lines
95 KiB
C
3257 lines
95 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
shell.c
|
|
|
|
Abstract:
|
|
|
|
Contains code that implements shell folder migration. Shell folders
|
|
are moved into new NT locations whenever possible. Also, a set of
|
|
filters alter the content of the shell folders.
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 24-Aug-1998
|
|
|
|
Revision History:
|
|
|
|
Calin Negreanu (calinn) 09-Sep-1998 Obsolete links, fixes and other changes
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include <linkpif.h>
|
|
#include "migmainp.h"
|
|
|
|
#define DBG_SHELL "Shell"
|
|
|
|
#define SHELL_FOLDER_FILTERS_9X_NT \
|
|
DEFMAC(pObsoleteLinksFilter) \
|
|
DEFMAC(pStartupDisableFilter) \
|
|
DEFMAC(pFontNameFilter) \
|
|
DEFMAC(pCollisionDetection9xNt) \
|
|
|
|
#define SHELL_FOLDER_FILTERS_NT_9X \
|
|
DEFMAC(pDetectOtherShellFolder) \
|
|
DEFMAC(pCollisionDetectionNt9x) \
|
|
|
|
|
|
typedef enum {
|
|
INITIALIZE,
|
|
PROCESS_PATH,
|
|
TERMINATE
|
|
} CALL_CONTEXT;
|
|
|
|
|
|
#define SHELLFILTER_OK 0
|
|
#define SHELLFILTER_SKIP_FILE 1
|
|
#define SHELLFILTER_SKIP_DIRECTORY 2
|
|
#define SHELLFILTER_ERROR 3
|
|
#define SHELLFILTER_FORCE_CHANGE 4
|
|
|
|
|
|
typedef struct {
|
|
IN PCWSTR Win9xUser; OPTIONAL
|
|
IN PCWSTR FixedUserName; OPTIONAL
|
|
IN HKEY UserHiveRoot; // HKLM or the Default User hive
|
|
IN PCWSTR ShellFolderIdentifier; // i.e., Fonts, Programs, etc...
|
|
IN OUT WCHAR TempSourcePath[MEMDB_MAX]; // full path, a child of SrcRootPath
|
|
IN OUT WCHAR DestinationPath[MEMDB_MAX];
|
|
IN PCWSTR DefaultShellFolder; OPTIONAL
|
|
IN PCWSTR UserDefaultLocation;
|
|
IN PCWSTR SrcRootPath; // the temp root dir
|
|
IN PCWSTR DestRootPath; // the dest root dir
|
|
IN PCWSTR OrigRootPath; // the Win9x root dir
|
|
IN OUT DWORD Attributes;
|
|
IN DWORD UserFlags;
|
|
IN OUT DWORD State;
|
|
IN PMIGRATE_USER_ENUM EnumPtr;
|
|
IN CALL_CONTEXT Context;
|
|
} PROFILE_MERGE_DATA, *PPROFILE_MERGE_DATA;
|
|
|
|
typedef DWORD(PROFILEMERGEFILTER_PROTOTYPE)(IN OUT PPROFILE_MERGE_DATA Data);
|
|
typedef PROFILEMERGEFILTER_PROTOTYPE * PROFILEMERGEFILTER;
|
|
|
|
typedef struct {
|
|
PROFILEMERGEFILTER Fn;
|
|
PCSTR Name;
|
|
DWORD State;
|
|
} SHELL_FOLDER_FILTER, *PSHELL_FOLDER_FILTER;
|
|
|
|
|
|
#define DEFMAC(fn) PROFILEMERGEFILTER_PROTOTYPE fn;
|
|
|
|
SHELL_FOLDER_FILTERS_9X_NT
|
|
|
|
SHELL_FOLDER_FILTERS_NT_9X
|
|
|
|
#undef DEFMAC
|
|
|
|
|
|
|
|
#define DEFMAC(fn) {fn, #fn},
|
|
|
|
static SHELL_FOLDER_FILTER g_Filters_9xNt[] = {
|
|
SHELL_FOLDER_FILTERS_9X_NT /* , */
|
|
{NULL}
|
|
};
|
|
|
|
static SHELL_FOLDER_FILTER g_Filters_Nt9x[] = {
|
|
SHELL_FOLDER_FILTERS_NT_9X /* , */
|
|
{NULL}
|
|
};
|
|
|
|
#undef DEFMAC
|
|
|
|
GROWLIST g_SfQueueSrc;
|
|
GROWLIST g_SfQueueDest;
|
|
|
|
PVOID g_SystemSfList;
|
|
PVOID g_UserSfList;
|
|
|
|
typedef struct {
|
|
PCTSTR sfName;
|
|
PCTSTR sfPath;
|
|
HKEY SfKey;
|
|
REGVALUE_ENUM SfKeyEnum;
|
|
BOOL UserSf;
|
|
} SF_ENUM, *PSF_ENUM;
|
|
|
|
#define MAX_SHELL_TAG 64
|
|
|
|
typedef struct {
|
|
INT CsidlValue;
|
|
PCTSTR Tag;
|
|
} CSIDLMAP, *PCSIDLMAP;
|
|
|
|
CSIDLMAP g_CsidlMap[] = {
|
|
CSIDL_ADMINTOOLS, TEXT("Administrative Tools"),
|
|
CSIDL_ALTSTARTUP, TEXT("AltStartup"),
|
|
CSIDL_APPDATA, TEXT("AppData"),
|
|
CSIDL_BITBUCKET, TEXT("RecycleBinFolder"),
|
|
CSIDL_CONNECTIONS, TEXT("ConnectionsFolder"),
|
|
CSIDL_CONTROLS, TEXT("ControlPanelFolder"),
|
|
CSIDL_COOKIES, TEXT("Cookies"),
|
|
CSIDL_DESKTOP, TEXT("Desktop"),
|
|
CSIDL_DRIVES, TEXT("DriveFolder"),
|
|
CSIDL_FAVORITES, TEXT("Favorites"),
|
|
CSIDL_FONTS, TEXT("Fonts"),
|
|
CSIDL_HISTORY, TEXT("History"),
|
|
CSIDL_INTERNET, TEXT("InternetFolder"),
|
|
CSIDL_INTERNET_CACHE, TEXT("Cache"),
|
|
CSIDL_LOCAL_APPDATA, TEXT("Local AppData"),
|
|
CSIDL_MYDOCUMENTS, TEXT("My Documents"),
|
|
CSIDL_MYMUSIC, TEXT("My Music"),
|
|
CSIDL_MYPICTURES, TEXT("My Pictures"),
|
|
CSIDL_MYVIDEO, TEXT("My Video"),
|
|
CSIDL_NETHOOD, TEXT("NetHood"),
|
|
CSIDL_NETWORK, TEXT("NetworkFolder"),
|
|
CSIDL_PERSONAL, TEXT("Personal"),
|
|
CSIDL_PROGRAMS, TEXT("Programs"),
|
|
CSIDL_RECENT, TEXT("Recent"),
|
|
CSIDL_SENDTO, TEXT("SendTo"),
|
|
CSIDL_STARTMENU, TEXT("Start Menu"),
|
|
CSIDL_STARTUP, TEXT("Startup"),
|
|
CSIDL_TEMPLATES, TEXT("Templates"),
|
|
CSIDL_COMMON_ADMINTOOLS, TEXT("Common Administrative Tools"),
|
|
CSIDL_COMMON_ALTSTARTUP, TEXT("Common AltStartup"),
|
|
CSIDL_COMMON_APPDATA, TEXT("Common AppData"),
|
|
CSIDL_COMMON_DESKTOPDIRECTORY, TEXT("Common Desktop"),
|
|
CSIDL_COMMON_DOCUMENTS, TEXT("Common Documents"),
|
|
CSIDL_COMMON_FAVORITES, TEXT("Common Favorites"),
|
|
CSIDL_COMMON_PROGRAMS, TEXT("Common Programs"),
|
|
CSIDL_COMMON_STARTMENU, TEXT("Common Start Menu"),
|
|
CSIDL_COMMON_STARTUP, TEXT("Common Startup"),
|
|
CSIDL_COMMON_TEMPLATES, TEXT("Common Templates"),
|
|
CSIDL_COMMON_DOCUMENTS, TEXT("Common Personal"),
|
|
CSIDL_COMMON_MUSIC, TEXT("CommonMusic"),
|
|
CSIDL_COMMON_PICTURES, TEXT("CommonPictures"),
|
|
CSIDL_COMMON_VIDEO, TEXT("CommonVideo"),
|
|
0, NULL
|
|
};
|
|
|
|
|
|
|
|
VOID
|
|
pConvertCommonSfToPerUser (
|
|
IN PCTSTR CommonSf,
|
|
OUT PTSTR PerUserSf // must hold MAX_SHELL_TAG chars
|
|
);
|
|
|
|
BOOL
|
|
pIsCommonSf (
|
|
IN PCTSTR ShellFolderTag
|
|
);
|
|
|
|
VOID
|
|
pConvertPerUserSfToCommon (
|
|
IN PCTSTR PerUserSf,
|
|
OUT PTSTR CommonSf // must hold MAX_SHELL_TAG chars
|
|
);
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
EnumFirstRegShellFolder and EnumNextRegShellFolder are enumeration routines that
|
|
enumerate all shell folders per system or for a particular user.
|
|
|
|
Arguments:
|
|
|
|
e - enumeration structure
|
|
EnumPtr - user enumeration structure
|
|
|
|
Return Value:
|
|
|
|
Both routines return TRUE if a new shell folder could be found, FALSE otherwise
|
|
|
|
--*/
|
|
|
|
|
|
BOOL
|
|
EnumFirstRegShellFolder (
|
|
IN OUT PSF_ENUM e,
|
|
IN BOOL UserSf
|
|
)
|
|
{
|
|
HKEY UsfKey;
|
|
|
|
e->UserSf = UserSf;
|
|
e->sfPath = NULL;
|
|
|
|
if (UserSf) {
|
|
e->SfKey = OpenRegKey (HKEY_CURRENT_USER, S_SHELL_FOLDERS_KEY_USER);
|
|
} else {
|
|
e->SfKey = OpenRegKeyStr (S_SHELL_FOLDERS_KEY_SYSTEM);
|
|
}
|
|
|
|
if (!e->SfKey) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (EnumFirstRegValue (&e->SfKeyEnum, e->SfKey)) {
|
|
e->sfName = e->SfKeyEnum.ValueName;
|
|
e->sfPath = NULL;
|
|
|
|
if (UserSf) {
|
|
UsfKey = OpenRegKey (HKEY_CURRENT_USER, S_USHELL_FOLDERS_KEY_USER);
|
|
} else {
|
|
UsfKey = OpenRegKeyStr (S_USHELL_FOLDERS_KEY_SYSTEM);
|
|
}
|
|
|
|
if (UsfKey) {
|
|
e->sfPath = GetRegValueString (UsfKey, e->SfKeyEnum.ValueName);
|
|
CloseRegKey (UsfKey);
|
|
}
|
|
|
|
if (e->sfPath == NULL) {
|
|
e->sfPath = GetRegValueString (e->SfKey, e->SfKeyEnum.ValueName);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CloseRegKey (e->SfKey);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumNextRegShellFolder (
|
|
IN OUT PSF_ENUM e
|
|
)
|
|
{
|
|
HKEY UsfKey;
|
|
|
|
if (e->sfPath) {
|
|
MemFree (g_hHeap, 0, e->sfPath);
|
|
e->sfPath = NULL;
|
|
}
|
|
|
|
if (EnumNextRegValue (&e->SfKeyEnum)) {
|
|
|
|
e->sfName = e->SfKeyEnum.ValueName;
|
|
e->sfPath = NULL;
|
|
|
|
if (e->UserSf) {
|
|
UsfKey = OpenRegKey (HKEY_CURRENT_USER, S_USHELL_FOLDERS_KEY_USER);
|
|
} else {
|
|
UsfKey = OpenRegKeyStr (S_USHELL_FOLDERS_KEY_SYSTEM);
|
|
}
|
|
|
|
if (UsfKey) {
|
|
e->sfPath = GetRegValueString (UsfKey, e->SfKeyEnum.ValueName);
|
|
CloseRegKey (UsfKey);
|
|
}
|
|
|
|
if (e->sfPath == NULL) {
|
|
e->sfPath = GetRegValueString (e->SfKey, e->SfKeyEnum.ValueName);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CloseRegKey (e->SfKey);
|
|
return FALSE;
|
|
}
|
|
|
|
VOID
|
|
AbortEnumRegShellFolder (
|
|
IN OUT PSF_ENUM e
|
|
)
|
|
{
|
|
if (e->sfPath) {
|
|
MemFree (g_hHeap, 0, e->sfPath);
|
|
e->sfPath = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pPrepareSfRestartability(
|
|
VOID
|
|
)
|
|
{
|
|
PTSTR userProfilePath = NULL;
|
|
DWORD Size;
|
|
MIGRATE_USER_ENUM e;
|
|
|
|
if (EnumFirstUserToMigrate (&e, ENUM_NO_FLAGS)) {
|
|
do {
|
|
if (!e.CreateOnly &&
|
|
(e.AccountType != DEFAULT_USER_ACCOUNT) &&
|
|
(e.AccountType != LOGON_USER_SETTINGS)
|
|
) {
|
|
if (GetUserProfilePath (e.FixedUserName, &userProfilePath)) {
|
|
RenameOnRestartOfGuiMode (userProfilePath, NULL);
|
|
FreePathString (userProfilePath);
|
|
}
|
|
}
|
|
} while (EnumNextUserToMigrate (&e));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pFlushSfQueue (
|
|
VOID
|
|
)
|
|
{
|
|
UINT u;
|
|
UINT count;
|
|
PCTSTR source;
|
|
PCTSTR dest;
|
|
|
|
//
|
|
// For files that need to be copied, do that now before writing to the journal
|
|
//
|
|
|
|
count = GrowListGetSize (&g_SfQueueSrc);
|
|
if (!count) {
|
|
return;
|
|
}
|
|
|
|
for (u = 0 ; u < count ; u++) {
|
|
|
|
dest = GrowListGetString (&g_SfQueueDest, u);
|
|
if (!dest) {
|
|
continue;
|
|
}
|
|
|
|
if (DoesFileExist (dest)) {
|
|
|
|
source = GrowListGetString (&g_SfQueueSrc, u);
|
|
MYASSERT (source);
|
|
|
|
if (!OurCopyFileW (source, dest)) {
|
|
LOG ((LOG_WARNING, (PCSTR)MSG_COULD_NOT_MOVE_FILE_LOG, dest, GetLastError ()));
|
|
g_BlowAwayTempShellFolders = FALSE;
|
|
}
|
|
|
|
//
|
|
// Make the string pointers NULL for this item
|
|
//
|
|
|
|
GrowListResetItem (&g_SfQueueSrc, u);
|
|
GrowListResetItem (&g_SfQueueDest, u);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now record the remaining items in the journal (before the move
|
|
// happens). Ignore journal failures. Since we are undoing the move,
|
|
// source and dest must be flipped.
|
|
//
|
|
|
|
RenameListOnRestartOfGuiMode (&g_SfQueueDest, &g_SfQueueSrc);
|
|
|
|
//
|
|
// Do the move
|
|
//
|
|
|
|
for (u = 0 ; u < count ; u++) {
|
|
|
|
source = GrowListGetString (&g_SfQueueSrc, u);
|
|
dest = GrowListGetString (&g_SfQueueDest, u);
|
|
|
|
if (!source || !dest) {
|
|
continue;
|
|
}
|
|
|
|
if (!OurMoveFileEx (source, dest, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
|
|
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
|
DEBUGMSG ((DBG_WARNING, "%s already exists", dest));
|
|
} else {
|
|
LOG ((LOG_WARNING, (PCSTR)MSG_COULD_NOT_MOVE_FILE_LOG, dest, GetLastError ()));
|
|
g_BlowAwayTempShellFolders = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up -- grow lists are ready for reuse after FreeGrowList
|
|
//
|
|
|
|
FreeGrowList (&g_SfQueueSrc);
|
|
FreeGrowList (&g_SfQueueDest);
|
|
}
|
|
|
|
|
|
VOID
|
|
pQueueSfMove (
|
|
IN PCTSTR Source,
|
|
IN PCTSTR Destination
|
|
)
|
|
{
|
|
UINT count;
|
|
|
|
MYASSERT (Source && Destination);
|
|
|
|
count = GrowListGetSize (&g_SfQueueSrc);
|
|
if (count == 1000) {
|
|
//
|
|
// Do 1,000 moves at once
|
|
//
|
|
|
|
pFlushSfQueue();
|
|
}
|
|
|
|
GrowListAppendString (&g_SfQueueSrc, Source);
|
|
GrowListAppendString (&g_SfQueueDest, Destination);
|
|
}
|
|
|
|
|
|
PVOID
|
|
pCreateSystemSfList (
|
|
)
|
|
{
|
|
PCTSTR expandedPath;
|
|
PVOID Table;
|
|
SF_ENUM e;
|
|
|
|
Table = pSetupStringTableInitialize();
|
|
|
|
if (!Table) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Load all the System shell folders into this table
|
|
//
|
|
|
|
if (EnumFirstRegShellFolder (&e, FALSE)) {
|
|
|
|
do {
|
|
expandedPath = ExpandEnvironmentText (e.sfPath);
|
|
pSetupStringTableAddString (Table, (PVOID) expandedPath, STRTAB_CASE_INSENSITIVE);
|
|
FreeText (expandedPath);
|
|
} while (EnumNextRegShellFolder (&e));
|
|
}
|
|
return Table;
|
|
}
|
|
|
|
PVOID
|
|
pCreateUserSfList (
|
|
IN PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
PTSTR CurrentUserProfilePath = NULL;
|
|
TCHAR DefaultUserProfilePath[MAX_TCHAR_PATH];
|
|
DWORD Size;
|
|
PCTSTR expandedPath;
|
|
PCTSTR tempExpand;
|
|
PVOID Table;
|
|
SF_ENUM e;
|
|
|
|
if (Data && Data->FixedUserName) {
|
|
|
|
if (!GetUserProfilePath (Data->FixedUserName, &CurrentUserProfilePath)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
Size = sizeof (DefaultUserProfilePath);
|
|
|
|
if (!GetDefaultUserProfileDirectory (DefaultUserProfilePath, &Size)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Table = pSetupStringTableInitialize();
|
|
|
|
if (!Table) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Load all the System shell folders into this table
|
|
//
|
|
|
|
if (EnumFirstRegShellFolder (&e, TRUE)) {
|
|
|
|
do {
|
|
tempExpand = StringSearchAndReplace (
|
|
e.sfPath,
|
|
S_USERPROFILE_ENV,
|
|
CurrentUserProfilePath?CurrentUserProfilePath:DefaultUserProfilePath
|
|
);
|
|
|
|
if (!tempExpand) {
|
|
tempExpand = DuplicatePathString (e.sfPath, 0);
|
|
}
|
|
|
|
expandedPath = ExpandEnvironmentText (tempExpand);
|
|
|
|
FreePathString (tempExpand);
|
|
|
|
pSetupStringTableAddString (Table, (PVOID) expandedPath, STRTAB_CASE_INSENSITIVE);
|
|
|
|
FreeText (expandedPath);
|
|
|
|
} while (EnumNextRegShellFolder (&e));
|
|
}
|
|
|
|
if (CurrentUserProfilePath) {
|
|
FreePathString (CurrentUserProfilePath);
|
|
CurrentUserProfilePath = NULL;
|
|
}
|
|
return Table;
|
|
}
|
|
|
|
VOID
|
|
pDestroySfList (
|
|
IN PVOID Table
|
|
)
|
|
{
|
|
if (Table) {
|
|
pSetupStringTableDestroy (Table);
|
|
}
|
|
}
|
|
|
|
PVOID g_LinkDataPool = NULL;
|
|
|
|
typedef struct _LINK_DATA {
|
|
PCTSTR Target;
|
|
PCTSTR Arguments;
|
|
PCTSTR ShellFolderName;
|
|
struct _LINK_DATA *Next;
|
|
} LINK_DATA, *PLINK_DATA;
|
|
|
|
PVOID g_FoldersTable;
|
|
PVOID g_Merged9xFolders;
|
|
|
|
typedef struct _LINK_RENAME_DATA {
|
|
PCTSTR OldTarget;
|
|
PCTSTR NewTarget;
|
|
PCTSTR OldArguments;
|
|
PCTSTR NewArguments;
|
|
PCTSTR ShellFolderName;
|
|
struct _LINK_RENAME_DATA *Next;
|
|
} LINK_RENAME_DATA, *PLINK_RENAME_DATA;
|
|
|
|
PLINK_RENAME_DATA g_LinkRenameData;
|
|
|
|
VOID
|
|
pAddAllLinksToList (
|
|
PTSTR AllocBuffer, // MEMDB_MAX * 4, caller-owned for less allocs
|
|
PCTSTR ShellFolderName,
|
|
PCTSTR RootPath,
|
|
IShellLink *ShellLink,
|
|
IPersistFile *PersistFile
|
|
)
|
|
{
|
|
TREE_ENUM e;
|
|
PTSTR ShortcutTarget;
|
|
PTSTR ShortcutArgs;
|
|
PTSTR ShortcutWorkDir;
|
|
PTSTR ShortcutIconPath;
|
|
INT ShortcutIcon;
|
|
WORD ShortcutHotKey;
|
|
BOOL dosApp;
|
|
BOOL msDosMode;
|
|
PLINK_DATA linkData;
|
|
LONG stringId;
|
|
|
|
ShortcutTarget = AllocBuffer + MEMDB_MAX;
|
|
ShortcutArgs = ShortcutTarget + MEMDB_MAX;
|
|
ShortcutWorkDir = ShortcutArgs + MEMDB_MAX;
|
|
ShortcutIconPath = ShortcutWorkDir + MEMDB_MAX;
|
|
|
|
if (EnumFirstFileInTree (&e, RootPath, NULL, FALSE)) {
|
|
|
|
do {
|
|
if (e.Directory) {
|
|
if (((g_SystemSfList) && (pSetupStringTableLookUpString (g_SystemSfList, (PVOID) e.FullPath, STRTAB_CASE_INSENSITIVE) != -1)) ||
|
|
((g_UserSfList) && (pSetupStringTableLookUpString (g_UserSfList, (PVOID) e.FullPath, STRTAB_CASE_INSENSITIVE) != -1))
|
|
) {
|
|
AbortEnumCurrentDir (&e);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Extracting shortcut info for enumerated file %s", e.FullPath));
|
|
|
|
if (ExtractShortcutInfo (
|
|
ShortcutTarget,
|
|
ShortcutArgs,
|
|
ShortcutWorkDir,
|
|
ShortcutIconPath,
|
|
&ShortcutIcon,
|
|
&ShortcutHotKey,
|
|
&dosApp,
|
|
&msDosMode,
|
|
NULL,
|
|
NULL,
|
|
e.FullPath,
|
|
ShellLink,
|
|
PersistFile
|
|
)) {
|
|
linkData = (PLINK_DATA) (PoolMemGetMemory (g_LinkDataPool, sizeof (LINK_DATA)));
|
|
ZeroMemory (linkData, sizeof (LINK_DATA));
|
|
|
|
linkData->Target = PoolMemDuplicateString (g_LinkDataPool, ShortcutTarget);
|
|
linkData->Arguments = PoolMemDuplicateString (g_LinkDataPool, ShortcutArgs);
|
|
linkData->ShellFolderName = PoolMemDuplicateString (g_LinkDataPool, ShellFolderName);
|
|
linkData->Next = NULL;
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Recording NT default shortcut: %s in %s", e.FullPath, ShellFolderName));
|
|
|
|
stringId = pSetupStringTableLookUpString (g_FoldersTable, (PTSTR)ShellFolderName, 0);
|
|
|
|
if (stringId != -1) {
|
|
pSetupStringTableGetExtraData (g_FoldersTable, stringId, &linkData->Next, sizeof (PLINK_DATA));
|
|
pSetupStringTableSetExtraData (g_FoldersTable, stringId, &linkData, sizeof (PLINK_DATA));
|
|
}
|
|
else {
|
|
pSetupStringTableAddStringEx (
|
|
g_FoldersTable,
|
|
(PTSTR)ShellFolderName,
|
|
STRTAB_CASE_INSENSITIVE,
|
|
&linkData,
|
|
sizeof (PLINK_DATA)
|
|
);
|
|
}
|
|
}
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pAddKnownLinks (
|
|
VOID
|
|
)
|
|
{
|
|
INFCONTEXT context;
|
|
TCHAR field[MEMDB_MAX];
|
|
BOOL result = FALSE;
|
|
PLINK_DATA linkData;
|
|
PCTSTR pathExp;
|
|
LONG stringId;
|
|
|
|
PCTSTR ArgList [4] = {TEXT("ProgramFiles"), g_ProgramFiles, NULL, NULL};
|
|
|
|
MYASSERT (g_WkstaMigInf);
|
|
|
|
if (SetupFindFirstLine (g_WkstaMigInf, S_KNOWN_NT_LINKS, NULL, &context)) {
|
|
|
|
do {
|
|
linkData = (PLINK_DATA) (PoolMemGetMemory (g_LinkDataPool, sizeof (LINK_DATA)));
|
|
ZeroMemory (linkData, sizeof (LINK_DATA));
|
|
result = FALSE;
|
|
|
|
__try {
|
|
|
|
if (!SetupGetStringField (&context, 1, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->Target = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 2, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->Arguments = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 3, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
linkData->ShellFolderName = PoolMemDuplicateString (g_LinkDataPool, field);
|
|
linkData->Next = NULL;
|
|
result = TRUE;
|
|
}
|
|
__finally {
|
|
|
|
if (result) {
|
|
DEBUGMSG ((DBG_SHELL, "Recording known link: %s in %s", linkData->Target, linkData->ShellFolderName));
|
|
|
|
stringId = pSetupStringTableLookUpString (g_FoldersTable, (PTSTR)linkData->ShellFolderName, 0);
|
|
|
|
if (stringId != -1) {
|
|
pSetupStringTableGetExtraData (g_FoldersTable, stringId, &linkData->Next, sizeof (PLINK_DATA));
|
|
pSetupStringTableSetExtraData (g_FoldersTable, stringId, &linkData, sizeof (PLINK_DATA));
|
|
}
|
|
else {
|
|
pSetupStringTableAddStringEx (
|
|
g_FoldersTable,
|
|
(PTSTR)linkData->ShellFolderName,
|
|
STRTAB_CASE_INSENSITIVE,
|
|
&linkData,
|
|
sizeof (PLINK_DATA)
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
|
|
if (linkData->Target) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->Target);
|
|
}
|
|
|
|
if (linkData->Arguments) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->Arguments);
|
|
}
|
|
|
|
if (linkData->ShellFolderName) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->ShellFolderName);
|
|
}
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData);
|
|
linkData = NULL;
|
|
}
|
|
}
|
|
} while (SetupFindNextLine (&context, &context));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pCreateLinksList (
|
|
VOID
|
|
)
|
|
{
|
|
SF_ENUM e;
|
|
PCTSTR expandedPath;
|
|
UINT commonLen;
|
|
DWORD Size;
|
|
PCTSTR tempExpand;
|
|
PTSTR DefaultUserProfilePath;
|
|
IShellLink *shellLink;
|
|
IPersistFile *persistFile;
|
|
PTSTR perUserName;
|
|
PTSTR bigBuf = NULL;
|
|
|
|
__try {
|
|
bigBuf = (PTSTR) MemAllocUninit ((MEMDB_MAX * 4 + MAX_TCHAR_PATH + MAX_SHELL_TAG) * sizeof (TCHAR));
|
|
if (!bigBuf) {
|
|
__leave;
|
|
}
|
|
|
|
DefaultUserProfilePath = bigBuf + MEMDB_MAX * 4;
|
|
perUserName = DefaultUserProfilePath + MAX_TCHAR_PATH;
|
|
|
|
g_LinkDataPool = PoolMemInitNamedPool ("LinkData Pool");
|
|
|
|
g_FoldersTable = pSetupStringTableInitializeEx (sizeof (PLINK_DATA), 0);
|
|
|
|
if (!g_FoldersTable) {
|
|
DEBUGMSG((DBG_ERROR, "Cannot initialize Shell Folders table."));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// First thing: Load links from the INF files. These are links that we know NT is going to install
|
|
//
|
|
pAddKnownLinks ();
|
|
|
|
if (InitCOMLink (&shellLink, &persistFile)) {
|
|
|
|
//
|
|
// Go through all system shell folders and list the links
|
|
//
|
|
|
|
if (EnumFirstRegShellFolder (&e, FALSE)) {
|
|
|
|
do {
|
|
if (*e.sfPath) {
|
|
expandedPath = ExpandEnvironmentText (e.sfPath);
|
|
|
|
pConvertCommonSfToPerUser (e.sfName, perUserName);
|
|
|
|
pAddAllLinksToList (bigBuf, perUserName, expandedPath, shellLink, persistFile);
|
|
FreeText (expandedPath);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_WARNING, "Shell Folder <%s> data is empty!", e.sfName));
|
|
} while (EnumNextRegShellFolder (&e));
|
|
}
|
|
|
|
Size = MAX_TCHAR_PATH;
|
|
|
|
if (!GetDefaultUserProfileDirectory (DefaultUserProfilePath, &Size)) {
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Go through all user shell folders and list the links from the default user dirs
|
|
//
|
|
|
|
if (EnumFirstRegShellFolder (&e, TRUE)) {
|
|
|
|
do {
|
|
if (*e.sfPath) {
|
|
tempExpand = StringSearchAndReplace (
|
|
e.sfPath,
|
|
S_USERPROFILE_ENV,
|
|
DefaultUserProfilePath
|
|
);
|
|
|
|
if (!tempExpand) {
|
|
tempExpand = DuplicatePathString (e.sfPath, 0);
|
|
}
|
|
|
|
expandedPath = ExpandEnvironmentText (tempExpand);
|
|
|
|
FreePathString (tempExpand);
|
|
|
|
pAddAllLinksToList (bigBuf, e.sfName, expandedPath, shellLink, persistFile);
|
|
|
|
FreeText (expandedPath);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_WARNING, "Shell Folder <%s> data is empty!", e.sfName));
|
|
} while (EnumNextRegShellFolder (&e));
|
|
}
|
|
|
|
FreeCOMLink (&shellLink, &persistFile);
|
|
|
|
}
|
|
else {
|
|
DEBUGMSG((DBG_ERROR, "Cannot initialize COM. Obsolete links filter will not work."));
|
|
}
|
|
}
|
|
__finally {
|
|
if (bigBuf) {
|
|
FreeMem (bigBuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pCreateLinksRenameList (
|
|
VOID
|
|
)
|
|
{
|
|
INFCONTEXT context;
|
|
TCHAR field[MEMDB_MAX];
|
|
BOOL result = FALSE;
|
|
PLINK_RENAME_DATA linkData;
|
|
PCTSTR pathExp;
|
|
PCTSTR ArgList [4] = {TEXT("ProgramFiles"), g_ProgramFiles, NULL, NULL};
|
|
|
|
MYASSERT (g_WkstaMigInf);
|
|
|
|
if (SetupFindFirstLine (g_WkstaMigInf, S_OBSOLETE_LINKS, NULL, &context)) {
|
|
|
|
do {
|
|
linkData = (PLINK_RENAME_DATA) (PoolMemGetMemory (g_LinkDataPool, sizeof (LINK_RENAME_DATA)));
|
|
ZeroMemory (linkData, sizeof (LINK_RENAME_DATA));
|
|
result = FALSE;
|
|
|
|
__try {
|
|
|
|
if (!SetupGetStringField (&context, 1, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->OldTarget = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 2, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->OldArguments = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 3, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->NewTarget = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 4, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
pathExp = ExpandEnvironmentTextEx (field, ArgList);
|
|
linkData->NewArguments = PoolMemDuplicateString (g_LinkDataPool, pathExp);
|
|
FreeText (pathExp);
|
|
|
|
if (!SetupGetStringField (&context, 5, field, MEMDB_MAX, NULL)) {
|
|
__leave;
|
|
}
|
|
linkData->ShellFolderName = PoolMemDuplicateString (g_LinkDataPool, field);
|
|
result = TRUE;
|
|
}
|
|
__finally {
|
|
|
|
if (result) {
|
|
linkData->Next = g_LinkRenameData;
|
|
g_LinkRenameData = linkData;
|
|
}
|
|
else {
|
|
|
|
if (linkData->OldTarget) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->OldTarget);
|
|
}
|
|
|
|
if (linkData->NewTarget) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->NewTarget);
|
|
}
|
|
|
|
if (linkData->OldArguments) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->OldArguments);
|
|
}
|
|
|
|
if (linkData->NewArguments) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->NewArguments);
|
|
}
|
|
|
|
if (linkData->ShellFolderName) {
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData->ShellFolderName);
|
|
}
|
|
PoolMemReleaseMemory (g_LinkDataPool, (PVOID)linkData);
|
|
linkData = NULL;
|
|
}
|
|
}
|
|
} while (SetupFindNextLine (&context, &context));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pDestroyLinksData (
|
|
VOID
|
|
)
|
|
{
|
|
if (g_LinkDataPool != NULL) {
|
|
PoolMemDestroyPool (g_LinkDataPool);
|
|
g_LinkDataPool = NULL;
|
|
}
|
|
|
|
if (g_FoldersTable != NULL) {
|
|
pSetupStringTableDestroy (g_FoldersTable);
|
|
}
|
|
g_LinkRenameData = NULL;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pMigrateShellFolder (
|
|
IN PCTSTR Win9xUser, OPTIONAL
|
|
IN PCTSTR FixedUserName, OPTIONAL
|
|
IN BOOL SystemShellFolder,
|
|
IN PCTSTR ShellFolderIdentifier,
|
|
IN PCTSTR SourcePath,
|
|
IN PCTSTR DestinationPath,
|
|
IN PCTSTR OrigSourcePath,
|
|
IN DWORD UserFlags,
|
|
IN PMIGRATE_USER_ENUM EnumPtr
|
|
);
|
|
|
|
TCHAR g_DefaultHivePath[MAX_TCHAR_PATH];
|
|
HKEY g_DefaultHiveRoot;
|
|
INT g_DefaultHiveMapped;
|
|
|
|
|
|
VOID
|
|
pMigrateSystemShellFolders (
|
|
VOID
|
|
)
|
|
{
|
|
FILEOP_ENUM eOp;
|
|
FILEOP_PROP_ENUM eOpProp;
|
|
PTSTR NewDest;
|
|
PTSTR OrigSrc;
|
|
|
|
if (EnumFirstPathInOperation (&eOp, OPERATION_SHELL_FOLDER)) {
|
|
|
|
do {
|
|
if (IsPatternMatch (S_DOT_ALLUSERS TEXT("\\*"), eOp.Path)) {
|
|
|
|
NewDest = NULL;
|
|
OrigSrc = NULL;
|
|
if (EnumFirstFileOpProperty (&eOpProp, eOp.Sequencer, OPERATION_SHELL_FOLDER)) {
|
|
|
|
do {
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_DEST)) {
|
|
NewDest = DuplicatePathString (eOpProp.Property, 0);
|
|
}
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_ORIGINAL_SRC)) {
|
|
OrigSrc = DuplicatePathString (eOpProp.Property, 0);
|
|
}
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_SRC)) {
|
|
|
|
MYASSERT (NewDest);
|
|
MYASSERT (OrigSrc);
|
|
|
|
DEBUGMSG ((DBG_NAUSEA, "System SourcePath: %s", eOpProp.Property));
|
|
|
|
pMigrateShellFolder (
|
|
NULL,
|
|
NULL,
|
|
TRUE,
|
|
_tcsinc(_tcschr (eOp.Path, '\\')),
|
|
eOpProp.Property,
|
|
NewDest,
|
|
OrigSrc,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
} while (EnumNextFileOpProperty (&eOpProp));
|
|
}
|
|
if (NewDest) {
|
|
FreePathString (NewDest);
|
|
NewDest = NULL;
|
|
}
|
|
if (OrigSrc) {
|
|
FreePathString (OrigSrc);
|
|
OrigSrc = NULL;
|
|
}
|
|
}
|
|
} while (EnumNextPathInOperation (&eOp));
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pWriteMyDocsHelpFile (
|
|
IN PCTSTR SubDir
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pWriteMyDocsHelpFile outputs a text file to the given path. This assists
|
|
the user in locating their documents, when the My Documents shell folder
|
|
goes to Shared Documents.
|
|
|
|
Arguments:
|
|
|
|
SubDir - Specifies the path to the subdir where the file should be written
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE file;
|
|
PCTSTR fileName;
|
|
PCTSTR msg;
|
|
DWORD bytesWritten;
|
|
PCTSTR path;
|
|
|
|
fileName = GetStringResource (MSG_EMPTY_MYDOCS_TITLE);
|
|
msg = GetStringResource (MSG_EMPTY_MYDOCS_TEXT);
|
|
path = JoinPaths (SubDir, fileName);
|
|
|
|
if (fileName && msg && path) {
|
|
//
|
|
// For uninstall, mark the file as create. Because of a bug, we have
|
|
// to treat this file as an OS file. What we really want to do is
|
|
// call:
|
|
//
|
|
// MarkFileForCreation (path);
|
|
//
|
|
// but this does not work. So we call MarkFileAsOsFile.
|
|
//
|
|
|
|
MarkFileAsOsFile (path); // allows uninstall to work properly
|
|
|
|
file = CreateFile (
|
|
path,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (file != INVALID_HANDLE_VALUE) {
|
|
#ifdef UNICODE
|
|
WriteFile (file, "\xff\xfe", 2, &bytesWritten, NULL);
|
|
#endif
|
|
WriteFile (file, msg, SizeOfString (msg), &bytesWritten, NULL);
|
|
CloseHandle (file);
|
|
}
|
|
}
|
|
|
|
FreeStringResource (msg);
|
|
FreeStringResource (fileName);
|
|
FreePathString (path);
|
|
}
|
|
|
|
|
|
VOID
|
|
pMigrateUserShellFolders (
|
|
IN PMIGRATE_USER_ENUM EnumPtr
|
|
)
|
|
{
|
|
FILEOP_ENUM eOp;
|
|
FILEOP_PROP_ENUM eOpProp;
|
|
PTSTR NewDest;
|
|
PTSTR OrigSrc;
|
|
TCHAR node[MEMDB_MAX];
|
|
MEMDB_ENUM e;
|
|
|
|
if (EnumFirstPathInOperation (&eOp, OPERATION_SHELL_FOLDER)) {
|
|
|
|
do {
|
|
MemDbBuildKey (node, EnumPtr->FixedUserName, TEXT("*"), NULL, NULL);
|
|
|
|
if (IsPatternMatch (node, eOp.Path)) {
|
|
|
|
NewDest = NULL;
|
|
OrigSrc = NULL;
|
|
|
|
if (EnumFirstFileOpProperty (&eOpProp, eOp.Sequencer, OPERATION_SHELL_FOLDER)) {
|
|
|
|
do {
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_DEST)) {
|
|
NewDest = DuplicatePathString (eOpProp.Property, 0);
|
|
}
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_ORIGINAL_SRC)) {
|
|
OrigSrc = DuplicatePathString (eOpProp.Property, 0);
|
|
}
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_SHELLFOLDERS_SRC)) {
|
|
|
|
MYASSERT (NewDest);
|
|
MYASSERT (OrigSrc);
|
|
|
|
DEBUGMSG ((DBG_NAUSEA, "Per-User SourcePath: %s", eOpProp.Property));
|
|
|
|
pMigrateShellFolder (
|
|
EnumPtr->Win9xUserName,
|
|
EnumPtr->FixedUserName,
|
|
FALSE,
|
|
_tcsinc(_tcschr (eOp.Path, '\\')),
|
|
eOpProp.Property,
|
|
NewDest,
|
|
OrigSrc,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
} while (EnumNextFileOpProperty (&eOpProp));
|
|
}
|
|
if (NewDest) {
|
|
FreePathString (NewDest);
|
|
NewDest = NULL;
|
|
}
|
|
if (OrigSrc) {
|
|
FreePathString (OrigSrc);
|
|
OrigSrc = NULL;
|
|
}
|
|
}
|
|
} while (EnumNextPathInOperation (&eOp));
|
|
}
|
|
|
|
if (EnumPtr->FixedUserName) {
|
|
MemDbBuildKey (
|
|
node,
|
|
MEMDB_CATEGORY_MYDOCS_WARNING,
|
|
EnumPtr->FixedUserName,
|
|
TEXT("*"),
|
|
NULL
|
|
);
|
|
|
|
if (MemDbEnumFirstValue (&e, node, MEMDB_ALL_SUBLEVELS, MEMDB_ENDPOINTS_ONLY)) {
|
|
do {
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Creating mydocs help file %s", e.szName));
|
|
|
|
pWriteMyDocsHelpFile (e.szName);
|
|
|
|
} while (MemDbEnumNextValue (&e));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
pCleanupDir (
|
|
IN PCTSTR Path,
|
|
IN BOOL CleanUpRoot
|
|
)
|
|
{
|
|
TREE_ENUM e;
|
|
DWORD oldAttributes;
|
|
|
|
if (EnumFirstFileInTreeEx (&e, Path, NULL, TRUE, TRUE, FILE_ENUM_ALL_LEVELS)) {
|
|
|
|
do {
|
|
|
|
if (e.Directory) {
|
|
//
|
|
// This is a dir. Let's see if we enter another shell folder
|
|
//
|
|
|
|
if (((g_SystemSfList) && (pSetupStringTableLookUpString (g_SystemSfList, (PVOID) e.FullPath, STRTAB_CASE_INSENSITIVE) != -1)) ||
|
|
((g_UserSfList) && (pSetupStringTableLookUpString (g_UserSfList, (PVOID) e.FullPath, STRTAB_CASE_INSENSITIVE) != -1)) ||
|
|
(IsDirectoryMarkedAsEmpty (e.FullPath))
|
|
) {
|
|
//
|
|
// we are just getting into another shell folder. Let's skip it
|
|
//
|
|
AbortEnumCurrentDir (&e);
|
|
}
|
|
else {
|
|
SetLongPathAttributes (e.FullPath, FILE_ATTRIBUTE_NORMAL);
|
|
|
|
if (!RemoveLongDirectoryPath (e.FullPath)) {
|
|
SetLongPathAttributes (e.FullPath, e.FindData->dwFileAttributes);
|
|
}
|
|
}
|
|
}
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
AbortEnumFileInTree (&e);
|
|
|
|
if (CleanUpRoot) {
|
|
|
|
oldAttributes = GetLongPathAttributes (Path);
|
|
|
|
SetLongPathAttributes (Path, FILE_ATTRIBUTE_NORMAL);
|
|
|
|
if (!RemoveLongDirectoryPath (Path)) {
|
|
SetLongPathAttributes (Path, oldAttributes);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
INT
|
|
pGetCsidlFromTag (
|
|
IN PCTSTR ShellFolderIdentifier
|
|
)
|
|
{
|
|
PCSIDLMAP map;
|
|
|
|
for (map = g_CsidlMap ; map->Tag ; map++) {
|
|
if (StringIMatch (map->Tag, ShellFolderIdentifier)) {
|
|
return map->CsidlValue;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
INT
|
|
CALLBACK
|
|
pSfCopyCallback (
|
|
PCTSTR FullFileSpec,
|
|
PCTSTR DestSpec,
|
|
WIN32_FIND_DATA *FindData,
|
|
DWORD EnumTreeID,
|
|
PVOID Param,
|
|
PDWORD CurrentDirData
|
|
)
|
|
{
|
|
//
|
|
// Put this file in the cleanout category, so that it gets removed unless
|
|
// it has been backed up.
|
|
//
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_CLEAN_OUT,
|
|
DestSpec,
|
|
NULL,
|
|
NULL,
|
|
BACKUP_FILE,
|
|
NULL
|
|
);
|
|
|
|
return CALLBACK_CONTINUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pCreateSfWithApi (
|
|
IN PCTSTR ShellFolderIdentifier,
|
|
IN PCTSTR FolderToCreate
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
INT csidl;
|
|
TCHAR folderPath[MAX_PATH];
|
|
BOOL destroy = FALSE;
|
|
BOOL result = TRUE;
|
|
DWORD attribs;
|
|
|
|
//
|
|
// Convert the tag to a CSIDL constant
|
|
//
|
|
|
|
csidl = pGetCsidlFromTag (ShellFolderIdentifier);
|
|
if (csidl < 0) {
|
|
DEBUGMSG ((DBG_VERBOSE, "CSIDL ID for %s not known", ShellFolderIdentifier));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Query the shell for an existing shell folder
|
|
//
|
|
|
|
hr = SHGetFolderPath (NULL, csidl, NULL, SHGFP_TYPE_CURRENT, folderPath);
|
|
|
|
if (hr != S_OK && hr != S_FALSE) {
|
|
DEBUGMSG ((DBG_WARNING, "Can't get shell folder path for ID %s", ShellFolderIdentifier));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Get the attributes of the existing shell folder
|
|
//
|
|
|
|
if (hr == S_OK) {
|
|
DEBUGMSG ((DBG_VERBOSE, "Shell folder %s already exists at %s", ShellFolderIdentifier, folderPath));
|
|
attribs = GetLongPathAttributes (folderPath);
|
|
} else {
|
|
attribs = INVALID_ATTRIBUTES;
|
|
}
|
|
|
|
//
|
|
// If existing shell folder is not present, create it temporarily
|
|
//
|
|
|
|
if (attribs == INVALID_ATTRIBUTES) {
|
|
DEBUGMSG ((DBG_VERBOSE, "Shell folder %s needs to be created", ShellFolderIdentifier));
|
|
destroy = TRUE;
|
|
|
|
hr = SHGetFolderPath (
|
|
NULL,
|
|
csidl | CSIDL_FLAG_CREATE,
|
|
NULL,
|
|
SHGFP_TYPE_CURRENT,
|
|
folderPath
|
|
);
|
|
|
|
if (hr != S_OK) {
|
|
LOG ((LOG_ERROR, "Can't create shell folder path for ID %s", ShellFolderIdentifier));
|
|
return FALSE;
|
|
}
|
|
|
|
attribs = GetLongPathAttributes (folderPath);
|
|
|
|
if (attribs == INVALID_ATTRIBUTES) {
|
|
LOG ((LOG_ERROR, "Can't get attributes of %s for ID %s", folderPath, ShellFolderIdentifier));
|
|
result = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// On success (either existing sf or we created it), make a copy of the whole folder
|
|
//
|
|
|
|
if (result) {
|
|
MakeSurePathExists (FolderToCreate, TRUE);
|
|
attribs = GetLongPathAttributes (folderPath);
|
|
if (attribs != INVALID_ATTRIBUTES) {
|
|
SetLongPathAttributes (FolderToCreate, attribs);
|
|
}
|
|
|
|
CopyTree (
|
|
folderPath,
|
|
FolderToCreate,
|
|
0, // no EnumTree ID
|
|
COPYTREE_DOCOPY | COPYTREE_NOOVERWRITE,
|
|
ENUM_ALL_LEVELS,
|
|
FILTER_ALL,
|
|
NULL, // no exclude.inf struct
|
|
pSfCopyCallback,
|
|
NULL // no error callback
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we created the sf, we must destroy it to return the system back
|
|
// to its original state. We punt the case where power goes out and
|
|
// GUI mode restarts.
|
|
//
|
|
|
|
if (destroy) {
|
|
RemoveCompleteDirectory (folderPath);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pMigrateShellFolder (
|
|
IN PCTSTR Win9xUser, OPTIONAL
|
|
IN PCTSTR FixedUserName, OPTIONAL
|
|
IN BOOL SystemShellFolder,
|
|
IN PCTSTR ShellFolderIdentifier,
|
|
IN PCTSTR SourcePath,
|
|
IN PCTSTR OrgDestinationPath,
|
|
IN PCTSTR OrigSourcePath,
|
|
IN DWORD UserFlags,
|
|
IN PMIGRATE_USER_ENUM EnumPtr
|
|
)
|
|
{
|
|
TREE_ENUM e;
|
|
PSHELL_FOLDER_FILTER Filter;
|
|
TCHAR DefaultShellFolder[MAX_TCHAR_PATH];
|
|
PCTSTR DestPath = NULL;
|
|
PROFILE_MERGE_DATA Data;
|
|
BOOL Result = FALSE;
|
|
TCHAR UserRoot[MAX_TCHAR_PATH];
|
|
PCTSTR NtDefaultLocation = NULL;
|
|
PCTSTR DefaultUserLocation = NULL;
|
|
PCTSTR tempExpand = NULL;
|
|
PCTSTR nextExpand;
|
|
TCHAR ShellFolderPath[MAX_TCHAR_PATH];
|
|
DWORD Offset;
|
|
DWORD Size;
|
|
HKEY Key;
|
|
DWORD Attributes;
|
|
PCTSTR ValData = NULL;
|
|
PTSTR p;
|
|
DWORD d;
|
|
HKEY UserHiveRoot;
|
|
LONG rc;
|
|
PCTSTR EncodedKey;
|
|
PCTSTR NewDestPath;
|
|
BOOL AlreadyMoved;
|
|
PCTSTR OrigFullPath;
|
|
BOOL regFolder;
|
|
PCTSTR freeMe;
|
|
TCHAR driveLetter[] = TEXT("?:");
|
|
BOOL allUsers;
|
|
BOOL keep;
|
|
PCWSTR OrigRootPath, DestRootPath;
|
|
PBYTE bufferRoot;
|
|
PTSTR destPathBuffer;
|
|
DWORD fileStatus;
|
|
|
|
__try {
|
|
|
|
bufferRoot = MemAllocUninit (MEMDB_MAX * sizeof (TCHAR));
|
|
if (!bufferRoot) {
|
|
__leave;
|
|
}
|
|
|
|
destPathBuffer = (PTSTR) bufferRoot;
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Entering shell folder %s", ShellFolderIdentifier));
|
|
|
|
regFolder = TRUE;
|
|
|
|
if (StringIMatch (ShellFolderIdentifier, S_SF_PROFILES)) {
|
|
regFolder = FALSE;
|
|
}
|
|
if (StringIMatch (ShellFolderIdentifier, S_SF_COMMON_PROFILES)) {
|
|
regFolder = FALSE;
|
|
}
|
|
|
|
//
|
|
// Get root default folder
|
|
//
|
|
|
|
Size = sizeof (DefaultShellFolder);
|
|
|
|
if (!GetDefaultUserProfileDirectory (DefaultShellFolder, &Size)) {
|
|
MYASSERT (FALSE);
|
|
__leave;
|
|
}
|
|
|
|
if (regFolder) {
|
|
//
|
|
// Get ShellFolderPath (with environment variables in it)
|
|
//
|
|
|
|
if (SystemShellFolder) {
|
|
UserHiveRoot = HKEY_LOCAL_MACHINE;
|
|
} else {
|
|
UserHiveRoot = g_DefaultHiveRoot;
|
|
}
|
|
|
|
Key = OpenRegKey (UserHiveRoot, S_USER_SHELL_FOLDERS_KEY);
|
|
|
|
if (Key) {
|
|
ValData = GetRegValueString (Key, ShellFolderIdentifier);
|
|
DEBUGMSG_IF ((!ValData, DBG_WARNING, "Can't get NT default for %s from registry", ShellFolderIdentifier));
|
|
|
|
CloseRegKey (Key);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_ERROR, "Can't open %s", S_USER_SHELL_FOLDERS_KEY));
|
|
|
|
if (ValData) {
|
|
StringCopy (ShellFolderPath, ValData);
|
|
MemFree (g_hHeap, 0, ValData);
|
|
ValData = NULL;
|
|
} else {
|
|
wsprintf (ShellFolderPath, TEXT("%s\\%s"), S_USERPROFILE_ENV, ShellFolderIdentifier);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the user's profile root
|
|
//
|
|
|
|
if (FixedUserName) {
|
|
|
|
if (!GetUserProfilePath (FixedUserName, &p)) {
|
|
MYASSERT (FALSE);
|
|
__leave;
|
|
}
|
|
|
|
StringCopy (UserRoot, p);
|
|
allUsers = FALSE;
|
|
|
|
FreePathString (p);
|
|
|
|
} else {
|
|
|
|
Size = sizeof (UserRoot);
|
|
|
|
if (regFolder) {
|
|
if (!GetAllUsersProfileDirectory (UserRoot, &Size)) {
|
|
MYASSERT (FALSE);
|
|
__leave;
|
|
}
|
|
|
|
allUsers = TRUE;
|
|
|
|
} else {
|
|
if (!GetProfilesDirectory (UserRoot, &Size)) {
|
|
MYASSERT (FALSE);
|
|
__leave;
|
|
}
|
|
|
|
allUsers = FALSE;
|
|
}
|
|
}
|
|
|
|
if (regFolder) {
|
|
//
|
|
// Compute the default NT location and the Default User location
|
|
//
|
|
|
|
tempExpand = StringSearchAndReplace (
|
|
ShellFolderPath,
|
|
S_USERPROFILE_ENV,
|
|
UserRoot
|
|
);
|
|
|
|
if (!tempExpand) {
|
|
tempExpand = DuplicatePathString (ShellFolderPath, 0);
|
|
}
|
|
} else {
|
|
tempExpand = DuplicatePathString (UserRoot, 0);
|
|
}
|
|
|
|
NtDefaultLocation = ExpandEnvironmentText (tempExpand);
|
|
|
|
FreePathString (tempExpand);
|
|
|
|
if (regFolder) {
|
|
tempExpand = StringSearchAndReplace (
|
|
ShellFolderPath,
|
|
S_USERPROFILE_ENV,
|
|
DefaultShellFolder
|
|
);
|
|
|
|
if (!tempExpand) {
|
|
tempExpand = DuplicatePathString (ShellFolderPath, 0);
|
|
}
|
|
} else {
|
|
tempExpand = StringSearchAndReplace (
|
|
UserRoot,
|
|
S_USERPROFILE_ENV,
|
|
DefaultShellFolder
|
|
);
|
|
|
|
if (!tempExpand) {
|
|
tempExpand = DuplicatePathString (UserRoot, 0);
|
|
}
|
|
}
|
|
|
|
DefaultUserLocation = ExpandEnvironmentText (tempExpand);
|
|
|
|
FreePathString (tempExpand);
|
|
|
|
//
|
|
// Init the filter data struct
|
|
//
|
|
|
|
ZeroMemory (&Data, sizeof (Data));
|
|
|
|
Data.Win9xUser = Win9xUser;
|
|
Data.FixedUserName = FixedUserName;
|
|
Data.UserHiveRoot = UserHiveRoot;
|
|
Data.ShellFolderIdentifier = ShellFolderIdentifier;
|
|
Data.DefaultShellFolder = DefaultUserLocation;
|
|
Data.UserDefaultLocation = NtDefaultLocation;
|
|
Data.UserFlags = UserFlags;
|
|
Data.Context = INITIALIZE;
|
|
StringCopyByteCount (Data.TempSourcePath, SourcePath, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, OrgDestinationPath, sizeof (Data.DestinationPath));
|
|
Data.SrcRootPath = SourcePath;
|
|
Data.DestRootPath = OrgDestinationPath;
|
|
Data.OrigRootPath = OrigSourcePath;
|
|
Data.EnumPtr = EnumPtr;
|
|
Data.Attributes = GetLongPathAttributes (OrgDestinationPath);
|
|
|
|
//
|
|
// Establish the shell folder using the shell APIs
|
|
//
|
|
|
|
if (pCreateSfWithApi (
|
|
ShellFolderIdentifier,
|
|
OrgDestinationPath
|
|
)) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_VERBOSE,
|
|
"Using API defaults for shell folder %s",
|
|
ShellFolderIdentifier
|
|
));
|
|
|
|
Data.Attributes = GetLongPathAttributes (OrgDestinationPath);
|
|
}
|
|
|
|
if (Data.Attributes == INVALID_ATTRIBUTES) {
|
|
//
|
|
// We don't care about this shell folder's desktop.ini or
|
|
// attributes -- use the NT default attributes, or the
|
|
// Win9x attributes if there is no default.
|
|
//
|
|
|
|
Data.Attributes = GetLongPathAttributes (NtDefaultLocation);
|
|
|
|
if (Data.Attributes == INVALID_ATTRIBUTES) {
|
|
Data.Attributes = GetLongPathAttributes (Data.TempSourcePath);
|
|
}
|
|
|
|
if (Data.Attributes == INVALID_ATTRIBUTES) {
|
|
//
|
|
// This happens for shell folders like My Music & My Video
|
|
// which don't exist on Win9x
|
|
//
|
|
Data.Attributes = FILE_ATTRIBUTE_READONLY;
|
|
}
|
|
|
|
MakeSureLongPathExists (OrgDestinationPath, TRUE);
|
|
SetLongPathAttributes (OrgDestinationPath, Data.Attributes);
|
|
|
|
DEBUGMSG ((
|
|
DBG_VERBOSE,
|
|
"Using previous OS desktop.ini for shell folder %s, attribs=%08X",
|
|
ShellFolderIdentifier,
|
|
Data.Attributes
|
|
));
|
|
|
|
}
|
|
|
|
//
|
|
// Now add string mappings for this shell folder. The reason for doing
|
|
// this is that we want to catch the case of paths to non-existent files
|
|
// within shell stored in the registry.
|
|
//
|
|
|
|
OrigRootPath = JoinPaths (Data.OrigRootPath, TEXT(""));
|
|
DestRootPath = JoinPaths (Data.DestRootPath, TEXT(""));
|
|
AddStringMappingPair (g_SubStringMap, OrigRootPath, DestRootPath);
|
|
FreePathString (DestRootPath);
|
|
FreePathString (OrigRootPath);
|
|
|
|
//
|
|
// PHASE ONE - move the files from 9x shell folder to their NT locations
|
|
//
|
|
|
|
//
|
|
// Call filters for init
|
|
//
|
|
|
|
for (Filter = g_Filters_9xNt ; Filter->Fn ; Filter++) {
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: INIT: %s (enter)", Filter->Name));
|
|
|
|
Data.State = 0;
|
|
Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: INIT: %s (done)", Filter->Name));
|
|
}
|
|
|
|
//
|
|
// Enumerate the shell folder and move it to the destination
|
|
//
|
|
|
|
DEBUGMSG ((DBG_SHELL, "9X->NT: Enumerating %s", SourcePath));
|
|
|
|
if (EnumFirstFileInTree (&e, SourcePath, NULL, FALSE)) {
|
|
|
|
do {
|
|
//
|
|
// Update the filter data struct
|
|
//
|
|
|
|
OrigFullPath = JoinPaths (OrigSourcePath, e.SubPath);
|
|
fileStatus = GetFileInfoOnNt (OrigFullPath, destPathBuffer, MEMDB_MAX);
|
|
DestPath = destPathBuffer;
|
|
|
|
if (fileStatus == FILESTATUS_UNCHANGED) {
|
|
//
|
|
// No reason not to move this file too
|
|
//
|
|
|
|
MYASSERT (StringIMatch (destPathBuffer, OrigFullPath));
|
|
|
|
DestPath = JoinPaths (Data.DestRootPath, e.SubPath);
|
|
|
|
if (!StringIMatch (OrigFullPath, DestPath)) {
|
|
MarkFileForMoveExternal (OrigFullPath, DestPath);
|
|
}
|
|
}
|
|
|
|
Data.Attributes = e.FindData->dwFileAttributes;
|
|
StringCopyByteCount (Data.TempSourcePath, e.FullPath, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, DestPath, sizeof (Data.DestinationPath));
|
|
Data.Context = PROCESS_PATH;
|
|
|
|
DEBUGMSG ((DBG_SHELL, "9X->NT: Original temp source path: %s", Data.TempSourcePath));
|
|
|
|
//
|
|
// Allow filters to change source or dest, or to skip copy
|
|
//
|
|
|
|
keep = TRUE;
|
|
|
|
for (Filter = g_Filters_9xNt ; Filter->Fn ; Filter++) {
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: FILTER: %s (enter)", Filter->Name));
|
|
|
|
Data.State = Filter->State;
|
|
d = Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: FILTER: %s (result=%u)", Filter->Name, d));
|
|
|
|
// ignore SHELLFILTER_ERROR & try to complete processing
|
|
|
|
if (d == SHELLFILTER_FORCE_CHANGE) {
|
|
DEBUGMSG ((DBG_SHELL, "9X->NT: Skipping additional filters because shell folder filter %hs said so", Filter->Name));
|
|
break;
|
|
}
|
|
|
|
if (d == SHELLFILTER_SKIP_FILE) {
|
|
DEBUGMSG ((DBG_SHELL, "9X->NT:Skipping %s because shell folder filter %hs said so", DestPath, Filter->Name));
|
|
keep = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (d == SHELLFILTER_SKIP_DIRECTORY) {
|
|
AbortEnumCurrentDir (&e);
|
|
keep = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keep && !(Data.Attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
//
|
|
// Is source different from the dest?
|
|
//
|
|
|
|
if (!StringIMatch (Data.TempSourcePath, Data.DestinationPath)) {
|
|
|
|
//
|
|
// Make sure dest exists
|
|
//
|
|
|
|
MakeSureLongPathExists (Data.DestinationPath, FALSE); // FALSE == not path only
|
|
|
|
//
|
|
// Move or copy the file.
|
|
//
|
|
pQueueSfMove (Data.TempSourcePath, Data.DestinationPath);
|
|
}
|
|
|
|
} else if (keep) {
|
|
|
|
MakeSureLongPathExists (Data.DestinationPath, TRUE); // TRUE == path only
|
|
SetLongPathAttributes (Data.DestinationPath, Data.Attributes);
|
|
|
|
} else if (d == SHELLFILTER_SKIP_FILE) {
|
|
//
|
|
// Mark this file for deletion if it won't be moved from temp to dest
|
|
//
|
|
if (!StringIMatch (Data.TempSourcePath, Data.DestinationPath)) {
|
|
DEBUGMSG ((DBG_SHELL, "Deleting shell folder file %s", e.FullPath));
|
|
ForceOperationOnPath (e.FullPath, OPERATION_CLEANUP);
|
|
}
|
|
}
|
|
|
|
if (DestPath && DestPath != destPathBuffer) {
|
|
FreePathString (DestPath);
|
|
}
|
|
|
|
DestPath = NULL;
|
|
FreePathString (OrigFullPath);
|
|
OrigFullPath = NULL;
|
|
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
|
|
pFlushSfQueue();
|
|
|
|
//
|
|
// Call filters one last time
|
|
//
|
|
|
|
Data.Attributes = 0;
|
|
Data.Context = TERMINATE;
|
|
StringCopyByteCount (Data.TempSourcePath, SourcePath, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, OrgDestinationPath, sizeof (Data.DestinationPath));
|
|
|
|
for (Filter = g_Filters_9xNt ; Filter->Fn ; Filter++) {
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: TERMINATE: %s (enter)", Filter->Name));
|
|
|
|
Data.State = Filter->State;
|
|
Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "9X->NT: TERMINATE: %s (done)", Filter->Name));
|
|
}
|
|
|
|
//
|
|
// Now cleanup this directory for all empty dirs (excluding the root)
|
|
// Do not cleanup non reg folders!!
|
|
//
|
|
if (regFolder) {
|
|
DEBUGMSG ((DBG_NAUSEA, "Cleaning up %s", Data.DestinationPath));
|
|
pCleanupDir (Data.DestinationPath, FALSE);
|
|
}
|
|
|
|
//
|
|
// PHASE TWO - if necessary, merge files from NT default shell folder
|
|
// to the new location and update the registry
|
|
//
|
|
|
|
if (regFolder) {
|
|
|
|
//
|
|
// Encode string with %USERPROFILE%/%ALLUSERSPROFILE%, %SYSTEMROOT%
|
|
// or %SYSTEMDRIVE% if possible
|
|
//
|
|
|
|
// %USERPROFILE% or %ALLUSERSPROFILE%
|
|
tempExpand = OrgDestinationPath;
|
|
|
|
if (allUsers) {
|
|
nextExpand = StringSearchAndReplace (
|
|
tempExpand,
|
|
UserRoot,
|
|
S_ALLUSERSPROFILE_ENV
|
|
);
|
|
} else {
|
|
nextExpand = StringSearchAndReplace (
|
|
tempExpand,
|
|
UserRoot,
|
|
S_USERPROFILE_ENV
|
|
);
|
|
}
|
|
|
|
if (nextExpand) {
|
|
tempExpand = nextExpand;
|
|
}
|
|
|
|
// %SYSTEMROOT%
|
|
nextExpand = StringSearchAndReplace (
|
|
tempExpand,
|
|
g_WinDir,
|
|
S_SYSTEMROOT_ENV
|
|
);
|
|
|
|
if (nextExpand) {
|
|
if (tempExpand != OrgDestinationPath) {
|
|
FreePathString (tempExpand);
|
|
}
|
|
|
|
tempExpand = nextExpand;
|
|
}
|
|
|
|
// %SYSTEMDRIVE%
|
|
driveLetter[0] = g_WinDir[0];
|
|
|
|
nextExpand = StringSearchAndReplace (
|
|
tempExpand,
|
|
driveLetter,
|
|
S_SYSTEMDRIVE_ENV
|
|
);
|
|
|
|
if (nextExpand) {
|
|
if (tempExpand != OrgDestinationPath) {
|
|
FreePathString (tempExpand);
|
|
}
|
|
|
|
tempExpand = nextExpand;
|
|
}
|
|
|
|
// tempExpand points to OrgDestinationPath or a expanded path from the path pool
|
|
MYASSERT (tempExpand);
|
|
|
|
//
|
|
// Now store it. If HKLM, put it in the registry. Otherwise, put it
|
|
// in memdb, which will later be put in the user's hive.
|
|
//
|
|
|
|
if (Data.UserHiveRoot == HKEY_LOCAL_MACHINE) {
|
|
|
|
//
|
|
// Update the registry, User Shell Folder must point to original
|
|
// location
|
|
//
|
|
|
|
Key = OpenRegKey (Data.UserHiveRoot, S_USER_SHELL_FOLDERS_KEY);
|
|
|
|
if (Key) {
|
|
rc = RegSetValueEx (
|
|
Key,
|
|
Data.ShellFolderIdentifier,
|
|
0,
|
|
REG_EXPAND_SZ,
|
|
(PBYTE) tempExpand,
|
|
SizeOfString (tempExpand)
|
|
);
|
|
|
|
DEBUGMSG_IF ((
|
|
rc != ERROR_SUCCESS,
|
|
DBG_ERROR,
|
|
"Can't save %s for %s",
|
|
tempExpand,
|
|
Data.ShellFolderIdentifier
|
|
));
|
|
|
|
DEBUGMSG_IF ((
|
|
rc == ERROR_SUCCESS,
|
|
DBG_SHELL,
|
|
"Win9x shell location preserved: %s (%s)",
|
|
tempExpand,
|
|
Data.ShellFolderIdentifier
|
|
));
|
|
|
|
CloseRegKey (Key);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_ERROR, "Can't open %s", S_USER_SHELL_FOLDERS_KEY));
|
|
|
|
} else {
|
|
|
|
EncodedKey = CreateEncodedRegistryStringEx (
|
|
S_USER_SHELL_FOLDERS_KEY,
|
|
Data.ShellFolderIdentifier,
|
|
FALSE
|
|
);
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_USER_REGISTRY_VALUE,
|
|
tempExpand,
|
|
NULL,
|
|
NULL,
|
|
REG_EXPAND_SZ,
|
|
&Offset
|
|
);
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_SET_USER_REGISTRY,
|
|
Data.FixedUserName,
|
|
EncodedKey,
|
|
NULL,
|
|
Offset,
|
|
NULL
|
|
);
|
|
|
|
FreeEncodedRegistryString (EncodedKey);
|
|
}
|
|
|
|
if (tempExpand != OrgDestinationPath) {
|
|
FreePathString (tempExpand);
|
|
}
|
|
|
|
}
|
|
|
|
if (!StringIMatch (OrgDestinationPath, NtDefaultLocation)) {
|
|
//
|
|
// Now move from the NT default location into the preserved location
|
|
//
|
|
|
|
//
|
|
// Fix the Data structure
|
|
//
|
|
|
|
Data.UserFlags = UserFlags;
|
|
Data.Context = INITIALIZE;
|
|
StringCopyByteCount (Data.TempSourcePath, NtDefaultLocation, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, OrgDestinationPath, sizeof (Data.DestinationPath));
|
|
Data.SrcRootPath = NtDefaultLocation;
|
|
Data.DestRootPath = OrgDestinationPath;
|
|
Data.OrigRootPath = OrigSourcePath;
|
|
|
|
//
|
|
// Now check to see if we already moved something into the preserved directory.
|
|
// If we did, we will not make the move (we will only delete the default files).
|
|
//
|
|
if (g_Merged9xFolders && (pSetupStringTableLookUpString (g_Merged9xFolders, (PTSTR)Data.DestRootPath, 0) != -1)) {
|
|
AlreadyMoved = TRUE;
|
|
}
|
|
else {
|
|
AlreadyMoved = FALSE;
|
|
pSetupStringTableAddString (g_Merged9xFolders, (PVOID) Data.DestRootPath, STRTAB_CASE_INSENSITIVE);
|
|
}
|
|
|
|
//
|
|
// Call filters for init
|
|
//
|
|
|
|
for (Filter = g_Filters_Nt9x ; Filter->Fn ; Filter++) {
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: INIT: %s (enter)", Filter->Name));
|
|
|
|
Data.State = 0;
|
|
Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: INIT: %s (done)", Filter->Name));
|
|
}
|
|
|
|
DEBUGMSG ((DBG_SHELL, "NT->9X: Enumerating %s", Data.TempSourcePath));
|
|
|
|
MYASSERT (Data.TempSourcePath && *Data.TempSourcePath);
|
|
if (EnumFirstFileInTree (&e, Data.TempSourcePath, NULL, FALSE)) {
|
|
|
|
do {
|
|
|
|
//
|
|
// This is only needed for user shell folders but does not hurt.
|
|
//
|
|
|
|
if (StringIMatch (TEXT("ntuser.dat"), e.Name)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// start with the assumption that the dest file is under the original
|
|
// destination path
|
|
//
|
|
|
|
NewDestPath = JoinPaths (OrgDestinationPath, e.SubPath);
|
|
|
|
//
|
|
// If this is desktop.ini, merge it with the existing one
|
|
//
|
|
|
|
if (StringIMatch (TEXT("desktop.ini"), e.Name)) {
|
|
DEBUGMSG ((
|
|
DBG_VERBOSE,
|
|
"Merging clean install %s with the one in Default User",
|
|
e.FullPath
|
|
));
|
|
MergeIniFile (NewDestPath, e.FullPath, FALSE);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Not the root shell folder desktop.ini -- continue processing
|
|
//
|
|
|
|
Data.Attributes = e.FindData->dwFileAttributes;
|
|
StringCopyByteCount (Data.TempSourcePath, e.FullPath, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, NewDestPath, sizeof (Data.DestinationPath));
|
|
Data.Context = PROCESS_PATH;
|
|
|
|
DEBUGMSG ((DBG_SHELL, "NT->9X: Original temp source path: %s", Data.TempSourcePath));
|
|
|
|
//
|
|
// if we only need to delete the default files, skip the filters
|
|
//
|
|
|
|
if (AlreadyMoved) {
|
|
|
|
SetLongPathAttributes (Data.TempSourcePath, FILE_ATTRIBUTE_NORMAL);
|
|
if (!DeleteLongPath (Data.TempSourcePath)) {
|
|
SetLongPathAttributes (Data.TempSourcePath, Data.Attributes);
|
|
DEBUGMSG ((DBG_WARNING, "%s could not be removed.", Data.TempSourcePath));
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Allow filters to change source or dest, or to skip copy
|
|
//
|
|
|
|
keep = TRUE;
|
|
|
|
for (Filter = g_Filters_Nt9x ; Filter->Fn ; Filter++) {
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: FILTER: %s (enter)", Filter->Name));
|
|
|
|
Data.State = Filter->State;
|
|
d = Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: FILTER: %s (result=%u)", Filter->Name, d));
|
|
|
|
if (d == SHELLFILTER_FORCE_CHANGE) {
|
|
break;
|
|
}
|
|
|
|
if (d == SHELLFILTER_SKIP_FILE) {
|
|
keep = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (d == SHELLFILTER_SKIP_DIRECTORY) {
|
|
AbortEnumCurrentDir (&e);
|
|
keep = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keep) {
|
|
|
|
if (!(e.FindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
pQueueSfMove (Data.TempSourcePath, Data.DestinationPath);
|
|
}
|
|
else {
|
|
|
|
MakeSureLongPathExists (Data.DestinationPath, TRUE); // TRUE == path only
|
|
SetLongPathAttributes (Data.DestinationPath, Data.Attributes);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
FreePathString (NewDestPath);
|
|
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
|
|
pFlushSfQueue();
|
|
|
|
//
|
|
// Call filters one last time
|
|
//
|
|
|
|
Data.Attributes = 0;
|
|
Data.Context = TERMINATE;
|
|
StringCopyByteCount (Data.TempSourcePath, NtDefaultLocation, sizeof (Data.TempSourcePath));
|
|
StringCopyByteCount (Data.DestinationPath, OrgDestinationPath, sizeof (Data.DestinationPath));
|
|
|
|
for (Filter = g_Filters_Nt9x ; Filter->Fn ; Filter++) {
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: TERMINATE: %s (enter)", Filter->Name));
|
|
|
|
Data.State = Filter->State;
|
|
Filter->Fn (&Data);
|
|
Filter->State = Data.State;
|
|
|
|
//DEBUGMSGA ((DBG_SHELL, "NT->9X: TERMINATE: %s (done)", Filter->Name));
|
|
}
|
|
|
|
//
|
|
// Now cleanup this directory for all empty dirs (including the root)
|
|
// Do not cleanup non reg folders!!
|
|
//
|
|
if (regFolder) {
|
|
DEBUGMSG ((DBG_NAUSEA, "Cleaning up %s (including root)", Data.TempSourcePath));
|
|
pCleanupDir (Data.TempSourcePath, TRUE);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Loop through the whole tree and add desktop.ini to cleanup
|
|
//
|
|
|
|
if (EnumFirstFileInTree (&e, OrgDestinationPath, NULL, FALSE)) {
|
|
do {
|
|
if (!e.Directory) {
|
|
continue;
|
|
}
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_CLEAN_OUT,
|
|
e.FullPath,
|
|
TEXT("desktop.ini"),
|
|
NULL,
|
|
BACKUP_FILE,
|
|
NULL
|
|
);
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
|
|
Result = TRUE;
|
|
|
|
}
|
|
__finally {
|
|
PushError();
|
|
AbortEnumFileInTree (&e);
|
|
FreeText (NtDefaultLocation);
|
|
FreeText (DefaultUserLocation);
|
|
|
|
if (bufferRoot) {
|
|
FreeMem (bufferRoot);
|
|
}
|
|
|
|
PopError();
|
|
}
|
|
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Leaving shell folder %s with result %s",
|
|
ShellFolderIdentifier,
|
|
Result ? TEXT("TRUE") : TEXT("FALSE")
|
|
));
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
HKEY
|
|
pLoadDefaultUserHive (
|
|
VOID
|
|
)
|
|
{
|
|
DWORD Size;
|
|
BOOL b;
|
|
LONG rc;
|
|
|
|
if (!g_DefaultHiveMapped) {
|
|
|
|
if (!g_DefaultHivePath[0]) {
|
|
Size = sizeof (g_DefaultHivePath);
|
|
b = GetDefaultUserProfileDirectory (g_DefaultHivePath, &Size);
|
|
MYASSERT (b);
|
|
|
|
if (!b) {
|
|
wsprintf (g_DefaultHivePath, TEXT("%s\\profiles\\default user"), g_WinDir);
|
|
}
|
|
|
|
StringCopy (AppendWack (g_DefaultHivePath), TEXT("ntuser.dat"));
|
|
}
|
|
|
|
rc = RegLoadKey (HKEY_USERS, S_DEFAULT_USER, g_DefaultHivePath);
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
DEBUGMSG ((DBG_ERROR, "Can't load default user hive from %s", g_DefaultHivePath));
|
|
g_DefaultHiveRoot = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
g_DefaultHiveRoot = OpenRegKey (HKEY_USERS, S_DEFAULT_USER);
|
|
|
|
if (!g_DefaultHiveRoot) {
|
|
DEBUGMSG ((DBG_WHOOPS, "Loaded hive %s but could not open it", g_DefaultHivePath));
|
|
}
|
|
}
|
|
|
|
g_DefaultHiveMapped++;
|
|
|
|
return g_DefaultHiveRoot;
|
|
}
|
|
|
|
|
|
VOID
|
|
pUnloadDefaultUserHive (
|
|
VOID
|
|
)
|
|
{
|
|
if (!g_DefaultHiveMapped) {
|
|
return;
|
|
}
|
|
|
|
g_DefaultHiveMapped--;
|
|
|
|
if (!g_DefaultHiveMapped) {
|
|
CloseRegKey (g_DefaultHiveRoot);
|
|
RegUnLoadKey (HKEY_USERS, S_DEFAULT_USER);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pLoadIgnoredCollisions (
|
|
VOID
|
|
)
|
|
{
|
|
INFCONTEXT context;
|
|
TCHAR sfId[MEMDB_MAX];
|
|
TCHAR file[MEMDB_MAX];
|
|
INT value;
|
|
|
|
MYASSERT (g_WkstaMigInf);
|
|
|
|
if (SetupFindFirstLine (g_WkstaMigInf, S_IGNORED_COLLISIONS, NULL, &context)) {
|
|
|
|
do {
|
|
if (SetupGetStringField (&context, 1, sfId, MEMDB_MAX, NULL) &&
|
|
SetupGetStringField (&context, 2, file, MEMDB_MAX, NULL) &&
|
|
SetupGetIntField (&context, 3, &value)
|
|
) {
|
|
MemDbSetValueEx (MEMDB_CATEGORY_IGNORED_COLLISIONS, sfId, file, NULL, value, NULL);
|
|
}
|
|
} while (SetupFindNextLine (&context, &context));
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
MigrateShellFolders (
|
|
IN DWORD Request
|
|
)
|
|
{
|
|
MIGRATE_USER_ENUM e;
|
|
|
|
if (Request == REQUEST_QUERYTICKS) {
|
|
return TICKS_SYSTEM_SHELL_MIGRATION;
|
|
} else if (Request != REQUEST_RUN) {
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
pPrepareSfRestartability();
|
|
|
|
pLoadIgnoredCollisions ();
|
|
|
|
g_SystemSfList = pCreateSystemSfList ();
|
|
g_UserSfList = pCreateUserSfList (NULL);
|
|
|
|
pCreateLinksList ();
|
|
|
|
pCreateLinksRenameList ();
|
|
|
|
pDestroySfList (g_UserSfList);
|
|
|
|
pLoadDefaultUserHive();
|
|
|
|
g_Merged9xFolders = pSetupStringTableInitialize();
|
|
|
|
pMigrateSystemShellFolders();
|
|
|
|
if (EnumFirstUserToMigrate (&e, ENUM_NO_FLAGS)) {
|
|
|
|
do {
|
|
|
|
if (!e.CreateOnly && e.AccountType != DEFAULT_USER_ACCOUNT) {
|
|
pMigrateUserShellFolders (&e);
|
|
}
|
|
|
|
} while (EnumNextUserToMigrate (&e));
|
|
}
|
|
|
|
if (g_Merged9xFolders) {
|
|
|
|
pSetupStringTableDestroy (g_Merged9xFolders);
|
|
}
|
|
|
|
pFlushSfQueue();
|
|
|
|
pUnloadDefaultUserHive();
|
|
|
|
pDestroyLinksData ();
|
|
|
|
pDestroySfList (g_SystemSfList);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
GenerateNewFileName (
|
|
IN PCTSTR OldName,
|
|
IN WORD Sequencer,
|
|
IN BOOL CheckExistence
|
|
)
|
|
{
|
|
PCTSTR extPtr;
|
|
PTSTR newName;
|
|
PTSTR result;
|
|
|
|
extPtr = GetFileExtensionFromPath (OldName);
|
|
|
|
if (!extPtr) {
|
|
extPtr = GetEndOfString (OldName);
|
|
}
|
|
else {
|
|
extPtr = _tcsdec (OldName, extPtr);
|
|
}
|
|
newName = DuplicatePathString (OldName, 0);
|
|
result = DuplicatePathString (OldName, 10);
|
|
StringCopyAB (newName, OldName, extPtr);
|
|
|
|
do {
|
|
Sequencer ++;
|
|
wsprintf (result, TEXT("%s (%u)%s"), newName, Sequencer, extPtr);
|
|
} while ((CheckExistence) && (DoesFileExist (result)));
|
|
FreePathString (newName);
|
|
return result;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIgnoredCollisions (
|
|
IN PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
TCHAR key[MEMDB_MAX];
|
|
DWORD value;
|
|
|
|
MemDbBuildKey (
|
|
key,
|
|
MEMDB_CATEGORY_IGNORED_COLLISIONS,
|
|
Data->ShellFolderIdentifier,
|
|
GetFileNameFromPath (Data->DestinationPath),
|
|
NULL);
|
|
if (MemDbGetPatternValue (key, &value)) {
|
|
return value;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Filters 9X -> NT
|
|
//
|
|
|
|
|
|
DWORD
|
|
pCollisionDetection9xNt (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
//
|
|
// this filter will detect name collision while copying win9x shell folders files.
|
|
// If we have a name collision, it means that NT already installed a file with the
|
|
// same name. In this case, we want the new file to be survive even with a different
|
|
// name. We will build a new file name starting with filename.ext. The new file will
|
|
// look something like filename001.ext. In all cases we want to keep the extension,
|
|
// since there might be some shell extensions active for this file.
|
|
// Important: we do not care about directory collisions.
|
|
//
|
|
|
|
PCTSTR newName;
|
|
PCTSTR OriginalSource;
|
|
PCTSTR extPtr;
|
|
DWORD value;
|
|
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
|
|
if ((!(Data->Attributes & FILE_ATTRIBUTE_DIRECTORY)) &&
|
|
(!StringIMatch (Data->SrcRootPath, Data->DestRootPath)) &&
|
|
(DoesFileExist (Data->DestinationPath))
|
|
) {
|
|
|
|
value = pIgnoredCollisions (Data);
|
|
if (value) {
|
|
|
|
if (value == 1) {
|
|
|
|
// we should keep the NT file
|
|
// By returning SHELLFILTER_SKIP_FILE we are instructing the copy routine
|
|
// not to copy this file. As a result the already installed NT file will
|
|
// survive
|
|
|
|
return SHELLFILTER_SKIP_FILE;
|
|
|
|
} else {
|
|
|
|
// we should keep the 9x file
|
|
// We want to delete the NT file installed here to make room for the 9x
|
|
// file that should be copied when we return from this filter
|
|
|
|
SetLongPathAttributes (Data->DestinationPath, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteLongPath (Data->DestinationPath);
|
|
}
|
|
|
|
} else {
|
|
|
|
newName = GenerateNewFileName (Data->DestinationPath, 0, TRUE); //TRUE - check unique
|
|
StringCopyByteCount (Data->DestinationPath, newName, sizeof (Data->DestinationPath));
|
|
FreePathString (newName);
|
|
|
|
//
|
|
// now if this was a link we need to fix the destination of the move external operation
|
|
// We have two reasons to do this. One is that the LinkEdit code needs the actual destination
|
|
// to be able to edit the link, and secondly we need this new target for the uninstall programs
|
|
// to work properly. If this file is not a LNK or a PIF, we don't care, we want everybody to
|
|
// use the NT installed file. BTW, there is a collision here only because NT installed a file
|
|
// with the same name in this location.
|
|
//
|
|
extPtr = GetFileExtensionFromPath (Data->DestinationPath);
|
|
if ((extPtr) &&
|
|
((StringIMatch (extPtr, TEXT("LNK"))) ||
|
|
(StringIMatch (extPtr, TEXT("PIF")))
|
|
)
|
|
) {
|
|
//
|
|
// Get the original source for this file
|
|
//
|
|
OriginalSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, Data->OrigRootPath);
|
|
MYASSERT (OriginalSource);
|
|
|
|
if (IsFileMarkedForOperation (OriginalSource, OPERATION_FILE_MOVE_SHELL_FOLDER)) {
|
|
RemoveOperationsFromPath (OriginalSource, OPERATION_FILE_MOVE_SHELL_FOLDER);
|
|
MarkFileForShellFolderMove (OriginalSource, Data->DestinationPath);
|
|
}
|
|
FreePathString (OriginalSource);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TERMINATE:
|
|
break;
|
|
}
|
|
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pFontNameFilter (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
static HASHTABLE HashTable;
|
|
HKEY FontKey;
|
|
REGVALUE_ENUM e;
|
|
PCTSTR Font;
|
|
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
//
|
|
// Preload a hash table with all the font names
|
|
//
|
|
|
|
HashTable = HtAlloc();
|
|
|
|
FontKey = OpenRegKeyStr (TEXT("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"));
|
|
if (FontKey) {
|
|
|
|
if (EnumFirstRegValue (&e, FontKey)) {
|
|
do {
|
|
|
|
Font = GetRegValueString (FontKey, e.ValueName);
|
|
|
|
if (Font) {
|
|
HtAddString (HashTable, Font);
|
|
MemFree (g_hHeap, 0, Font);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_ERROR, "Can't get value data for %s in fonts key", e.ValueName));
|
|
|
|
} while (EnumNextRegValue (&e));
|
|
}
|
|
|
|
CloseRegKey (FontKey);
|
|
}
|
|
ELSE_LOG ((LOG_ERROR, "Can't open Fonts registry key. There may be duplicate font files."));
|
|
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
//
|
|
// If the shell folder is Fonts, and the font is already
|
|
// registered, skip the Win9x copy.
|
|
//
|
|
|
|
if (StringIMatch (Data->ShellFolderIdentifier, TEXT("Fonts"))) {
|
|
|
|
if (!(Data->Attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
if (DoesFileExist (Data->DestinationPath)) {
|
|
//
|
|
// NT already installed this file. We won't overwrite this
|
|
// with the 9x copy.
|
|
//
|
|
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Skipping copy of already existent font file: %s",
|
|
Data->DestinationPath
|
|
));
|
|
|
|
return SHELLFILTER_SKIP_FILE;
|
|
}
|
|
|
|
if (HtFindString (HashTable, GetFileNameFromPath (Data->DestinationPath))) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Skipping copy of already registered font file: %s",
|
|
Data->DestinationPath
|
|
));
|
|
|
|
return SHELLFILTER_SKIP_FILE;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case TERMINATE:
|
|
HtFree (HashTable);
|
|
HashTable = NULL;
|
|
break;
|
|
}
|
|
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsCommonSf (
|
|
IN PCTSTR ShellFolderTag
|
|
)
|
|
{
|
|
TCHAR memdbKey[MAX_SHELL_TAG + 32];
|
|
|
|
if (StringIPrefix (ShellFolderTag, TEXT("Common"))) {
|
|
return TRUE;
|
|
}
|
|
|
|
MemDbBuildKey (memdbKey, MEMDB_CATEGORY_SF_COMMON, ShellFolderTag, NULL, NULL);
|
|
|
|
return MemDbGetValue (memdbKey, NULL);
|
|
}
|
|
|
|
|
|
VOID
|
|
pConvertPerUserSfToCommon (
|
|
IN PCTSTR PerUserSf,
|
|
OUT PTSTR CommonSf // must hold MAX_SHELL_TAG chars
|
|
)
|
|
{
|
|
TCHAR memdbKey[MAX_SHELL_TAG + 32];
|
|
DWORD offset;
|
|
BOOL useDefault = TRUE;
|
|
|
|
MemDbBuildKey (memdbKey, MEMDB_CATEGORY_SF_PERUSER, PerUserSf, NULL, NULL);
|
|
if (MemDbGetValue (memdbKey, &offset)) {
|
|
if (MemDbBuildKeyFromOffset (offset, CommonSf, 1, NULL)) {
|
|
useDefault = FALSE;
|
|
}
|
|
}
|
|
|
|
if (useDefault) {
|
|
wsprintf (CommonSf, TEXT("Common %s"), PerUserSf);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
pConvertCommonSfToPerUser (
|
|
IN PCTSTR CommonSf,
|
|
OUT PTSTR PerUserSf // must hold MAX_SHELL_TAG chars
|
|
)
|
|
{
|
|
TCHAR memdbKey[MAX_SHELL_TAG + 32];
|
|
DWORD offset;
|
|
BOOL useDefault = TRUE;
|
|
|
|
MemDbBuildKey (memdbKey, MEMDB_CATEGORY_SF_COMMON, CommonSf, NULL, NULL);
|
|
if (MemDbGetValue (memdbKey, &offset)) {
|
|
if (MemDbBuildKeyFromOffset (offset, PerUserSf, 1, NULL)) {
|
|
useDefault = FALSE;
|
|
}
|
|
}
|
|
|
|
if (useDefault) {
|
|
if (StringIPrefix (CommonSf, TEXT("Common"))) {
|
|
CommonSf += 6;
|
|
if (_tcsnextc (CommonSf) == TEXT(' ')) {
|
|
CommonSf++;
|
|
}
|
|
}
|
|
|
|
StringCopy (PerUserSf, CommonSf);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsObsoleteLink (
|
|
IN PCTSTR ShortcutName,
|
|
IN PCTSTR ShortcutTarget,
|
|
IN PCTSTR ShortcutArgs,
|
|
IN PCTSTR CurrentShellFolder,
|
|
IN PCTSTR CurrentShellFolderPath
|
|
)
|
|
{
|
|
PLINK_DATA linkData = NULL;
|
|
PLINK_RENAME_DATA linkRenameData = NULL;
|
|
LONG stringId;
|
|
TCHAR perUserName[MAX_SHELL_TAG];
|
|
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"pIsObsoleteLink: Checking %s\n"
|
|
" Input Target: %s\n"
|
|
" Input Args: %s\n"
|
|
" Current Shell Folder: %s\n"
|
|
" Current Shell Folder Path: %s",
|
|
ShortcutName,
|
|
ShortcutTarget,
|
|
ShortcutArgs,
|
|
CurrentShellFolder,
|
|
CurrentShellFolderPath
|
|
));
|
|
|
|
pConvertCommonSfToPerUser (CurrentShellFolder, perUserName);
|
|
|
|
stringId = pSetupStringTableLookUpString (g_FoldersTable, perUserName, 0);
|
|
|
|
if (stringId != -1) {
|
|
|
|
pSetupStringTableGetExtraData (g_FoldersTable, stringId, &linkData, sizeof (PLINK_DATA));
|
|
while (linkData) {
|
|
|
|
#if 0
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Checking NT-installed LNK:\n"
|
|
" Target: %s\n"
|
|
" Args: %s",
|
|
linkData->Target,
|
|
linkData->Arguments
|
|
));
|
|
#endif
|
|
|
|
if ((IsPatternMatch (linkData->Target, ShortcutTarget)) &&
|
|
(IsPatternMatch (linkData->Arguments, ShortcutArgs))
|
|
) {
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Obsolete link:\n"
|
|
" \"%s\" matched \"%s\"\n"
|
|
" \"%s\" matched \"%s\"",
|
|
linkData->Target,
|
|
ShortcutTarget,
|
|
linkData->Arguments,
|
|
ShortcutArgs
|
|
));
|
|
return TRUE;
|
|
}
|
|
|
|
linkRenameData = g_LinkRenameData;
|
|
while (linkRenameData) {
|
|
|
|
#if 0
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Checking NT rename data:\n"
|
|
" Old Target: %s\n"
|
|
" New Target: %s\n"
|
|
" Old Args: %s\n"
|
|
" New Args: %s",
|
|
linkRenameData->OldTarget,
|
|
linkRenameData->NewTarget,
|
|
linkRenameData->OldArguments,
|
|
linkRenameData->NewArguments
|
|
));
|
|
#endif
|
|
|
|
if (StringIMatch (linkRenameData->ShellFolderName, perUserName)) {
|
|
|
|
if ((IsPatternMatch (linkRenameData->OldTarget, ShortcutTarget)) &&
|
|
(IsPatternMatch (linkRenameData->NewTarget, linkData->Target)) &&
|
|
(IsPatternMatch (linkRenameData->OldArguments, ShortcutArgs)) &&
|
|
(IsPatternMatch (linkRenameData->NewArguments, linkData->Arguments))
|
|
) {
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Obsolete link:\n"
|
|
" \"%s\" matched \"%s\"\n"
|
|
" \"%s\" matched \"%s\"\n"
|
|
" \"%s\" matched \"%s\"\n"
|
|
" \"%s\" matched \"%s\"\n",
|
|
linkRenameData->OldTarget, ShortcutTarget,
|
|
linkRenameData->NewTarget, linkData->Target,
|
|
linkRenameData->OldArguments, ShortcutArgs,
|
|
linkRenameData->NewArguments, linkData->Arguments
|
|
));
|
|
return TRUE;
|
|
}
|
|
}
|
|
linkRenameData = linkRenameData->Next;
|
|
}
|
|
linkData = linkData->Next;
|
|
}
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_SHELL, "Nothing in shell folder %s is obsolete", perUserName));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pStartupDisableFilter (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
DWORD status;
|
|
PCTSTR originalSource;
|
|
PCTSTR newSource;
|
|
PCTSTR path;
|
|
TCHAR disablePath[MAX_TCHAR_PATH];
|
|
PTSTR dontCare;
|
|
DWORD result = SHELLFILTER_OK;
|
|
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
__FUNCTION__ ": Processing %s in %s",
|
|
Data->TempSourcePath,
|
|
Data->ShellFolderIdentifier
|
|
));
|
|
|
|
if (!StringIMatch (Data->ShellFolderIdentifier, TEXT("startup")) &&
|
|
!StringIMatch (Data->ShellFolderIdentifier, TEXT("common startup"))
|
|
) {
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Shell folder ID %s does not match startup or common startup",
|
|
Data->ShellFolderIdentifier
|
|
));
|
|
break;
|
|
}
|
|
|
|
if (Data->DestRootPath[0] == 0 ||
|
|
Data->DestRootPath[1] == 0 ||
|
|
Data->DestRootPath[2] == 0 ||
|
|
Data->DestRootPath[3] == 0
|
|
) {
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Skipping disable of startup item %s because its dest is a root directory",
|
|
Data->DestinationPath
|
|
));
|
|
break;
|
|
}
|
|
|
|
originalSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, Data->OrigRootPath);
|
|
MYASSERT (originalSource);
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Checking if %s is disabled", originalSource));
|
|
|
|
if (IsFileDisabled (originalSource)) {
|
|
//
|
|
// Redirect disabled startup items to ..\Disabled Startup
|
|
//
|
|
|
|
path = JoinPaths (Data->DestRootPath, TEXT("..\\Disabled Startup"));
|
|
MakeSureLongPathExists (path, TRUE); // TRUE == path only
|
|
GetFullPathName (path, ARRAYSIZE(disablePath), disablePath, &dontCare);
|
|
FreePathString (path);
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Disabled startup dest is %s", disablePath));
|
|
SetLongPathAttributes (disablePath, FILE_ATTRIBUTE_HIDDEN);
|
|
|
|
newSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, disablePath);
|
|
StringCopy (Data->DestinationPath, newSource);
|
|
FreePathString (newSource);
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Startup item moved to %s", Data->DestinationPath));
|
|
|
|
RemoveOperationsFromPath (originalSource, OPERATION_FILE_DISABLED);
|
|
|
|
if (IsFileMarkedForOperation (originalSource, OPERATION_FILE_MOVE_SHELL_FOLDER)) {
|
|
RemoveOperationsFromPath (originalSource, OPERATION_FILE_MOVE_SHELL_FOLDER);
|
|
MarkFileForShellFolderMove (originalSource, Data->DestinationPath);
|
|
}
|
|
|
|
//
|
|
// By returning SHELLFILTER_FORCE_CHANGE, we are instructing the
|
|
// shell folder algorithm to use our destination and not call anyone
|
|
// else.
|
|
//
|
|
|
|
result = SHELLFILTER_FORCE_CHANGE;
|
|
}
|
|
|
|
FreePathString (originalSource);
|
|
break;
|
|
|
|
case TERMINATE:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pObsoleteLinksFilter (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
static IShellLink *shellLink = NULL;
|
|
static IPersistFile *persistFile = NULL;
|
|
static PTSTR bigBuf;
|
|
static PTSTR ShortcutTarget;
|
|
static PTSTR ShortcutArgs;
|
|
static PTSTR ShortcutWorkDir;
|
|
static PTSTR ShortcutIconPath;
|
|
INT ShortcutIcon;
|
|
WORD ShortcutHotKey;
|
|
BOOL result = FALSE;
|
|
BOOL dosApp;
|
|
BOOL msDosMode;
|
|
PCTSTR extPtr;
|
|
FILEOP_PROP_ENUM eOpProp;
|
|
PTSTR NewTarget;
|
|
PCTSTR OriginalSource;
|
|
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
|
|
if (!InitCOMLink (&shellLink, &persistFile)) {
|
|
DEBUGMSG ((DBG_ERROR, "Cannot initialize COM. Obsolete links filter will not work."));
|
|
return SHELLFILTER_ERROR;
|
|
}
|
|
|
|
bigBuf = (PTSTR) MemAllocUninit ((MEMDB_MAX * 4) * sizeof (TCHAR));
|
|
if (!bigBuf) {
|
|
return SHELLFILTER_ERROR;
|
|
}
|
|
|
|
ShortcutTarget = bigBuf;
|
|
ShortcutArgs = ShortcutTarget + MEMDB_MAX;
|
|
ShortcutWorkDir = ShortcutArgs + MEMDB_MAX;
|
|
ShortcutIconPath = ShortcutWorkDir + MEMDB_MAX;
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
|
|
extPtr = GetFileExtensionFromPath (Data->DestinationPath);
|
|
|
|
if (!extPtr) {
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
if ((!StringIMatch (extPtr, TEXT("LNK"))) &&
|
|
(!StringIMatch (extPtr, TEXT("PIF")))
|
|
) {
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
DEBUGMSG ((DBG_SHELL, "Extracting shortcut info for temp file %s", Data->TempSourcePath));
|
|
|
|
if ((shellLink) &&
|
|
(persistFile) &&
|
|
(ExtractShortcutInfo (
|
|
ShortcutTarget,
|
|
ShortcutArgs,
|
|
ShortcutWorkDir,
|
|
ShortcutIconPath,
|
|
&ShortcutIcon,
|
|
&ShortcutHotKey,
|
|
&dosApp,
|
|
&msDosMode,
|
|
NULL,
|
|
NULL,
|
|
Data->TempSourcePath,
|
|
shellLink,
|
|
persistFile
|
|
))) {
|
|
|
|
// get the new destination if this shortcut is to be edited
|
|
NewTarget = NULL;
|
|
|
|
//
|
|
// Get the original source for this file
|
|
//
|
|
OriginalSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, Data->OrigRootPath);
|
|
MYASSERT (OriginalSource);
|
|
|
|
DEBUGMSG ((DBG_SHELL, "OriginalSource for shortcut is %s", OriginalSource));
|
|
|
|
if (IsFileMarkedForOperation (OriginalSource, OPERATION_LINK_EDIT)) {
|
|
|
|
DEBUGMSG ((DBG_SHELL, "OriginalSource is marked for file edit"));
|
|
|
|
if (EnumFirstFileOpProperty (&eOpProp, GetSequencerFromPath (OriginalSource), OPERATION_LINK_EDIT)) {
|
|
|
|
do {
|
|
|
|
if (StringIMatch (eOpProp.PropertyName, MEMDB_CATEGORY_LINKEDIT_TARGET)) {
|
|
NewTarget = DuplicatePathString (eOpProp.Property, 0);
|
|
break;
|
|
}
|
|
} while (EnumNextFileOpProperty (&eOpProp));
|
|
}
|
|
}
|
|
|
|
FreePathString (OriginalSource);
|
|
|
|
if (!NewTarget) {
|
|
NewTarget = DuplicatePathString (ShortcutTarget, 0);
|
|
}
|
|
|
|
result = pIsObsoleteLink (Data->DestinationPath, NewTarget, ShortcutArgs, Data->ShellFolderIdentifier, Data->DestRootPath);
|
|
|
|
DEBUGMSG_IF ((result, DBG_SHELL, "%s is obsolete", Data->DestinationPath));
|
|
DEBUGMSG_IF ((!result, DBG_SHELL, "%s is not obsolete", Data->DestinationPath));
|
|
|
|
FreePathString (NewTarget);
|
|
}
|
|
|
|
if (result) {
|
|
//
|
|
// If this link is to be edited by the LinkEdit code we should remove this
|
|
// operation because the file will not be available.
|
|
//
|
|
|
|
DEBUGMSG ((DBG_SHELL, "File %s will not be available for LinkEdit", Data->TempSourcePath));
|
|
|
|
//
|
|
// Get the original source for this file
|
|
//
|
|
OriginalSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, Data->OrigRootPath);
|
|
MYASSERT (OriginalSource);
|
|
|
|
if (IsFileMarkedForOperation (OriginalSource, OPERATION_LINK_EDIT)) {
|
|
RemoveOperationsFromPath (OriginalSource, OPERATION_LINK_EDIT);
|
|
}
|
|
FreePathString (OriginalSource);
|
|
|
|
//
|
|
// Now remove the source file. We cannot keep this file to be restored by the UNDO code.
|
|
// The reason for this is that we might have some other
|
|
// shell folder pointing to the same source and destination. In this case, obsolete links
|
|
// filter will not work since we just removed the file from OPERATION_LINK_EDIT.
|
|
//
|
|
MYASSERT ((Data->Attributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
|
|
|
|
SetLongPathAttributes (Data->TempSourcePath, FILE_ATTRIBUTE_NORMAL);
|
|
if (!DeleteLongPath (Data->TempSourcePath)) {
|
|
DEBUGMSG ((DBG_ERROR, "Cannot remove file %s", Data->TempSourcePath));
|
|
SetLongPathAttributes (Data->TempSourcePath, Data->Attributes);
|
|
}
|
|
|
|
return SHELLFILTER_SKIP_FILE;
|
|
}
|
|
return SHELLFILTER_OK;
|
|
|
|
case TERMINATE:
|
|
|
|
if (bigBuf) {
|
|
FreeMem (bigBuf);
|
|
}
|
|
|
|
FreeCOMLink (&shellLink, &persistFile);
|
|
break;
|
|
}
|
|
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Filters NT -> 9X
|
|
//
|
|
|
|
|
|
DWORD
|
|
pCollisionDetectionNt9x (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
//
|
|
// this filter will detect name collision while copying files from NT shell folders
|
|
// or default user to migrated 9x shell folder.
|
|
// If we have a name collision, we want to keep the NT file original name and to rename
|
|
// the migrated Win9x file. We will build a new file name starting with filename.ext.
|
|
// The new file will look something like filename001.ext. In all cases we want to keep
|
|
// the extension, since there might be some shell extensions active for this file.
|
|
// Important: we do not care about directory collisions.
|
|
//
|
|
|
|
PCTSTR newName;
|
|
PCTSTR extPtr;
|
|
PCTSTR OriginalSource;
|
|
DWORD value;
|
|
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
|
|
if ((!(Data->Attributes & FILE_ATTRIBUTE_DIRECTORY)) &&
|
|
(!StringIMatch (Data->SrcRootPath, Data->DestRootPath)) &&
|
|
(DoesFileExist (Data->DestinationPath))
|
|
) {
|
|
|
|
value = pIgnoredCollisions (Data);
|
|
if (value) {
|
|
|
|
if (value == 1) {
|
|
|
|
// we should keep the 9x file
|
|
// By returning SHELLFILTER_SKIP_FILE we are instructing the copy routine
|
|
// not to copy this file. As a result the already installed 9x file will
|
|
// survive
|
|
|
|
return SHELLFILTER_SKIP_FILE;
|
|
|
|
} else {
|
|
|
|
// we should keep the NT file
|
|
// We want to delete the 9x file installed here to make room for the NT
|
|
// file that should be copied when we return from this filter
|
|
|
|
SetLongPathAttributes (Data->DestinationPath, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteLongPath (Data->DestinationPath);
|
|
}
|
|
|
|
} else {
|
|
|
|
newName = GenerateNewFileName (Data->DestinationPath, 0, TRUE); //TRUE - check unique
|
|
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"9x file collides with NT file -- renaming 9x file from %s to %s",
|
|
Data->DestinationPath,
|
|
newName
|
|
));
|
|
|
|
pQueueSfMove (Data->DestinationPath, newName);
|
|
|
|
//
|
|
// now if this was a link we need to fix the destination of the move external operation
|
|
// We have two reasons to do this. One is that the LinkEdit code needs the actual destination
|
|
// to be able to edit the link, and secondly we need this new target for the uninstall programs
|
|
// to work properly. If this file is not a LNK or a PIF, we don't care, we want everybody to
|
|
// use the NT installed file. BTW, there is a collision here only because NT installed a file
|
|
// with the same name in this location.
|
|
//
|
|
extPtr = GetFileExtensionFromPath (Data->DestinationPath);
|
|
|
|
if ((extPtr) &&
|
|
((StringIMatch (extPtr, TEXT("LNK"))) ||
|
|
(StringIMatch (extPtr, TEXT("PIF")))
|
|
)
|
|
) {
|
|
//
|
|
// Get the original source for this file
|
|
//
|
|
OriginalSource = StringSearchAndReplace (Data->TempSourcePath, Data->SrcRootPath, Data->OrigRootPath);
|
|
MYASSERT (OriginalSource);
|
|
|
|
if (IsFileMarkedForOperation (OriginalSource, OPERATION_FILE_MOVE_SHELL_FOLDER)) {
|
|
DEBUGMSG ((
|
|
DBG_SHELL,
|
|
"Removing shell move op from %s",
|
|
OriginalSource
|
|
));
|
|
|
|
RemoveOperationsFromPath (OriginalSource, OPERATION_FILE_MOVE_SHELL_FOLDER);
|
|
MarkFileForShellFolderMove (OriginalSource, newName);
|
|
}
|
|
FreePathString (OriginalSource);
|
|
}
|
|
|
|
FreePathString (newName);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TERMINATE:
|
|
break;
|
|
}
|
|
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pDetectOtherShellFolder (
|
|
IN OUT PPROFILE_MERGE_DATA Data
|
|
)
|
|
{
|
|
switch (Data->Context) {
|
|
|
|
case INITIALIZE:
|
|
g_UserSfList = pCreateUserSfList (Data);
|
|
break;
|
|
|
|
case PROCESS_PATH:
|
|
|
|
if (Data->Attributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
//
|
|
// This is a dir. Let's see if we enter another shell folder
|
|
//
|
|
|
|
if (((g_SystemSfList) && (pSetupStringTableLookUpString (g_SystemSfList, (PVOID) Data->TempSourcePath, STRTAB_CASE_INSENSITIVE) != -1)) ||
|
|
((g_UserSfList) && (pSetupStringTableLookUpString (g_UserSfList, (PVOID) Data->TempSourcePath, STRTAB_CASE_INSENSITIVE) != -1))
|
|
) {
|
|
//
|
|
// we are just getting into another shell folder. Let's skip it
|
|
//
|
|
return SHELLFILTER_SKIP_DIRECTORY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TERMINATE:
|
|
pDestroySfList (g_UserSfList);
|
|
break;
|
|
}
|
|
|
|
return SHELLFILTER_OK;
|
|
}
|
|
|
|
|