windows-nt/Source/XPSP1/NT/shell/shell32/folder.cpp

2734 lines
94 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
#include "shellprv.h"
#include "apithk.h"
#include "folder.h"
#include "ids.h"
#include "deskfldr.h"
#include <winnls.h>
#include "shitemid.h"
#include "sddl.h"
#ifdef _WIN64
#include <wow64t.h>
#endif
#include "filefldr.h"
#include "lmcons.h"
#include "netview.h"
//---------------------------------------------------------------------------
// Get the path for the CSIDL_ folders and optionally create it if it
// doesn't exist.
//
// Returns FALSE if the special folder given isn't one of those above or the
// directory couldn't be created.
// By default all the special folders are in the windows directory.
// This can be overidden by a [.Shell Folders] section in win.ini with
// entries like Desktop = c:\stuff\desktop
// This in turn can be overidden by a "per user" section in win.ini eg
// [Shell Folder Ianel] - the user name for this section is the current
// network user name, if this fails the default network user name is used
// and if this fails the name given at setup time is used.
//
// "Shell Folders" is the key that records all the absolute paths to the
// shell folders. The values there are always supposed to be present.
//
// "User Shell Folders" is the key where the user's modifications from
// the defaults are stored.
//
// When we need to find the location of a path, we look in "User Shell Folders"
// first, and if that's not there, generate the default path. In either
// case we then write the absolute path under "Shell Folders" for other
// apps to look at. This is so that HKEY_CURRENT_USER can be propagated
// to a machine with Windows installed in a different directory, and as
// long as the user hasn't changed the setting, they won't have the other
// Windows directory hard-coded in the registry.
// -- gregj, 11/10/94
typedef enum {
SDIF_NONE = 0,
SDIF_CREATE_IN_ROOT = 0x00000001, // create in root (not in profiles dir)
SDIF_CREATE_IN_WINDIR = 0x00000002, // create in the windows dir (not in profiles dir)
SDIF_CREATE_IN_ALLUSERS = 0x00000003, // create in "All Users" folder (not in profiles dir)
SDIF_CREATE_IN_MYDOCUMENTS = 0x00000004, // create in CSIDL_PERSONAL folder
SDIF_CREATE_IN_LOCALSET = 0x00000005, // create in <user>\Local Settings folder
SDIF_CREATE_IN_MASK = 0x0000000F, // mask for above values
SDIF_CAN_DELETE = 0x00000010,
SDIF_SHORTCUT_RELATIVE = 0x00000020, // make shortcuts relative to this folder
SDIF_HIDE = 0x00000040, // hide these when we create them
SDIF_EMPTY_IF_NOT_IN_REG = 0x00000080, // does not exist if nothing in the registry
SDIF_NOT_FILESYS = 0x00000100, // not a file system folder
SDIF_NOT_TRACKED = 0x00000200, // don't track this, it can't change
SDIF_CONST_IDLIST = 0x00000400, // don't alloc or free this
SDIF_REMOVABLE = 0x00000800, // Can exist on removable media
SDIF_CANT_MOVE_RENAME = 0x00001000, // can't move or rename this
SDIF_WX86 = 0x00002000, // do Wx86 thunking
SDIF_NETWORKABLE = 0x00004000, // Can be moved to the net
SDIF_MAYBE_ALIASED = 0x00008000, // could have an alias representation
SDIF_PERSONALIZED = 0x00010000, // resource name is to be personalized
SDIF_POLICY_NO_MOVE = 0x00020000, // policy blocks move
} ;
typedef DWORD FOLDER_FLAGS;
typedef void (*FOLDER_CREATE_PROC)(int id, LPCTSTR pszPath);
void _InitMyPictures(int id, LPCTSTR pszPath);
void _InitMyMusic(int id, LPCTSTR pszPath);
void _InitMyVideos(int id, LPCTSTR pszPath);
void _InitPerUserMyMusic(int id, LPCTSTR pszPath);
void _InitPerUserMyPictures(int id, LPCTSTR pszPath);
void _InitRecentDocs(int id, LPCTSTR pszPath);
void _InitFavorites(int id, LPCTSTR pszPath);
typedef struct {
int id; // CSIDL_ value
int idsDefault; // string id of default folder name name
LPCTSTR pszValueName; // reg key (not localized)
HKEY hKey; // HKCU or HKLM (Current User or Local Machine)
FOLDER_FLAGS dwFlags;
FOLDER_CREATE_PROC pfnInit;
INT idsLocalizedName;
} FOLDER_INFO;
// typical entry
#define FOLDER(csidl, ids, value, key, ff) \
{ csidl, ids, value, key, ff, NULL, 0}
// FIXEDFOLDER entries must have be marked SDIF_CONST_IDLIST
// or have code in _GetFolderDefaultPath() to create their path
// if they have a filesys path
#define FIXEDFOLDER(csidl, value, ff) \
{ csidl, 0, value, NULL, ff, NULL, 0}
// PROCFOLDER's have a FOLDER_CREATE_PROC pfn that gets
// run in _PostCreateStuff()
#define PROCFOLDER(csidl, ids, value, key, ff, proc, idsLocal) \
{csidl, ids, value, key, ff, proc, idsLocal}
// folder that needs SHSetLocalizedName() in _PostCreateStuff()
#define LOCALFOLDER(csidl, ids, value, key, ff, idsLocal) \
{csidl, ids, value, key, ff, NULL, idsLocal}
const FOLDER_INFO c_rgFolderInfo[] =
{
FOLDER( CSIDL_DESKTOP,
IDS_CSIDL_DESKTOPDIRECTORY,
TEXT("DesktopFolder"),
NULL,
SDIF_NOT_TRACKED | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_NETWORK,
TEXT("NetworkFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_DRIVES,
TEXT("DriveFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_INTERNET,
TEXT("InternetFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_CONTROLS,
TEXT("ControlPanelFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_PRINTERS,
TEXT("PrintersFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_BITBUCKET,
TEXT("RecycleBinFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FIXEDFOLDER( CSIDL_CONNECTIONS,
TEXT("ConnectionsFolder"),
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
FOLDER( CSIDL_FONTS,
0,
TEXT("Fonts"),
HKEY_CURRENT_USER,
SDIF_NOT_TRACKED | SDIF_CREATE_IN_WINDIR | SDIF_CANT_MOVE_RENAME),
FOLDER( CSIDL_DESKTOPDIRECTORY,
IDS_CSIDL_DESKTOPDIRECTORY,
TEXT("Desktop"),
HKEY_CURRENT_USER, SDIF_SHORTCUT_RELATIVE),
// _STARTUP is a subfolder of _PROGRAMS is a subfolder of _STARTMENU -- keep that order
FOLDER( CSIDL_STARTUP,
IDS_CSIDL_STARTUP,
TEXT("Startup"),
HKEY_CURRENT_USER, SDIF_NONE),
FOLDER( CSIDL_PROGRAMS,
IDS_CSIDL_PROGRAMS,
TEXT("Programs"),
HKEY_CURRENT_USER,
SDIF_NONE),
FOLDER( CSIDL_STARTMENU,
IDS_CSIDL_STARTMENU,
TEXT("Start Menu"),
HKEY_CURRENT_USER,
SDIF_SHORTCUT_RELATIVE),
PROCFOLDER( CSIDL_RECENT,
IDS_CSIDL_RECENT,
TEXT("Recent"),
HKEY_CURRENT_USER,
SDIF_HIDE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE,
_InitRecentDocs,
IDS_FOLDER_RECENTDOCS),
FOLDER( CSIDL_SENDTO,
IDS_CSIDL_SENDTO,
TEXT("SendTo"),
HKEY_CURRENT_USER,
SDIF_HIDE),
FOLDER( CSIDL_PERSONAL,
IDS_CSIDL_PERSONAL,
TEXT("Personal"),
HKEY_CURRENT_USER,
SDIF_SHORTCUT_RELATIVE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CONST_IDLIST | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE),
PROCFOLDER( CSIDL_FAVORITES,
IDS_CSIDL_FAVORITES,
TEXT("Favorites"),
HKEY_CURRENT_USER,
SDIF_POLICY_NO_MOVE,
_InitFavorites,
IDS_FOLDER_FAVORITES),
FOLDER( CSIDL_NETHOOD,
IDS_CSIDL_NETHOOD,
TEXT("NetHood"),
HKEY_CURRENT_USER,
SDIF_HIDE),
FOLDER( CSIDL_PRINTHOOD,
IDS_CSIDL_PRINTHOOD,
TEXT("PrintHood"),
HKEY_CURRENT_USER,
SDIF_HIDE),
FOLDER( CSIDL_TEMPLATES,
IDS_CSIDL_TEMPLATES,
TEXT("Templates"),
HKEY_CURRENT_USER,
SDIF_HIDE),
// Common special folders
// _STARTUP is a subfolder of _PROGRAMS is a subfolder of _STARTMENU -- keep that order
FOLDER( CSIDL_COMMON_STARTUP,
IDS_CSIDL_STARTUP,
TEXT("Common Startup"),
HKEY_LOCAL_MACHINE,
SDIF_CREATE_IN_ALLUSERS | SDIF_CANT_MOVE_RENAME | SDIF_EMPTY_IF_NOT_IN_REG),
FOLDER( CSIDL_COMMON_PROGRAMS,
IDS_CSIDL_PROGRAMS,
TEXT("Common Programs"),
HKEY_LOCAL_MACHINE,
SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
FOLDER( CSIDL_COMMON_STARTMENU,
IDS_CSIDL_STARTMENU,
TEXT("Common Start Menu"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
FOLDER( CSIDL_COMMON_DESKTOPDIRECTORY,
IDS_CSIDL_DESKTOPDIRECTORY,
TEXT("Common Desktop"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS),
FOLDER( CSIDL_COMMON_FAVORITES,
IDS_CSIDL_FAVORITES,
TEXT("Common Favorites"),
HKEY_LOCAL_MACHINE,
SDIF_CREATE_IN_ALLUSERS),
FOLDER( CSIDL_COMMON_APPDATA,
IDS_CSIDL_APPDATA,
TEXT("Common AppData"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS),
FOLDER( CSIDL_COMMON_TEMPLATES,
IDS_CSIDL_TEMPLATES,
TEXT("Common Templates"),
HKEY_LOCAL_MACHINE,
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_CREATE_IN_ALLUSERS),
LOCALFOLDER( CSIDL_COMMON_DOCUMENTS,
IDS_CSIDL_ALLUSERS_DOCUMENTS,
TEXT("Common Documents"),
HKEY_LOCAL_MACHINE,
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
IDS_LOCALGDN_FLD_SHARED_DOC),
// Application Data special folder
FOLDER( CSIDL_APPDATA,
IDS_CSIDL_APPDATA,
TEXT("AppData"),
HKEY_CURRENT_USER, SDIF_SHORTCUT_RELATIVE),
FOLDER( CSIDL_LOCAL_APPDATA,
IDS_CSIDL_APPDATA,
TEXT("Local AppData"),
HKEY_CURRENT_USER, SDIF_CREATE_IN_LOCALSET),
// Non-localized startup folder (do not localize this folder name)
FOLDER( CSIDL_ALTSTARTUP,
IDS_CSIDL_ALTSTARTUP,
TEXT("AltStartup"),
HKEY_CURRENT_USER,
SDIF_EMPTY_IF_NOT_IN_REG),
// Non-localized Common StartUp group (do not localize this folde name)
FOLDER( CSIDL_COMMON_ALTSTARTUP,
IDS_CSIDL_ALTSTARTUP,
TEXT("Common AltStartup"),
HKEY_LOCAL_MACHINE,
SDIF_EMPTY_IF_NOT_IN_REG | SDIF_CREATE_IN_ALLUSERS),
// Per-user Internet-related folders
FOLDER( CSIDL_INTERNET_CACHE,
IDS_CSIDL_CACHE,
TEXT("Cache"),
HKEY_CURRENT_USER,
SDIF_CREATE_IN_LOCALSET),
FOLDER( CSIDL_COOKIES,
IDS_CSIDL_COOKIES,
TEXT("Cookies"),
HKEY_CURRENT_USER,
SDIF_NONE),
FOLDER( CSIDL_HISTORY,
IDS_CSIDL_HISTORY,
TEXT("History"),
HKEY_CURRENT_USER,
SDIF_CREATE_IN_LOCALSET),
FIXEDFOLDER( CSIDL_SYSTEM,
TEXT("System"),
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_SHORTCUT_RELATIVE),
FIXEDFOLDER( CSIDL_SYSTEMX86,
TEXT("SystemX86"),
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_WX86 | SDIF_SHORTCUT_RELATIVE),
FIXEDFOLDER( CSIDL_WINDOWS,
TEXT("Windows"),
SDIF_NOT_TRACKED | SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME),
FIXEDFOLDER( CSIDL_PROFILE,
TEXT("Profile"),
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME),
PROCFOLDER( CSIDL_MYPICTURES,
IDS_CSIDL_MYPICTURES,
TEXT("My Pictures"),
HKEY_CURRENT_USER,
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_SHORTCUT_RELATIVE | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
_InitPerUserMyPictures,
0),
//
// CSIDL_PROGRAM_FILES must come after CSIDL_PROGRAM_FILESX86 so that shell links for x86 apps
// work correctly on non-x86 platforms.
// Example: On IA64 a 32-bit app creates a shortcut via IShellLink to the Program
// Files directory. A WOW64 registry hive maps "Program Files" to "Program Files (x86)". The shell
// link code then tries to abstract the special folder part of the path by mapping to one of the
// entries in this table. Since CSIDL_PROGRAM_FILES and CSIDL_PROGRAM_FILESX86 are the same it
// will map to the one that appears first in this table. When the shortcut is accessed in
// 64-bit mode the cidls are no longer the same. If CSIDL_PROGRAM_FILES was used instead of
// CSIDL_PROGRAM_FILESX86 the shortcut will be broken.
#ifdef WX86
FIXEDFOLDER( CSIDL_PROGRAM_FILESX86,
TEXT("ProgramFilesX86"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE|SDIF_WX86),
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMONX86,
TEXT("CommonProgramFilesX86"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_WX86),
#else
FIXEDFOLDER( CSIDL_PROGRAM_FILESX86,
TEXT("ProgramFilesX86"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE),
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMONX86,
TEXT("CommonProgramFilesX86"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE),
#endif
// CSIDL_PROGRAM_FILES must come after CSIDL_PROGRAM_FILESX86. See comment above.
FIXEDFOLDER( CSIDL_PROGRAM_FILES,
TEXT("ProgramFiles"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE),
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMON,
TEXT("CommonProgramFiles"),
SDIF_NOT_TRACKED | SDIF_CAN_DELETE),
FOLDER( CSIDL_ADMINTOOLS,
IDS_CSIDL_ADMINTOOLS,
TEXT("Administrative Tools"),
HKEY_CURRENT_USER,
SDIF_NONE),
FOLDER( CSIDL_COMMON_ADMINTOOLS,
IDS_CSIDL_ADMINTOOLS,
TEXT("Common Administrative Tools"),
HKEY_LOCAL_MACHINE,
SDIF_CREATE_IN_ALLUSERS),
PROCFOLDER( CSIDL_MYMUSIC,
IDS_CSIDL_MYMUSIC,
TEXT("My Music"),
HKEY_CURRENT_USER,
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
_InitPerUserMyMusic,
0),
PROCFOLDER( CSIDL_MYVIDEO,
IDS_CSIDL_MYVIDEO,
TEXT("My Video"),
HKEY_CURRENT_USER,
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
_InitMyVideos,
0),
PROCFOLDER( CSIDL_COMMON_PICTURES,
IDS_CSIDL_ALLUSERS_PICTURES,
TEXT("CommonPictures"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
_InitMyPictures,
IDS_SHAREDPICTURES),
PROCFOLDER( CSIDL_COMMON_MUSIC,
IDS_CSIDL_ALLUSERS_MUSIC,
TEXT("CommonMusic"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
_InitMyMusic,
IDS_SHAREDMUSIC),
PROCFOLDER( CSIDL_COMMON_VIDEO,
IDS_CSIDL_ALLUSERS_VIDEO,
TEXT("CommonVideo"),
HKEY_LOCAL_MACHINE,
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
_InitMyVideos,
IDS_SHAREDVIDEO),
FIXEDFOLDER( CSIDL_RESOURCES,
TEXT("ResourceDir"),
SDIF_NOT_TRACKED),
FIXEDFOLDER( CSIDL_RESOURCES_LOCALIZED,
TEXT("LocalizedResourcesDir"),
SDIF_NOT_TRACKED),
FOLDER( CSIDL_COMMON_OEM_LINKS,
IDS_CSIDL_ALLUSERS_OEM_LINKS,
TEXT("OEM Links"),
HKEY_LOCAL_MACHINE,
SDIF_CAN_DELETE | SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
FOLDER( CSIDL_CDBURN_AREA,
IDS_CSIDL_CDBURN_AREA,
TEXT("CD Burning"),
HKEY_CURRENT_USER,
SDIF_CAN_DELETE | SDIF_CREATE_IN_LOCALSET),
FIXEDFOLDER( CSIDL_COMPUTERSNEARME,
TEXT("ComputersNearMe"),
SDIF_NONE),
FIXEDFOLDER(-1, NULL, SDIF_NONE)
};
EXTERN_C const IDLREGITEM c_idlMyDocs =
{
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_MYDOCS,
{ 0x450d8fba, 0xad25, 0x11d0, 0x98,0xa8,0x08,0x00,0x36,0x1b,0x11,0x03, },}, // CLSID_MyDocuments
0,
} ;
EXTERN_C const IDREGITEM c_idlPrinters[] =
{
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_DRIVES,
{ 0x20D04FE0, 0x3AEA, 0x1069, 0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_MyComputer
{sizeof(IDREGITEM), SHID_COMPUTER_REGITEM, 0,
{ 0x21EC2020, 0x3AEA, 0x1069, 0xA2,0xDD,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_ControlPanel
{sizeof(IDREGITEM), SHID_CONTROLPANEL_REGITEM, 0,
{ 0x2227A280, 0x3AEA, 0x1069, 0xA2, 0xDE, 0x08, 0x00, 0x2B, 0x30, 0x30, 0x9D, },}, // CLSID_Printers
0,
} ;
EXTERN_C const IDREGITEM c_idlControls[] =
{
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_DRIVES,
{ 0x20D04FE0, 0x3AEA, 0x1069, 0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_MyComputer
{sizeof(IDREGITEM), SHID_COMPUTER_REGITEM, 0,
{ 0x21EC2020, 0x3AEA, 0x1069, 0xA2,0xDD,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_ControlPanel
0,
} ;
EXTERN_C const IDLREGITEM c_idlBitBucket =
{
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_RECYCLEBIN,
{ 0x645FF040, 0x5081, 0x101B, 0x9F, 0x08, 0x00, 0xAA, 0x00, 0x2F, 0x95, 0x4E, },}, // CLSID_RecycleBin
0,
} ;
// this array holds a cache of the values of these folders. this cache can only
// be used in the hToken == NULL case otherwise we would need a per user version
// of this cache.
#define SFENTRY(x) { (LPTSTR)-1, (LPITEMIDLIST)x , (LPITEMIDLIST)-1}
EXTERN_C const IDREGITEM c_aidlConnections[];
struct {
LPTSTR psz;
LPITEMIDLIST pidl;
LPITEMIDLIST pidlNonAlias;
} g_aFolderCache[] = {
SFENTRY(&c_idlDesktop), // CSIDL_DESKTOP (0x0000)
SFENTRY(&c_idlInetRoot), // CSIDL_INTERNET (0x0001)
SFENTRY(-1), // CSIDL_PROGRAMS (0x0002)
SFENTRY(&c_idlControls), // CSIDL_CONTROLS (0x0003)
SFENTRY(&c_idlPrinters), // CSIDL_PRINTERS (0x0004)
SFENTRY(&c_idlMyDocs), // CSIDL_PERSONAL (0x0005)
SFENTRY(-1), // CSIDL_FAVORITES (0x0006)
SFENTRY(-1), // CSIDL_STARTUP (0x0007)
SFENTRY(-1), // CSIDL_RECENT (0x0008)
SFENTRY(-1), // CSIDL_SENDTO (0x0009)
SFENTRY(&c_idlBitBucket), // CSIDL_BITBUCKET (0x000a)
SFENTRY(-1), // CSIDL_STARTMENU (0x000b)
SFENTRY(-1), // CSIDL_MYDOCUMENTS (0x000c)
SFENTRY(-1), // CSIDL_MYMUSIC (0x000d)
SFENTRY(-1), // CSIDL_MYVIDEO (0x000e)
SFENTRY(-1), // <unused> (0x000f)
SFENTRY(-1), // CSIDL_DESKTOPDIRECTORY (0x0010)
SFENTRY(&c_idlDrives), // CSIDL_DRIVES (0x0011)
SFENTRY(&c_idlNet), // CSIDL_NETWORK (0x0012)
SFENTRY(-1), // CSIDL_NETHOOD (0x0013)
SFENTRY(-1), // CSIDL_FONTS (0x0014)
SFENTRY(-1), // CSIDL_TEMPLATES (0x0015)
SFENTRY(-1), // CSIDL_COMMON_STARTMENU (0x0016)
SFENTRY(-1), // CSIDL_COMMON_PROGRAMS (0X0017)
SFENTRY(-1), // CSIDL_COMMON_STARTUP (0x0018)
SFENTRY(-1), // CSIDL_COMMON_DESKTOPDIRECTORY (0x0019)
SFENTRY(-1), // CSIDL_APPDATA (0x001a)
SFENTRY(-1), // CSIDL_PRINTHOOD (0x001b)
SFENTRY(-1), // CSIDL_LOCAL_APPDATA (0x001c)
SFENTRY(-1), // CSIDL_ALTSTARTUP (0x001d)
SFENTRY(-1), // CSIDL_COMMON_ALTSTARTUP (0x001e)
SFENTRY(-1), // CSIDL_COMMON_FAVORITES (0x001f)
SFENTRY(-1), // CSIDL_INTERNET_CACHE (0x0020)
SFENTRY(-1), // CSIDL_COOKIES (0x0021)
SFENTRY(-1), // CSIDL_HISTORY (0x0022)
SFENTRY(-1), // CSIDL_COMMON_APPDATA (0x0023)
SFENTRY(-1), // CSIDL_WINDOWS (0x0024)
SFENTRY(-1), // CSIDL_SYSTEM (0x0025)
SFENTRY(-1), // CSIDL_PROGRAM_FILES (0x0026)
SFENTRY(-1), // CSIDL_MYPICTURES (0x0027)
SFENTRY(-1), // CSIDL_PROFILE (0x0028)
SFENTRY(-1), // CSIDL_SYSTEMX86 (0x0029)
SFENTRY(-1), // CSIDL_PROGRAM_FILESX86 (0x002a)
SFENTRY(-1), // CSIDL_PROGRAM_FILES_COMMON (0x002b)
SFENTRY(-1), // CSIDL_PROGRAM_FILES_COMMONX86 (0x002c)
SFENTRY(-1), // CSIDL_COMMON_TEMPLATES (0x002d)
SFENTRY(-1), // CSIDL_COMMON_DOCUMENTS (0x002e)
SFENTRY(-1), // CSIDL_COMMON_ADMINTOOLS (0x002f)
SFENTRY(-1), // CSIDL_ADMINTOOLS (0x0030)
SFENTRY(c_aidlConnections), // CSIDL_CONNECTIONS (0x0031)
SFENTRY(-1), // (0x0032)
SFENTRY(-1), // (0x0033)
SFENTRY(-1), // (0x0034)
SFENTRY(-1), // CSIDL_COMMON_MUSIC (0x0035)
SFENTRY(-1), // CSIDL_COMMON_PICTURES (0x0036)
SFENTRY(-1), // CSIDL_COMMON_VIDEO (0x0037)
SFENTRY(-1), // CSIDL_RESOURCES (0x0038)
SFENTRY(-1), // CSIDL_RESOURCES_LOCALIZED (0x0039)
SFENTRY(-1), // CSIDL_COMMON_OEM_LINKS (0x003a)
SFENTRY(-1), // CSIDL_CDBURN_AREA (0x003b)
SFENTRY(-1), // <unused> (0x003c)
SFENTRY(-1), // CSIDL_COMPUTERSNEARME (0x003d)
};
HRESULT _OpenKeyForFolder(const FOLDER_INFO *pfi, HANDLE hToken, LPCTSTR pszSubKey, HKEY *phkey);
void _UpdateShellFolderCache(void);
BOOL GetUserProfileDir(HANDLE hToken, TCHAR *pszPath);
HRESULT VerifyAndCreateFolder(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPTSTR pszPath) ;
#define _IsDefaultUserToken(hToken) ((HANDLE)-1 == hToken)
const FOLDER_INFO *_GetFolderInfo(int csidl)
{
const FOLDER_INFO *pfi;
// make sure g_aFolderCache can be indexed by the CSIDL values
COMPILETIME_ASSERT((ARRAYSIZE(g_aFolderCache) - 1) == CSIDL_COMPUTERSNEARME);
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
if (pfi->id == csidl)
return pfi;
}
return NULL;
}
// expand an individual enviornment variable
// in:
// pszVar "%USERPROFILE%
// pszValue "c:\winnt\profiles\user"
//
// in/out:
// pszToExpand in: %USERPROFILE%\My Docs", out: c:\winnt\profiles\user\My Docs"
BOOL ExpandEnvVar(LPCTSTR pszVar, LPCTSTR pszValue, LPTSTR pszToExpand)
{
TCHAR *pszStart = StrStrI(pszToExpand, pszVar);
if (pszStart)
{
TCHAR szAfter[MAX_PATH];
lstrcpy(szAfter, pszStart + lstrlen(pszVar)); // save the tail
lstrcpyn(pszStart, pszValue, (int) (MAX_PATH - (pszStart - pszToExpand)));
StrCatBuff(pszToExpand, szAfter, MAX_PATH); // put the tail back on
return TRUE;
}
return FALSE;
}
HANDLE GetCurrentUserToken()
{
HANDLE hToken;
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken) ||
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_IMPERSONATE, &hToken))
return hToken;
return NULL;
}
// like ExpandEnvironmentStrings but is robust to the enviornment variables
// not being set. this works on...
// %SYSTEMROOT%
// %SYSTEMDRIVE%
// %USERPROFILE%
// %ALLUSERSPROFILE%
//
// in the rare case (Winstone!) that there is a NULL enviornment block
DWORD ExpandEnvironmentStringsNoEnv(HANDLE hToken, LPCTSTR pszExpand, LPTSTR pszOut, UINT cchOut)
{
TCHAR szPath[MAX_PATH];
if (hToken && !_IsDefaultUserToken(hToken))
{
if (!ExpandEnvironmentStringsForUser(hToken, pszExpand, pszOut, cchOut))
lstrcpyn(pszOut, pszExpand, cchOut);
}
else if (hToken == NULL)
{
// to debug env expansion failure...
// lstrcpyn(pszOut, pszExpand, cchOut);
SHExpandEnvironmentStrings(pszExpand, pszOut, cchOut);
}
// manually expand in this order since
// %USERPROFILE% -> %SYSTEMDRIVE%\Docs & Settings
if (StrChr(pszOut, TEXT('%')) && (hToken == NULL))
{
hToken = GetCurrentUserToken();
if (hToken)
{
// this does %USERPROFILE% and other per user stuff
ExpandEnvironmentStringsForUser(hToken, pszExpand, pszOut, cchOut);
CloseHandle(hToken);
}
}
else if (_IsDefaultUserToken(hToken) && StrChr(pszOut, TEXT('%')))
{
GetUserProfileDir(hToken, szPath);
ExpandEnvVar(TEXT("%USERPROFILE%"), szPath, pszOut);
}
if (*pszOut == TEXT('%'))
{
GetAllUsersDirectory(szPath);
ExpandEnvVar(TEXT("%ALLUSERSPROFILE%"), szPath, pszOut);
}
if (*pszOut == TEXT('%'))
{
GetSystemWindowsDirectory(szPath, ARRAYSIZE(szPath));
ExpandEnvVar(TEXT("%SYSTEMROOT%"), szPath, pszOut);
}
if (*pszOut == TEXT('%'))
{
GetSystemWindowsDirectory(szPath, ARRAYSIZE(szPath));
ASSERT(szPath[1] == TEXT(':')); // this better not be a UNC!
szPath[2] = 0; // SYSTEMDRIVE = 'c:', not 'c:\'
ExpandEnvVar(TEXT("%SYSTEMDRIVE%"), szPath, pszOut);
}
if (*pszOut == TEXT('%'))
*pszOut = 0;
return lstrlen(pszOut) + 1; // +1 to cover the NULL
}
// get the user profile directory:
// uses the hToken as needed to determine the proper user profile
BOOL GetUserProfileDir(HANDLE hToken, TCHAR *pszPath)
{
DWORD dwcch = MAX_PATH;
HANDLE hClose = NULL;
BOOL fRet;
*pszPath = 0; // in case of error
if (!hToken)
{
hClose = hToken = GetCurrentUserToken();
}
if (_IsDefaultUserToken(hToken))
{
fRet = GetDefaultUserProfileDirectory(pszPath, &dwcch);
}
else
{
fRet = GetUserProfileDirectory(hToken, pszPath, &dwcch);
}
if (hClose)
{
CloseHandle(hClose);
}
return fRet;
}
#ifdef WX86
void SetUseKnownWx86Dll(const FOLDER_INFO *pfi, BOOL bValue)
{
if (pfi->dwFlags & SDIF_WX86)
{
// GetSystemDirectory() knows we're looking for the Wx86 system
// directory when this flag is set.
NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = bValue ? TRUE : FALSE;
}
}
#else
#define SetUseKnownWx86Dll(pfi, bValue)
#endif
// read from registry
BOOL GetProgramFiles(LPCTSTR pszValue, LPTSTR pszPath)
{
DWORD cbPath = MAX_PATH * sizeof(*pszPath);
*pszPath = 0;
SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion"),
pszValue, NULL, pszPath, &cbPath);
return (BOOL)*pszPath;
}
LPTSTR GetFontsDirectory(LPTSTR pszPath)
{
if (GetWindowsDirectory(pszPath, MAX_PATH))
{
PathAppend(pszPath, TEXT("Fonts"));
}
return pszPath;
}
void LoadDefaultString(int idString, LPTSTR lpBuffer, int cchBufferMax)
{
BOOL fSucceeded = FALSE;
HRSRC hResInfo;
HANDLE hStringSeg;
LPWSTR lpsz;
int cch;
HMODULE hmod = GetModuleHandle(TEXT("SHELL32"));
// Make sure the parms are valid.
if (lpBuffer == NULL || cchBufferMax == 0)
{
return;
}
cch = 0;
// String Tables are broken up into 16 string segments. Find the segment
// containing the string we are interested in.
if (hResInfo = FindResourceExW(hmod, (LPCWSTR)RT_STRING,
(LPWSTR)((LONG_PTR)(((USHORT)idString >> 4) + 1)), GetSystemDefaultUILanguage()))
{
// Load that segment.
hStringSeg = LoadResource(hmod, hResInfo);
// Lock the resource.
if (lpsz = (LPWSTR)LockResource(hStringSeg))
{
// Move past the other strings in this segment.
// (16 strings in a segment -> & 0x0F)
idString &= 0x0F;
while (TRUE)
{
cch = *((WORD *)lpsz++); // PASCAL like string count
// first UTCHAR is count if TCHARs
if (idString-- == 0) break;
lpsz += cch; // Step to start if next string
}
// Account for the NULL
cchBufferMax--;
// Don't copy more than the max allowed.
if (cch > cchBufferMax)
cch = cchBufferMax;
// Copy the string into the buffer.
CopyMemory(lpBuffer, lpsz, cch*sizeof(WCHAR));
// Attach Null terminator.
lpBuffer[cch] = 0;
fSucceeded = TRUE;
}
}
if (!fSucceeded)
{
LoadString(HINST_THISDLL, idString, lpBuffer, cchBufferMax);
}
}
BOOL GetLocalSettingsDir(HANDLE hToken, LPTSTR pszPath)
{
*pszPath = 0;
GetUserProfileDir(hToken, pszPath);
if (*pszPath)
{
TCHAR szEntry[MAX_PATH];
LoadDefaultString(IDS_LOCALSETTINGS, szEntry, ARRAYSIZE(szEntry));
PathAppend(pszPath, szEntry);
}
return *pszPath ? TRUE : FALSE;
}
HRESULT GetResourcesDir(IN BOOL fLocalized, IN LPTSTR pszPath, IN DWORD cchSize)
{
HRESULT hr = E_FAIL;
TCHAR szTemp[MAX_PATH];
RIP(IS_VALID_WRITE_BUFFER(pszPath, TCHAR, cchSize));
pszPath[0] = 0; // Terminate in case we fail.
if (SHGetSystemWindowsDirectory(szTemp, ARRAYSIZE(szTemp)))
{
// It's now "%windir%\resources\".
PathAppend(szTemp, TEXT("resources"));
if (fLocalized)
{
LANGID lidUI = GetUserDefaultUILanguage();
TCHAR szSubDir[10];
// Now make it "%windir%\resources\<LangID>\"
wnsprintfW(szSubDir, ARRAYSIZE(szSubDir), TEXT("%04x"), lidUI);
PathAppend(szTemp, szSubDir);
}
StrCpyN(pszPath, szTemp, cchSize);
hr = S_OK;
}
return hr;
}
// out:
// pszPath fills in with the full path with no env gunk (MAX_PATH)
HRESULT _GetFolderDefaultPath(const FOLDER_INFO *pfi, HANDLE hToken, LPTSTR pszPath)
{
ASSERT(!(pfi->dwFlags & SDIF_NOT_FILESYS)); // speical folders should not come here
*pszPath = 0;
TCHAR szEntry[MAX_PATH];
switch (pfi->id)
{
case CSIDL_PROFILE:
GetUserProfileDir(hToken, pszPath);
break;
case CSIDL_PROGRAM_FILES:
GetProgramFiles(TEXT("ProgramFilesDir"), pszPath);
break;
case CSIDL_PROGRAM_FILES_COMMON:
GetProgramFiles(TEXT("CommonFilesDir"), pszPath);
break;
case CSIDL_PROGRAM_FILESX86:
GetProgramFiles(TEXT("ProgramFilesDir (x86)"), pszPath);
break;
case CSIDL_PROGRAM_FILES_COMMONX86:
GetProgramFiles(TEXT("CommonFilesDir (x86)"), pszPath);
break;
#ifdef _WIN64
case CSIDL_SYSTEMX86:
//
// downlevel systems do not have GetSystemWindowsDirectory export,
// but shell thunking layer handles this gracefully
GetSystemWindowsDirectory(pszPath, MAX_PATH);
//
// tack on subdirectory
//
PathCombine(pszPath, pszPath, TEXT(WOW64_SYSTEM_DIRECTORY));
break;
#else
case CSIDL_SYSTEMX86:
#endif
case CSIDL_SYSTEM:
GetSystemDirectory(pszPath, MAX_PATH);
break;
case CSIDL_WINDOWS:
GetWindowsDirectory(pszPath, MAX_PATH);
break;
case CSIDL_RESOURCES:
GetResourcesDir(FALSE, pszPath, MAX_PATH);
break;
case CSIDL_RESOURCES_LOCALIZED:
GetResourcesDir(TRUE, pszPath, MAX_PATH);
break;
case CSIDL_COMPUTERSNEARME:
// no path for this
break;
case CSIDL_FONTS:
GetFontsDirectory(pszPath);
break;
default:
switch (pfi->dwFlags & SDIF_CREATE_IN_MASK)
{
case SDIF_CREATE_IN_ROOT:
GetWindowsDirectory(pszPath, MAX_PATH);
PathStripToRoot(pszPath);
break;
case SDIF_CREATE_IN_ALLUSERS:
GetAllUsersDirectory(pszPath);
break;
case SDIF_CREATE_IN_WINDIR:
GetWindowsDirectory(pszPath, MAX_PATH);
break;
case SDIF_CREATE_IN_MYDOCUMENTS:
// 99/10/21 Mil#104600: When asking for folders in "My Documents" don't
// verify their existance. Just return the path. The caller will make
// the decision to create the folder or not.
// on failure *pszPath will be empty
SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_DONT_VERIFY, hToken, SHGFP_TYPE_CURRENT, pszPath);
break;
case SDIF_CREATE_IN_LOCALSET:
GetLocalSettingsDir(hToken, pszPath);
break;
default:
GetUserProfileDir(hToken, pszPath);
break;
}
if (*pszPath)
{
LoadDefaultString(pfi->idsDefault, szEntry, ARRAYSIZE(szEntry));
PathAppend(pszPath, szEntry);
}
break;
}
return *pszPath ? S_OK : E_FAIL;
}
void RegSetFolderPath(const FOLDER_INFO *pfi, LPCTSTR pszSubKey, LPCTSTR pszPath)
{
HKEY hk;
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, pszSubKey, &hk)))
{
if (pszPath)
RegSetValueEx(hk, pfi->pszValueName, 0, REG_SZ, (LPBYTE)pszPath, (1 + lstrlen(pszPath)) * sizeof(TCHAR));
else
RegDeleteValue(hk, pfi->pszValueName);
RegCloseKey(hk);
}
}
BOOL RegQueryPath(HKEY hk, LPCTSTR pszValue, LPTSTR pszPath)
{
DWORD cbPath = MAX_PATH * sizeof(TCHAR);
*pszPath = 0;
SHQueryValueEx(hk, pszValue, 0, NULL, pszPath, &cbPath);
return (BOOL)*pszPath;
}
// More than 50 is silly
#define MAX_TEMP_FILE_TRIES 50
// returns:
// S_OK the path exists and it is a folder
// FAILED() result
HRESULT _IsFolderNotFile(LPCTSTR pszFolder)
{
HRESULT hr;
DWORD dwAttribs = GetFileAttributes(pszFolder);
if (dwAttribs == -1)
{
DWORD err = GetLastError();
hr = HRESULT_FROM_WIN32(err);
}
else
{
// see if it is a file, if so we need to rename that file
if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
{
hr = S_OK;
}
else
{
int iExt = 0;
do
{
TCHAR szExt[32], szDst[MAX_PATH];
wsprintf(szExt, TEXT(".%03d"), iExt);
lstrcpy(szDst, pszFolder);
lstrcat(szDst, szExt);
if (MoveFile(pszFolder, szDst))
iExt = 0;
else
{
// Normally we fail because .00x already exists but that may not be true.
DWORD dwError = GetLastError();
if (ERROR_ALREADY_EXISTS == dwError)
iExt++; // Try the next one...
else
iExt = 0; // We have problems and need to give up. (No write access?)
}
} while (iExt && (iExt < MAX_TEMP_FILE_TRIES));
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
}
return hr;
}
HRESULT _OpenKeyForFolder(const FOLDER_INFO *pfi, HANDLE hToken, LPCTSTR pszSubKey, HKEY *phkey)
{
TCHAR szRegPath[255];
LONG err;
HKEY hkRoot, hkeyToFree = NULL;
*phkey = NULL;
lstrcpy(szRegPath, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"));
lstrcat(szRegPath, pszSubKey);
if (_IsDefaultUserToken(hToken) && (pfi->hKey == HKEY_CURRENT_USER))
{
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_USERS, TEXT(".Default"), 0, KEY_READ, &hkRoot))
hkeyToFree = hkRoot;
else
return E_FAIL;
}
else if (hToken && (pfi->hKey == HKEY_CURRENT_USER))
{
if (GetUserProfileKey(hToken, &hkRoot))
hkeyToFree = hkRoot;
else
return E_FAIL;
}
else
hkRoot = pfi->hKey;
err = RegCreateKeyEx(hkRoot, szRegPath, 0, NULL, REG_OPTION_NON_VOLATILE,
MAXIMUM_ALLOWED, NULL, phkey, NULL);
if (hkeyToFree)
RegCloseKey(hkeyToFree);
return HRESULT_FROM_WIN32(err);
}
//
// Roaming Profiles can set up the environment variables and registry
// keys like so:
//
// HOMESHARE=\\server\share\user
// HOMEPATH=\
// My Music=%HOMESHARE%%HOMEPATH%\My Music
//
// so you end up with "\\server\share\user\\My Music", which is an
// invalid path. Clean them up; otherwise SHGetSpecialFolderLocation will
// fail.
//
void _CleanExpandedEnvironmentPath(LPTSTR szExpand)
{
// Search for "\\" at a location other than the start of the string.
// If found, collapse it.
LPTSTR pszWhackWhack;
while (lstrlen(szExpand) > 2 &&
(pszWhackWhack = StrStr(szExpand+1, TEXT("\\\\"))))
{
StrCpy(pszWhackWhack+1, pszWhackWhack+2);
}
}
// returns:
// S_OK found in registry, path well formed
// S_FALSE empty registry
// FAILED() failure result
HRESULT _GetFolderFromReg(const FOLDER_INFO *pfi, HANDLE hToken, LPTSTR pszPath)
{
HKEY hkUSF;
HRESULT hr;
*pszPath = 0;
hr = _OpenKeyForFolder(pfi, hToken, TEXT("User Shell Folders"), &hkUSF);
if (SUCCEEDED(hr))
{
TCHAR szExpand[MAX_PATH];
DWORD dwType, cbPath = sizeof(szExpand);
if (RegQueryValueEx(hkUSF, pfi->pszValueName, 0, &dwType, (BYTE *)szExpand, &cbPath) == ERROR_SUCCESS)
{
if (REG_SZ == dwType)
{
lstrcpyn(pszPath, szExpand, MAX_PATH);
}
else if (REG_EXPAND_SZ == dwType)
{
ExpandEnvironmentStringsNoEnv(hToken, szExpand, pszPath, MAX_PATH);
_CleanExpandedEnvironmentPath(pszPath);
}
TraceMsg(TF_PATH, "_CreateFolderPath 'User Shell Folders' %s = %s", pfi->pszValueName, pszPath);
}
if (*pszPath == 0)
{
hr = S_FALSE; // empty registry, success but empty
}
else if ((PathGetDriveNumber(pszPath) != -1) || PathIsUNC(pszPath))
{
hr = S_OK; // good reg path, fully qualified
}
else
{
*pszPath = 0; // bad reg data
hr = E_INVALIDARG;
}
RegCloseKey(hkUSF);
}
return hr;
}
HRESULT _GetFolderPath(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPTSTR pszPath)
{
HRESULT hr;
*pszPath = 0; // assume failure
if (pfi->hKey)
{
hr = _GetFolderFromReg(pfi, hToken, pszPath);
if (SUCCEEDED(hr))
{
if (hr == S_FALSE)
{
// empty registry, SDIF_EMPTY_IF_NOT_IN_REG means they don't exist
// if the registry is not populated with a value. this lets us disable
// the common items on platforms that don't want them
if (pfi->dwFlags & SDIF_EMPTY_IF_NOT_IN_REG)
return S_FALSE; // success, but empty
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
}
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
{
hr = VerifyAndCreateFolder(hwnd, pfi, uFlags, pszPath) ;
}
if (hr != S_OK)
{
*pszPath = 0;
}
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
{
HKEY hkey;
// record value in "Shell Folders", even in the failure case
// NOTE: we only do this for historical reasons. there may be some
// apps that depend on these values being in the registry, but in general
// the contetens here are unreliable as they are only written after someone
// asks for the folder through this API.
if (SUCCEEDED(_OpenKeyForFolder(pfi, hToken, TEXT("Shell Folders"), &hkey)))
{
RegSetValueEx(hkey, pfi->pszValueName, 0, REG_SZ, (LPBYTE)pszPath, (1 + lstrlen(pszPath)) * sizeof(TCHAR));
RegCloseKey(hkey);
}
}
}
}
else
{
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
if ((S_OK == hr) && !(uFlags & CSIDL_FLAG_DONT_VERIFY))
{
hr = VerifyAndCreateFolder(hwnd, pfi, uFlags, pszPath);
}
if (hr != S_OK)
{
*pszPath = 0;
}
}
ASSERT(hr == S_OK ? *pszPath != 0 : *pszPath == 0);
return hr;
}
void _PostCreateStuff(const FOLDER_INFO *pfi, LPTSTR pszPath, BOOL fUpgrade)
{
if (pfi->pfnInit || pfi->idsLocalizedName || (pfi->dwFlags & SDIF_PERSONALIZED))
{
if (fUpgrade)
{
// if we are upgrading, torch all our previous meta data
TCHAR sz[MAX_PATH];
PathCombine(sz, pszPath, TEXT("desktop.ini"));
if (PathFileExistsAndAttributes(sz, NULL))
{
WritePrivateProfileSection(TEXT(".ShellClassInfo"), NULL, sz);
// in the upgrade case, sometimes the desktop.ini
// file was there but the folder wasnt marked.
// insure that it is marked.
PathMakeSystemFolder(pszPath);
}
}
// now call the create proc if we have one
if (pfi->pfnInit)
pfi->pfnInit(pfi->id, pszPath);
// does the table specify a localized resource name that we should be
// using for this object?
if (pfi->idsLocalizedName)
SHSetLocalizedName(pszPath, TEXT("shell32.dll"), pfi->idsLocalizedName);
// do we need to store the user name for this folder?
if (pfi->dwFlags & SDIF_PERSONALIZED)
{
TCHAR szName[UNLEN+1];
DWORD dwName = ARRAYSIZE(szName);
if (GetUserName(szName, &dwName))
{
// CSharedDocuments depends on a per system list of MyDocs folders
// this is where we make sure that list is setup
if (!IsOS(OS_DOMAINMEMBER) && (pfi->id == CSIDL_PERSONAL))
{
SKSetValue(SHELLKEY_HKLM_EXPLORER, L"DocFolderPaths",
szName, REG_SZ, pszPath, (lstrlen(pszPath) + 1) * sizeof(TCHAR));
}
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("Owner"), szName);
wsprintf(szName, L"%d", pfi->id);
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", TEXT("Personalized"), szName);
LoadDefaultString(pfi->idsDefault, szName, ARRAYSIZE(szName));
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("PersonalizedName"), szName);
}
}
}
}
HRESULT VerifyAndCreateFolder(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPTSTR pszPath)
{
HRESULT hr = _IsFolderNotFile(pszPath);
// this code supports a UI mode of this API. but generally this is not used
// this code should be removed
if ((hr != S_OK) && hwnd)
{
// we might be able to reconnect if this is a net path
if (PathIsUNC(pszPath))
{
if (SHValidateUNC(hwnd, pszPath, 0))
hr = _IsFolderNotFile(pszPath);
}
else if (IsDisconnectedNetDrive(DRIVEID(pszPath)))
{
TCHAR szDrive[3];
PathBuildSimpleRoot(DRIVEID(pszPath), szDrive);
if (WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS)
hr = _IsFolderNotFile(pszPath);
}
}
// to avoid a sequence of long net timeouts or calls we know won't
// succeed test for these specific errors and don't try to create
// the folder
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) ||
hr == HRESULT_FROM_WIN32(ERROR_BAD_NETPATH))
{
return hr;
}
if ((hr != S_OK) && (uFlags & CSIDL_FLAG_CREATE))
{
DWORD err = SHCreateDirectory(NULL, pszPath);
hr = HRESULT_FROM_WIN32(err);
if (hr == S_OK)
{
ASSERT(NULL == StrChr(pszPath, TEXT('%')));
if (pfi->dwFlags & SDIF_HIDE)
SetFileAttributes(pszPath, GetFileAttributes(pszPath) | FILE_ATTRIBUTE_HIDDEN);
_PostCreateStuff(pfi, pszPath, FALSE);
}
}
else if (hr == S_OK)
{
if (uFlags & CSIDL_FLAG_PER_USER_INIT)
_PostCreateStuff(pfi, pszPath, TRUE);
}
return hr;
}
void _SetPathCache(const FOLDER_INFO *pfi, LPCTSTR psz)
{
LPTSTR pszOld = (LPTSTR)InterlockedExchangePointer((void **)&g_aFolderCache[pfi->id].psz, (void *)psz);
if (pszOld && pszOld != (LPTSTR)-1)
{
// check for the concurent use... very rare case
LocalFree(pszOld);
}
}
HRESULT _GetFolderPathCached(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPTSTR pszPath)
{
HRESULT hr;
*pszPath = 0;
// can only cache for the current user, hToken == NULL or per machine folders
if (!hToken || (pfi->hKey != HKEY_CURRENT_USER))
{
_UpdateShellFolderCache();
LPTSTR pszCache = (LPTSTR)InterlockedExchangePointer((void **)&g_aFolderCache[pfi->id].psz, (void *)-1);
if ((pszCache == (LPTSTR)-1) || (pszCache == NULL))
{
// either not cached or cached failed state
if ((pszCache == (LPTSTR)-1) || (uFlags & (CSIDL_FLAG_CREATE | CSIDL_FLAG_DONT_VERIFY)))
{
hr = _GetFolderPath(hwnd, pfi, hToken, uFlags, pszPath);
// only set the cache value if CSIDL_FLAG_DONT_VERIFY was NOT passed
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
{
if (hr == S_OK)
{
// dupe the string so we can add it to the cache
pszCache = StrDup(pszPath);
}
else
{
// we failed to get the folder path, null out the cache
ASSERT(*pszPath == 0);
pszCache = NULL;
}
_SetPathCache(pfi, pszCache);
}
}
else
{
// cache was null and user didnt pass create flag so we just fail
ASSERT(pszCache == NULL);
ASSERT(*pszPath == 0);
hr = E_FAIL;
}
}
else
{
// cache hit case: copy the cached string and then restore the cached value back
lstrcpyn(pszPath, pszCache, MAX_PATH);
_SetPathCache(pfi, pszCache);
hr = S_OK;
}
}
else
{
hr = _GetFolderPath(hwnd, pfi, hToken, uFlags, pszPath);
}
return hr;
}
// NOTE: possibly we need a csidlSkip param to avoid recursion?
BOOL _ReparentAliases(HWND hwnd, HANDLE hToken, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlAlias, DWORD dwXlateAliases)
{
static const struct {DWORD dwXlate; int idPath; int idAlias; BOOL fCommon;} s_rgidAliases[]=
{
{ XLATEALIAS_MYDOCS, CSIDL_PERSONAL | CSIDL_FLAG_NO_ALIAS, CSIDL_PERSONAL, FALSE},
{ XLATEALIAS_COMMONDOCS, CSIDL_COMMON_DOCUMENTS | CSIDL_FLAG_NO_ALIAS, CSIDL_COMMON_DOCUMENTS, FALSE},
{ XLATEALIAS_DESKTOP, CSIDL_DESKTOPDIRECTORY, CSIDL_DESKTOP, FALSE},
{ XLATEALIAS_DESKTOP, CSIDL_COMMON_DESKTOPDIRECTORY, CSIDL_DESKTOP, TRUE},
};
BOOL fContinue = TRUE;
*ppidlAlias = NULL;
for (int i = 0; fContinue && i < ARRAYSIZE(s_rgidAliases); i++)
{
LPITEMIDLIST pidlPath;
if ((dwXlateAliases & s_rgidAliases[i].dwXlate) &&
(S_OK == SHGetFolderLocation(hwnd, s_rgidAliases[i].idPath, hToken, 0, &pidlPath)))
{
LPCITEMIDLIST pidlChild = ILFindChild(pidlPath, pidl);
if (pidlChild)
{
// ok we need to use the alias instead of the path
LPITEMIDLIST pidlAlias;
if (S_OK == SHGetFolderLocation(hwnd, s_rgidAliases[i].idAlias, hToken, 0, &pidlAlias))
{
if (SUCCEEDED(SHILCombine(pidlAlias, pidlChild, ppidlAlias)))
{
if (s_rgidAliases[i].fCommon && !ILIsEmpty(*ppidlAlias))
{
// find the size of the special part (subtacting for null pidl terminator)
UINT cbSize = ILGetSize(pidlAlias) - sizeof(pidlAlias->mkid.cb);
LPITEMIDLIST pidlChildFirst = _ILSkip(*ppidlAlias, cbSize);
// We set the first ID under the common path to have the SHID_FS_COMMONITEM so that when we bind we
// can hand this to the proper merged psf
pidlChildFirst->mkid.abID[0] |= SHID_FS_COMMONITEM;
}
ILFree(pidlAlias);
}
fContinue = FALSE;
}
}
ILFree(pidlPath);
}
}
return (*ppidlAlias != NULL);
}
STDAPI SHILAliasTranslate(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlAlias, DWORD dwXlateAliases)
{
return _ReparentAliases(NULL, NULL, pidl, ppidlAlias, dwXlateAliases) ? S_OK : E_FAIL;
}
HRESULT _CreateFolderIDList(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPITEMIDLIST *ppidl)
{
HRESULT hr = S_OK;
*ppidl = NULL; // assume failure or empty
if (pfi->id == CSIDL_PRINTERS && (ACF_STAROFFICE5PRINTER & SHGetAppCompatFlags(ACF_STAROFFICE5PRINTER)))
{
// Star Office 5.0 relies on the fact that the printer pidl used to be like below. They skip the
// first simple pidl (My Computer) and do not check if there is anything else, they assume that the
// second simple pidl is the Printer folder one. (stephstm, 07/30/99)
// CLSID_MyComputer, CLSID_Printers
hr = ILCreateFromPathEx(TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{2227A280-3AEA-1069-A2DE-08002B30309D}"), NULL, ILCFP_FLAG_NO_MAP_ALIAS, ppidl, NULL);
}
else if (pfi->id == CSIDL_COMPUTERSNEARME)
{
if (IsOS(OS_DOMAINMEMBER))
{
// only if you are in a workgroup - fail otherwise
hr = E_FAIL;
}
else
{
// we computer this IDLIST from the domain/workgroup you are a member of
hr = SHGetDomainWorkgroupIDList(ppidl);
}
}
else if ((pfi->id == CSIDL_COMMON_DOCUMENTS)
&& !(uFlags & CSIDL_FLAG_NO_ALIAS))
{
// CLSID_MyComputer \ SharedDocumnets (canonical name)
hr = ILCreateFromPathEx(TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{59031a47-3f72-44a7-89c5-5595fe6b30ee},SharedDocuments"), NULL, ILCFP_FLAG_NO_MAP_ALIAS, ppidl, NULL);
}
else if ((pfi->dwFlags & SDIF_CONST_IDLIST)
&& (!(uFlags & CSIDL_FLAG_NO_ALIAS) || !(pfi->dwFlags & SDIF_MAYBE_ALIASED)))
{
// these are CONST, never change
hr = SHILClone(g_aFolderCache[pfi->id].pidl, ppidl);
}
else
{
TCHAR szPath[MAX_PATH];
hr = _GetFolderPathCached(hwnd, pfi, hToken, uFlags, szPath);
if (hr == S_OK)
{
HRESULT hrInit = SHCoInitialize();
hr = ILCreateFromPathEx(szPath, NULL, ILCFP_FLAG_SKIPJUNCTIONS, ppidl, NULL);
// attempt to reparent aliased pidls.
if (SUCCEEDED(hr)
&& (pfi->dwFlags & SDIF_MAYBE_ALIASED)
&& !(uFlags & CSIDL_FLAG_NO_ALIAS))
{
LPITEMIDLIST pidlAlias;
if (_ReparentAliases(hwnd, hToken, *ppidl, &pidlAlias, XLATEALIAS_ALL))
{
ILFree(*ppidl);
*ppidl = pidlAlias;
}
}
SHCoUninitialize(hrInit);
}
}
return hr;
}
void _SetIDListCache(const FOLDER_INFO *pfi, LPCITEMIDLIST pidl, BOOL fNonAlias)
{
if (fNonAlias || !(pfi->dwFlags & SDIF_CONST_IDLIST))
{
void **ppv = (void **) (fNonAlias ? &g_aFolderCache[pfi->id].pidlNonAlias : &g_aFolderCache[pfi->id].pidl);
LPITEMIDLIST pidlOld = (LPITEMIDLIST)InterlockedExchangePointer(ppv, (void *)pidl);
if (pidlOld && pidlOld != (LPITEMIDLIST)-1)
{
// check for the concurent use... very rare case
// ASSERT(pidl == (LPCITEMIDLIST)-1); // should not really be ASSERT
ILFree(pidlOld);
}
}
}
LPITEMIDLIST _GetIDListCache(const FOLDER_INFO *pfi, BOOL fNonAlias)
{
void **ppv = (void **) (fNonAlias ? &g_aFolderCache[pfi->id].pidlNonAlias : &g_aFolderCache[pfi->id].pidl);
ASSERT(fNonAlias || !(pfi->dwFlags & SDIF_CONST_IDLIST));
return (LPITEMIDLIST)InterlockedExchangePointer(ppv, (void *)-1);
}
// hold this lock for the minimal amout of time possible to avoid other users
// of this resource requring them to re-create the pidl
HRESULT _GetFolderIDListCached(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPITEMIDLIST *ppidl)
{
HRESULT hr;
BOOL fNonAlias = uFlags & CSIDL_FLAG_NO_ALIAS;
ASSERT(pfi->id < ARRAYSIZE(g_aFolderCache));
if ((pfi->dwFlags & SDIF_CONST_IDLIST) &&
(!fNonAlias || !(pfi->dwFlags & SDIF_MAYBE_ALIASED)))
{
// these are CONST, never change
hr = SHILClone(g_aFolderCache[pfi->id].pidl, ppidl);
}
else
{
LPITEMIDLIST pidlCache;
_UpdateShellFolderCache();
pidlCache = _GetIDListCache(pfi, fNonAlias);
if ((pidlCache == (LPCITEMIDLIST)-1) || (pidlCache == NULL))
{
// either uninitalized cache state OR cached failure (NULL)
if ((pidlCache == (LPCITEMIDLIST)-1) || (uFlags & CSIDL_FLAG_CREATE))
{
// not initialized (or concurent use) try creating it for this use
hr = _CreateFolderIDList(hwnd, pfi, NULL, uFlags, ppidl);
if (S_OK == hr)
hr = SHILClone(*ppidl, &pidlCache); // create cache copy
else
pidlCache = NULL;
}
else
hr = E_FAIL; // return cached failure
}
else
{
hr = SHILClone(pidlCache, ppidl); // cache hit
}
// store back the PIDL if it is non NULL or they specified CREATE
// and we failed to create it (cache the not existant state). this is needed
// so we don't cache a NULL if the first callers don't ask for create and
// subsequent callers do
if (pidlCache || (uFlags & CSIDL_FLAG_CREATE))
_SetIDListCache(pfi, pidlCache, fNonAlias);
}
return hr;
}
void _ClearCacheEntry(const FOLDER_INFO *pfi)
{
if (!(pfi->dwFlags & SDIF_CONST_IDLIST))
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, FALSE);
if (pfi->dwFlags & SDIF_MAYBE_ALIASED)
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, TRUE);
_SetPathCache(pfi, (LPCTSTR)-1);
}
void _ClearAllCacheEntrys()
{
for (const FOLDER_INFO *pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
_ClearCacheEntry(pfi);
}
}
void _ClearAllAliasCacheEntrys()
{
for (const FOLDER_INFO *pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
if (pfi->dwFlags & SDIF_MAYBE_ALIASED)
{
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, FALSE); // nuke the aliased pidl
}
}
}
// Per instance count of mods to Special Folder cache.
EXTERN_C HANDLE g_hCounter;
HANDLE g_hCounter = NULL; // Global count of mods to Special Folder cache.
int g_lPerProcessCount = 0;
// Make sure the special folder cache is up to date.
void _UpdateShellFolderCache(void)
{
HANDLE hCounter = SHGetCachedGlobalCounter(&g_hCounter, &GUID_SystemPidlChange);
// Is the cache up to date?
long lGlobalCount = SHGlobalCounterGetValue(hCounter);
if (lGlobalCount != g_lPerProcessCount)
{
_ClearAllCacheEntrys();
g_lPerProcessCount = lGlobalCount;
}
}
STDAPI_(void) SHFlushSFCache(void)
{
// Increment the shared variable; the per-process versions will no
// longer match, causing this and/or other processes to refresh their
// pidl caches when they next need to access a folder.
if (g_hCounter)
SHGlobalCounterIncrement(g_hCounter);
}
// use SHGetFolderLocation() instead using CSIDL_FLAG_CREATE
STDAPI_(LPITEMIDLIST) SHCloneSpecialIDList(HWND hwnd, int csidl, BOOL fCreate)
{
LPITEMIDLIST pidlReturn;
if (fCreate)
csidl |= CSIDL_FLAG_CREATE;
SHGetSpecialFolderLocation(hwnd, csidl, &pidlReturn);
return pidlReturn;
}
STDAPI SHGetSpecialFolderLocation(HWND hwnd, int csidl, LPITEMIDLIST *ppidl)
{
HRESULT hr = SHGetFolderLocation(hwnd, csidl, NULL, 0, ppidl);
if (hr == S_FALSE)
hr = E_FAIL; // mail empty case into failure for compat with this API
return hr;
}
// return IDLIST for special folder
// fCreate encoded in csidl with CSIDL_FLAG_CREATE (new for NT5)
//
// in:
// hwnd should be NULL
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
// dwType must be SHGFP_TYPE_CURRENT
//
// out:
// *ppild NULL on failure or empty, PIDL to be freed by caller on success
//
// returns:
// S_OK *ppidl is non NULL
// S_FALISE *ppidl is NULL, but valid csidl was passed (folder does not exist)
// FAILED(hr)
STDAPI SHGetFolderLocation(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPITEMIDLIST *ppidl)
{
const FOLDER_INFO *pfi;
HRESULT hr;
*ppidl = NULL; // in case of error or empty
// -1 is an invalid csidl
if ((dwType != SHGFP_TYPE_CURRENT) || (-1 == csidl))
return E_INVALIDARG; // no flags used yet, validate this param
pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
if (pfi)
{
HANDLE hTokenToFree = NULL;
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
{
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
hTokenToFree = hToken;
}
if (hToken && (pfi->hKey == HKEY_CURRENT_USER))
{
// we don't cache PIDLs for other users, do all of the work
hr = _CreateFolderIDList(hwnd, pfi, hToken, csidl & CSIDL_FLAG_MASK, (LPITEMIDLIST *)ppidl);
}
else
{
hr = _GetFolderIDListCached(hwnd, pfi, csidl & CSIDL_FLAG_MASK, ppidl);
}
if (hTokenToFree)
CloseHandle(hTokenToFree);
}
else
hr = E_INVALIDARG; // bad CSIDL (apps can check to veryify our support)
return hr;
}
STDAPI_(BOOL) SHGetSpecialFolderPath(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate)
{
if (fCreate)
csidl |= CSIDL_FLAG_CREATE;
return SHGetFolderPath(hwnd, csidl, NULL, 0, pszPath) == S_OK;
}
// in:
// hwnd should be NULL
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
// dwType must be SHGFP_TYPE_CURRENT
//
// out:
// *pszPath MAX_PATH buffer to get path name, zeroed on failure or empty case
//
// returns:
// S_OK filled in pszPath with path value
// S_FALSE pszPath is NULL, valid CSIDL value, but this folder does not exist
// E_FAIL
STDAPI SHGetFolderPath(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPWSTR pszPath)
{
HRESULT hr = E_INVALIDARG;
const FOLDER_INFO *pfi;
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH));
*pszPath = 0;
pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
if (pfi && !(pfi->dwFlags & SDIF_NOT_FILESYS))
{
switch (dwType)
{
case SHGFP_TYPE_DEFAULT:
ASSERT((csidl & CSIDL_FLAG_MASK) == 0); // meaningless for default
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
break;
case SHGFP_TYPE_CURRENT:
{
HANDLE hTokenToFree = NULL;
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
{
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
hTokenToFree = hToken;
}
hr = _GetFolderPathCached(hwnd, pfi, hToken, csidl & CSIDL_FLAG_MASK, pszPath);
if (hTokenToFree)
CloseHandle(hTokenToFree);
}
break;
}
}
return hr;
}
STDAPI SHGetFolderPathA(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPSTR pszPath)
{
WCHAR wsz[MAX_PATH];
HRESULT hr = SHGetFolderPath(hwnd, csidl, hToken, dwType, wsz);
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, CHAR, MAX_PATH));
SHUnicodeToAnsi(wsz, pszPath, MAX_PATH);
return hr;
}
STDAPI_(BOOL) SHGetSpecialFolderPathA(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate)
{
if (fCreate)
csidl |= CSIDL_FLAG_CREATE;
return SHGetFolderPathA(hwnd, csidl, NULL, 0, pszPath) == S_OK;
}
// Similar to SHGetFolderPath, but appends an optional subdirectory path after
// the csidl folder path. Handles creating the subdirectories.
STDAPI SHGetFolderPathAndSubDir(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPCWSTR pszSubDir, LPWSTR pszPath)
{
HRESULT hr = SHGetFolderPath(hwnd, csidl, hToken, dwFlags, pszPath);
if (S_OK == hr && pszSubDir && *pszSubDir)
{
if (!PathAppend(pszPath, pszSubDir))
{
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
}
else if (csidl & CSIDL_FLAG_CREATE)
{
int err = SHCreateDirectoryEx(NULL, pszPath, NULL);
if (ERROR_ALREADY_EXISTS == err)
{
err = ERROR_SUCCESS;
}
hr = HRESULT_FROM_WIN32(err);
}
else if (!(csidl & CSIDL_FLAG_DONT_VERIFY))
{
DWORD dwAttributes;
if (PathFileExistsAndAttributes(pszPath, &dwAttributes))
{
if ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS);
}
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
}
if (S_OK != hr)
{
*pszPath = 0;
}
}
return hr;
}
STDAPI SHGetFolderPathAndSubDirA(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPCSTR pszSubDir, LPSTR pszPath)
{
WCHAR wsz[MAX_PATH];
WCHAR wszSubDir[MAX_PATH];
SHAnsiToUnicode(pszSubDir, wszSubDir, MAX_PATH);
HRESULT hr = SHGetFolderPathAndSubDir(hwnd, csidl, hToken, dwFlags, wszSubDir, wsz);
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, CHAR, MAX_PATH));
SHUnicodeToAnsi(wsz, pszPath, MAX_PATH);
return hr;
}
// HRESULT SHSetFolderPath (int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)
//
// in:
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
// dwFlags reserved: should be 0x00000000
// pszPath path to change shell folder to (will optionally be unexpanded)
//
// returns:
// S_OK function succeeded and flushed cache
STDAPI SHSetFolderPath(int csidl, HANDLE hToken, DWORD dwFlags, LPCTSTR pszPath)
{
HRESULT hr = E_INVALIDARG;
// Validate csidl and dwFlags. Add extra valid flags as needed.
RIPMSG(((csidl & CSIDL_FLAG_MASK) & ~(CSIDL_FLAG_DONT_UNEXPAND | 0x00000000)) == 0, "SHSetFolderPath: CSIDL flag(s) invalid");
RIPMSG(dwFlags == 0, "SHSetFolderPath: dwFlags parameter must be 0x00000000");
// Exit with E_INVALIDARG if bad parameters.
if ((((csidl & CSIDL_FLAG_MASK) & ~(CSIDL_FLAG_DONT_UNEXPAND | 0x00000000)) != 0) ||
(dwFlags != 0) ||
(pszPath == NULL) ||
(pszPath[0] == 0))
{
return hr;
}
const FOLDER_INFO *pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
// Only allow setting for SDIF_NOT_FILESYS is clear
// SDIF_NOT_TRACKED is clear
// SDIF_CANT_MOVE_RENAME is clear
// and for non-NULL value
// If HKLM is used then rely on security or registry restrictions
// to enforce whether the change can be made.
if ((pfi != NULL) &&
((pfi->dwFlags & (SDIF_NOT_FILESYS | SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME)) == 0))
{
BOOL fSuccessfulUnexpand, fSuccessfulExpand, fEmptyOrNullPath;
LONG lError;
HANDLE hTokenToFree;
TCHAR szPath[MAX_PATH];
TCHAR szExpandedPath[MAX_PATH]; // holds expanded path for "Shell Folder" compat key
LPCTSTR pszWritePath;
hTokenToFree = NULL;
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
{
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
{
hTokenToFree = hToken;
}
}
fEmptyOrNullPath = ((pszPath == NULL) || (pszPath[0] == 0));
if (fEmptyOrNullPath)
{
HKEY hKeyDefaultUser;
pszWritePath = NULL;
if (SUCCEEDED(_OpenKeyForFolder(pfi, (HANDLE)-1, TEXT("User Shell Folders"), &hKeyDefaultUser)))
{
DWORD dwPathSize = sizeof(szPath);
if (ERROR_SUCCESS == RegQueryValueEx(hKeyDefaultUser, pfi->pszValueName,
NULL, NULL, (LPBYTE)szPath, &dwPathSize))
{
pszWritePath = szPath;
}
RegCloseKey(hKeyDefaultUser);
}
fSuccessfulUnexpand = TRUE;
}
else if (csidl & CSIDL_FLAG_DONT_UNEXPAND)
{
// Does the caller want to write the string as is? Leave
// it alone if so.
pszWritePath = pszPath;
fSuccessfulUnexpand = TRUE;
}
else
{
if (pfi->hKey == HKEY_CURRENT_USER)
{
fSuccessfulUnexpand = (PathUnExpandEnvStringsForUser(hToken, pszPath, szPath, ARRAYSIZE(szPath)) != FALSE);
}
else
{
fSuccessfulUnexpand = FALSE;
}
// Choose the appropriate source if the unexpansion was successful or not.
// Either way the unexpansion failure should be ignored.
if (fSuccessfulUnexpand)
{
pszWritePath = szPath;
}
else
{
fSuccessfulUnexpand = TRUE;
pszWritePath = pszPath;
}
}
if (fSuccessfulUnexpand)
{
HKEY hKeyUser, hKeyUSF, hKeyToFree;
// we also get the fully expanded path so that we can write it out to the "Shell Folders" key for apps that depend on
// the old registry values
fSuccessfulExpand = (SHExpandEnvironmentStringsForUser(hToken, pszPath, szExpandedPath, ARRAYSIZE(szExpandedPath)) != 0);
// Get either the current users HKCU or HKU\SID if a token
// was specified and running in NT.
if (hToken && GetUserProfileKey(hToken, &hKeyUser))
{
hKeyToFree = hKeyUser;
}
else
{
hKeyUser = pfi->hKey;
hKeyToFree = NULL;
}
// Open the key to the User Shell Folders and write the string
// there. Clear the shell folder cache.
// NOTE: This functionality is duplicated in SetFolderPath but
// that function deals with the USF key only. This function
// requires HKU\SID so while there is identical functionality
// from the point of view of settings the USF value that is
// where it ends. To make this function simple it just writes
// the value to registry itself.
// Additional note: there is a threading issue here with
// clearing the cache entry incrementing the counter. This
// should be locked access.
lError = RegOpenKeyEx(hKeyUser, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"),
0, KEY_READ | KEY_WRITE, &hKeyUSF);
if (lError == ERROR_SUCCESS)
{
if (pszWritePath)
{
lError = RegSetValueEx(hKeyUSF, pfi->pszValueName, 0, REG_EXPAND_SZ,
(LPBYTE)pszWritePath, (lstrlen(pszWritePath) + sizeof('\0')) * sizeof(TCHAR));
}
else
{
lError = RegDeleteValue(hKeyUSF, pfi->pszValueName);
}
RegCloseKey(hKeyUSF);
// nuke the cache state for this folder
_ClearCacheEntry(pfi);
// and all folders that might be aliased as those
// could be related to this folder (under MyDocs for example)
// and now their aliases forms my no longer be valid
_ClearAllAliasCacheEntrys();
g_lPerProcessCount = SHGlobalCounterIncrement(g_hCounter);
}
// update the old "Shell Folders" value for compat
if ((lError == ERROR_SUCCESS) && fSuccessfulExpand)
{
HKEY hkeySF;
if (RegOpenKeyEx(hKeyUser, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"),
0, KEY_READ | KEY_WRITE, &hkeySF) == ERROR_SUCCESS)
{
if (pszWritePath)
{
RegSetValueEx(hkeySF, pfi->pszValueName, 0, REG_SZ,
(LPBYTE)szExpandedPath, (lstrlen(szExpandedPath) + sizeof('\0')) * sizeof(TCHAR));
}
else
{
RegDeleteValue(hkeySF, pfi->pszValueName);
}
RegCloseKey(hkeySF);
}
}
if ((lError == ERROR_SUCCESS) && (pfi->hKey == HKEY_CURRENT_USER))
{
switch (csidl & ~CSIDL_FLAG_MASK)
{
case CSIDL_APPDATA:
{
HKEY hKeyVolatileEnvironment;
// In the case of AppData there is a matching environment variable
// for this shell folder. Make sure the place in the registry where
// userenv.dll places this value is updated and correct so that when
// the user context is created by winlogon it will have the updated
// value.
// It's probably also a good thing to check for a %APPDATA% variable
// in the calling process' context but this would only be good for
// the life of the process. What is really required is a mechanism
// to change the environment variable for the entire logon session.
lError = RegOpenKeyEx(hKeyUser, TEXT("Volatile Environment"), 0,
KEY_READ | KEY_WRITE, &hKeyVolatileEnvironment);
if (lError == ERROR_SUCCESS)
{
if (SUCCEEDED(SHGetFolderPath(NULL, csidl | CSIDL_FLAG_DONT_VERIFY,
hToken, SHGFP_TYPE_CURRENT, szPath)))
{
lError = RegSetValueEx(hKeyVolatileEnvironment, TEXT("APPDATA"),
0, REG_SZ, (LPBYTE)szPath, (lstrlen(szPath) + sizeof('\0')) * sizeof(TCHAR));
}
RegCloseKey(hKeyVolatileEnvironment);
}
break;
}
}
}
if (hKeyToFree)
{
RegCloseKey(hKeyToFree);
}
if (lError == ERROR_SUCCESS)
{
hr = S_OK;
}
else
{
hr = HRESULT_FROM_WIN32(lError);
}
}
if (hTokenToFree)
{
CloseHandle(hTokenToFree);
}
SHChangeDWORDAsIDList dwidl = { sizeof(dwidl) - sizeof(dwidl.cbZero), SHCNEE_UPDATEFOLDERLOCATION, csidl & ~CSIDL_FLAG_MASK, 0};
SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_ONLYNOTIFYINTERNALS | SHCNF_IDLIST, (LPCITEMIDLIST)&dwidl, NULL);
}
return hr;
}
STDAPI SHSetFolderPathA(int csidl, HANDLE hToken, DWORD dwType, LPCSTR pszPath)
{
WCHAR wsz[MAX_PATH];
SHAnsiToUnicode(pszPath, wsz, ARRAYSIZE(wsz));
return SHSetFolderPath(csidl, hToken, dwType, wsz);
}
// NOTE: called from DllEntry
void SpecialFolderIDTerminate()
{
ASSERTDLLENTRY // does not require a critical section
_ClearAllCacheEntrys();
if (g_hCounter)
{
CloseHandle(g_hCounter);
g_hCounter = NULL;
}
}
// update our cache and the registry for pfi with pszPath. this also invalidates the
// cache in other processes so they stay in sync
void SetFolderPath(const FOLDER_INFO *pfi, LPCTSTR pszPath)
{
_ClearCacheEntry(pfi);
if (pszPath)
{
HKEY hk;
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, TEXT("User Shell Folders"), &hk)))
{
LONG err;
TCHAR szDefaultPath[MAX_PATH];
// Check for an existing path, and if the unexpanded version
// of the existing path does not match the new path, then
// write the new path to the registry.
//
// RegQueryPath expands the environment variables for us
// so we can't just blindly set the new value to the registry.
//
RegQueryPath(hk, pfi->pszValueName, szDefaultPath);
if (lstrcmpi(szDefaultPath, pszPath) != 0)
{
// The paths are different. Write to the registry as file
// system path.
err = SHRegSetPath(hk, NULL, pfi->pszValueName, pszPath, 0);
}
else
{
err = ERROR_SUCCESS;
}
// clear out any temp paths
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), NULL);
if (err == ERROR_SUCCESS)
{
// this will force a new creation (see TRUE as fCreate).
// This will also copy the path from "User Shell Folders"
// to "Shell Folders".
LPITEMIDLIST pidl;
if (S_OK == _GetFolderIDListCached(NULL, pfi, CSIDL_FLAG_CREATE, &pidl))
{
ILFree(pidl);
}
else
{
// failed! null out the entry. this will go back to our default
RegDeleteValue(hk, pfi->pszValueName);
_ClearCacheEntry(pfi);
}
}
RegCloseKey(hk);
}
}
else
{
RegSetFolderPath(pfi, TEXT("User Shell Folders"), NULL);
// clear out any temp paths
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), NULL);
}
// set the global different from the per process variable
// to signal an update needs to happen other processes
g_lPerProcessCount = SHGlobalCounterIncrement(g_hCounter);
}
// file system change notifies come in here AFTER the folders have been moved/deleted
// we fix up the registry to match what occured in the file system
EXTERN_C void SFP_FSEvent(LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
{
const FOLDER_INFO *pfi;
TCHAR szSrc[MAX_PATH];
if (!(lEvent & (SHCNE_RENAMEFOLDER | SHCNE_RMDIR | SHCNE_MKDIR)) ||
!SHGetPathFromIDList(pidl, szSrc) ||
(pidlExtra && ILIsEqual(pidl, pidlExtra))) // when volume label changes, pidl==pidlExtra so we detect this case and skip it for perf
{
return;
}
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
if (0 == (pfi->dwFlags & (SDIF_NOT_TRACKED | SDIF_NOT_FILESYS)))
{
TCHAR szCurrent[MAX_PATH];
if (S_OK == _GetFolderPathCached(NULL, pfi, NULL, CSIDL_FLAG_DONT_VERIFY, szCurrent) &&
PathIsEqualOrSubFolder(szSrc, szCurrent))
{
TCHAR szDest[MAX_PATH];
szDest[0] = 0;
if (lEvent & SHCNE_RMDIR)
{
// complete the "move accross volume" case
HKEY hk;
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, TEXT("User Shell Folders\\New"), &hk)))
{
RegQueryPath(hk, pfi->pszValueName, szDest);
RegCloseKey(hk);
}
}
else if (pidlExtra)
{
SHGetPathFromIDList(pidlExtra, szDest);
}
if (szDest[0])
{
// rename the specal folder
UINT cch = PathCommonPrefix(szCurrent, szSrc, NULL);
ASSERT(cch != 0);
if (szCurrent[cch])
{
PathAppend(szDest, szCurrent + cch);
}
SetFolderPath(pfi, szDest);
}
}
}
}
}
ULONG _ILGetChildOffset(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild)
{
DWORD cbOff = 0;
LPCITEMIDLIST pidlChildT = ILFindChild(pidlParent, pidlChild);
if (pidlChildT)
{
cbOff = (ULONG)((LPBYTE)pidlChildT - (LPBYTE)pidlChild);
}
return cbOff;
}
// returns the first special folder CSIDL_ id that is a parent
// of the passed in pidl or 0 if not found. only CSIDL_ entries marked as
// SDIF_SHORTCUT_RELATIVE are considered for this.
//
// returns:
// CSIDL_ values
// *pcbOffset offset into pidl
STDAPI_(int) GetSpecialFolderParentIDAndOffset(LPCITEMIDLIST pidl, ULONG *pcbOffset)
{
int iRet = 0; // everything is desktop relative
const FOLDER_INFO *pfi;
*pcbOffset = 0;
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
if (pfi->dwFlags & SDIF_SHORTCUT_RELATIVE)
{
LPITEMIDLIST pidlFolder;
if (S_OK == _GetFolderIDListCached(NULL, pfi, 0, &pidlFolder))
{
ULONG cbOff = _ILGetChildOffset(pidlFolder, pidl);
if (cbOff > *pcbOffset)
{
*pcbOffset = cbOff;
iRet = pfi->id;
}
ILFree(pidlFolder);
}
}
}
return iRet;
}
// note, only works for file system path (bummer, we would like others supported too)
STDAPI_(BOOL) MakeShellURLFromPath(LPCTSTR pszPathIn, LPTSTR pszUrl, DWORD dwCch)
{
const FOLDER_INFO *pfi;
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
{
if ((pfi->dwFlags & SDIF_SHORTCUT_RELATIVE) &&
!(pfi->dwFlags & SDIF_NOT_FILESYS))
{
TCHAR szCurrent[MAX_PATH];
if (S_OK == _GetFolderPathCached(NULL, pfi, 0, CSIDL_FLAG_DONT_VERIFY, szCurrent))
{
if (PathIsPrefix(szCurrent, pszPathIn))
{
StrCpy(pszUrl, TEXT("shell:"));
StrCat(pszUrl, pfi->pszValueName);
PathAppend(pszUrl, &pszPathIn[lstrlen(szCurrent)]);
return TRUE;
}
}
}
}
return FALSE;
}
STDAPI_(BOOL) MakeShellURLFromPathA(LPCSTR pszPathIn, LPSTR pszUrl, DWORD dwCch)
{
WCHAR szTmp1[MAX_PATH], szTmp2[MAX_PATH];
SHAnsiToUnicode(pszPathIn, szTmp1, ARRAYSIZE(szTmp1));
BOOL bRet = MakeShellURLFromPathW(szTmp1, szTmp2, ARRAYSIZE(szTmp2));
SHUnicodeToAnsi(szTmp2, pszUrl, dwCch);
return bRet;
}
BOOL MoveBlockedByPolicy(const FOLDER_INFO *pfi)
{
BOOL bRet = FALSE;
if (pfi->dwFlags & SDIF_POLICY_NO_MOVE)
{
// similar to code in mydocs.dll
TCHAR szValue[128];
wnsprintf(szValue, ARRAYSIZE(szValue), TEXT("Disable%sDirChange"), pfi->pszValueName);
if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER,
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"),
szValue, NULL, NULL, NULL))
{
bRet = TRUE;
}
}
return bRet;
}
// this is called from the copy engine (like all other copy hooks)
// this is where we put up UI blocking the delete/move of some special folders
EXTERN_C int PathCopyHookCallback(HWND hwnd, UINT wFunc, LPCTSTR pszSrc, LPCTSTR pszDest)
{
int ret = IDYES;
if ((wFunc == FO_DELETE) || (wFunc == FO_MOVE) || (wFunc == FO_RENAME))
{
const FOLDER_INFO *pfi;
// is one of our system directories being affected?
for (pfi = c_rgFolderInfo; ret == IDYES && pfi->id != -1; pfi++)
{
// even non tracked folders (windows, system) come through here
if (0 == (pfi->dwFlags & SDIF_NOT_FILESYS))
{
TCHAR szCurrent[MAX_PATH];
if (S_OK == _GetFolderPathCached(NULL, pfi, NULL, CSIDL_FLAG_DONT_VERIFY, szCurrent) &&
PathIsEqualOrSubFolder(pszSrc, szCurrent))
{
// Yes
if (wFunc == FO_DELETE)
{
if (pfi->dwFlags & SDIF_CAN_DELETE)
{
SetFolderPath(pfi, NULL); // Let them delete some folders
}
else
{
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTDELETESPECIALDIR),
MAKEINTRESOURCE(IDS_DELETE), MB_OK | MB_ICONINFORMATION, PathFindFileName(pszSrc));
ret = IDNO;
}
}
else
{
int idSrc = PathGetDriveNumber(pszSrc);
int idDest = PathGetDriveNumber(pszDest);
ASSERT((wFunc == FO_MOVE) || (wFunc == FO_RENAME));
if ((pfi->dwFlags & SDIF_CANT_MOVE_RENAME) ||
((idSrc != -1) && (idDest == -1) && !(pfi->dwFlags & SDIF_NETWORKABLE)) ||
((idSrc != idDest) && PathIsRemovable(pszDest) && !(pfi->dwFlags & SDIF_REMOVABLE)) ||
MoveBlockedByPolicy(pfi))
{
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTMOVESPECIALDIRHERE),
wFunc == FO_MOVE ? MAKEINTRESOURCE(IDS_MOVE) : MAKEINTRESOURCE(IDS_RENAME),
MB_ICONERROR, PathFindFileName(pszSrc));
ret = IDNO;
}
else
{
//
// store this info here
// if we need it we will use it.
//
// we used to try to optimise in the case of same
// volume renames. we assumed that if it was the same
// volume we would later get a SHCNE_RENAME. but sometimes
// we have to do a copy even on the same volume. so
// we need to always set this value.
//
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), pszDest);
}
}
}
}
}
}
return ret;
}
// Given a key name ("programs", "desktop", "start menu"), convert it to
// the corresponding CSIDL.
STDAPI_(int) SHGetSpecialFolderID(LPCWSTR pszName)
{
// make sure g_aFolderCache can be indexed by the CSIDL values
COMPILETIME_ASSERT((ARRAYSIZE(g_aFolderCache) - 1) == CSIDL_COMPUTERSNEARME);
for (int i = 0; c_rgFolderInfo[i].id != -1; i++)
{
if (c_rgFolderInfo[i].pszValueName &&
(0 == StrCmpI(pszName, c_rgFolderInfo[i].pszValueName)))
{
return c_rgFolderInfo[i].id;
}
}
return -1;
}
// Given a CSIDL, returns the key name -- the opposite of
// SHGetSpecialFolderID
STDAPI_(LPCTSTR) SHGetSpecialFolderKey(int csidl)
{
const FOLDER_INFO *pfi = _GetFolderInfo(csidl);
return pfi ? pfi->pszValueName : NULL;
}
// Return the special folder ID, if this folder is one of them.
// At this point, we handle PROGRAMS folder only.
//
// GetSpecialFolderID()
// this allows a list of CSIDLs to be passed in.
// they will be searched in order for the specified csidl
// and the path will be checked against it.
// if -1 is specified as the csidl, then all of array entries should
// be checked for a match with the folder.
//
int GetSpecialFolderID(LPCTSTR pszFolder, const int *rgcsidl, UINT count)
{
for (UINT i = 0; i < count; i++)
{
int csidlSpecial = rgcsidl[i] & ~TEST_SUBFOLDER;
TCHAR szPath[MAX_PATH];
if (S_OK == SHGetFolderPath(NULL, csidlSpecial | CSIDL_FLAG_DONT_VERIFY, NULL, SHGFP_TYPE_CURRENT, szPath))
{
if (((rgcsidl[i] & TEST_SUBFOLDER) && PathIsEqualOrSubFolder(szPath, pszFolder)) ||
(lstrcmpi(szPath, pszFolder) == 0))
{
return csidlSpecial;
}
}
}
return -1;
}
/**
* Tacks a name onto a CSIDL, e.g. gets a pidl for
* CSIDL_COMMON_PICTURES\Sample Pictures
* if it exists.
* Called must free ppidlSampleMedia
* Note: The folder is *not* created if it does not exist.
*/
HRESULT _AppendPathToPIDL(int nAllUsersMediaFolder, LPCWSTR pszName, LPITEMIDLIST *ppidlSampleMedia)
{
LPITEMIDLIST pidlAllUsersMedia;
HRESULT hr = SHGetFolderLocation(NULL, nAllUsersMediaFolder, NULL, 0, &pidlAllUsersMedia);
if (SUCCEEDED(hr))
{
// Get the shellfolder for this guy.
IShellFolder *psf;
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlAllUsersMedia, &psf));
if (SUCCEEDED(hr))
{
// And now the pidl for the sample folder
LPITEMIDLIST pidlSampleMediaRel;
ULONG dwAttributes = 0;
hr = psf->ParseDisplayName(NULL, NULL, (LPOLESTR)pszName, NULL, &pidlSampleMediaRel, &dwAttributes);
if (SUCCEEDED(hr))
{
// It exists!
hr = SHILCombine(pidlAllUsersMedia, pidlSampleMediaRel, ppidlSampleMedia);
ILFree(pidlSampleMediaRel);
}
psf->Release();
}
ILFree(pidlAllUsersMedia);
}
return hr;
}
/**
* Returns a pidl to the samples folder under a particular CSIDL
* Caller must free ppidlSampleMedia
*/
HRESULT _ParseSubfolderResource(int csidl, UINT ids, LPITEMIDLIST *ppidl)
{
WCHAR szSub[MAX_PATH];
LoadDefaultString(ids, szSub, ARRAYSIZE(szSub));
return _AppendPathToPIDL(csidl, szSub, ppidl);
}
HRESULT SHGetSampleMediaFolder(int nAllUsersMediaFolder, LPITEMIDLIST *ppidlSampleMedia)
{
UINT uID = -1;
switch (nAllUsersMediaFolder)
{
case CSIDL_COMMON_PICTURES:
uID = IDS_SAMPLEPICTURES;
break;
case CSIDL_COMMON_MUSIC:
uID = IDS_SAMPLEMUSIC;
break;
default:
ASSERT(FALSE);
return E_INVALIDARG;
break;
}
return _ParseSubfolderResource(nAllUsersMediaFolder, uID, ppidlSampleMedia);
}
void _CreateLinkToSampleMedia(LPCWSTR pszNewFolderPath, int nAllUsersMediaFolder, UINT uIDSampleFolderName)
{
LPITEMIDLIST pidl;
if (SUCCEEDED(SHGetSampleMediaFolder(nAllUsersMediaFolder, &pidl)))
{
// Check to make sure the link doesn't already exist.
WCHAR szSampleFolderName[MAX_PATH];
WCHAR szFullLnkPath[MAX_PATH];
LoadString(HINST_THISDLL, uIDSampleFolderName, szSampleFolderName, ARRAYSIZE(szSampleFolderName));
StrCatBuff(szSampleFolderName, L".lnk", ARRAYSIZE(szSampleFolderName));
if (PathCombine(szFullLnkPath, pszNewFolderPath, szSampleFolderName))
{
if (!PathFileExists(szFullLnkPath))
{
// MUI-WARNING - we are not doing a SHSetLocalizedName for this link - ZekeL - 15-MAY-2001
// this means that this link is always created in the default system UI language
// we should probably call SHSetLocalizedName() here but i am scared right now of perf implications.
CreateLinkToPidl(pidl, pszNewFolderPath, NULL, 0);
}
}
ILFree(pidl);
}
}
void _InitFolder(LPCTSTR pszPath, UINT idsInfoTip, HINSTANCE hinstIcon, UINT idiIcon)
{
// Set the default custom settings for the folder.
SHFOLDERCUSTOMSETTINGS fcs = {sizeof(fcs), 0};
TCHAR szInfoTip[128];
TCHAR szPath[MAX_PATH];
// Get the infotip for this folder
if (idsInfoTip)
{
wnsprintf(szInfoTip,ARRAYSIZE(szInfoTip),TEXT("@Shell32.dll,-%u"),idsInfoTip);
fcs.pszInfoTip = szInfoTip;
fcs.cchInfoTip = ARRAYSIZE(szInfoTip);
fcs.dwMask |= FCSM_INFOTIP;
}
// this will be encoded to the %SystemRoot% style path when setting the folder information.
if (idiIcon)
{
GetModuleFileName(hinstIcon, szPath, ARRAYSIZE(szPath));
fcs.pszIconFile = szPath;
fcs.cchIconFile = ARRAYSIZE(szPath);
fcs.iIconIndex = idiIcon;
fcs.dwMask |= FCSM_ICONFILE;
}
// NOTE: we need FCS_FORCEWRITE because we didn't used to specify iIconIndex
// and so "0" was written to the ini file. When we upgrade, this API won't
// fix the ini file unless we pass FCS_FORCEWRITE
SHGetSetFolderCustomSettings(&fcs, pszPath, FCS_FORCEWRITE);
}
void _InitMyPictures(int id, LPCTSTR pszPath)
{
// Get the path to the icon. We reference MyDocs.dll for backwards compat.
HINSTANCE hinstMyDocs = LoadLibrary(TEXT("mydocs.dll"));
if (hinstMyDocs)
{
_InitFolder(pszPath, IDS_FOLDER_MYPICS_TT, hinstMyDocs, -101); // known index for IDI_MYPICS in mydocs.dll
FreeLibrary(hinstMyDocs);
}
}
void _InitMyMusic(int id, LPCTSTR pszPath)
{
_InitFolder(pszPath, IDS_FOLDER_MYMUSIC_TT, HINST_THISDLL, -IDI_MYMUSIC);
}
void _InitPerUserMyPictures(int id, LPCTSTR pszPath)
{
_InitMyPictures(id, pszPath);
_CreateLinkToSampleMedia(pszPath, CSIDL_COMMON_PICTURES, IDS_SAMPLEPICTURES);
}
void _InitPerUserMyMusic(int id, LPCTSTR pszPath)
{
_InitMyMusic(id, pszPath);
_CreateLinkToSampleMedia(pszPath, CSIDL_COMMON_MUSIC, IDS_SAMPLEMUSIC);
}
void _InitMyVideos(int id, LPCTSTR pszPath)
{
_InitFolder(pszPath, IDS_FOLDER_MYVIDEOS_TT, HINST_THISDLL, -IDI_MYVIDEOS);
}
void _InitRecentDocs(int id, LPCTSTR pszPath)
{
_InitFolder(pszPath, IDS_FOLDER_RECENTDOCS_TT, HINST_THISDLL, -IDI_STDOCS);
}
void _InitFavorites(int id, LPCTSTR pszPath)
{
_InitFolder(pszPath, 0, HINST_THISDLL, -IDI_FAVORITES);
}