Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

856 lines
21 KiB

//*************************************************************
// File name: profile.c
//
// Description: Fixes hard coded paths in the registry for
// special folder locations. Also fixes security
// on a few registry keys.
//
// Microsoft Confidential
// Copyright (c) Microsoft Corporation 1996
// All rights reserved
//
//*************************************************************
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <xpsp1res.h>
#include <userenv.h>
#include <userenvp.h>
#include <tchar.h>
#include "shmgdefs.h"
//*************************************************************
//
// ApplySecurityToRegistryTree()
//
// Purpose: Applies the passed security descriptor to the passed
// key and all its descendants. Only the parts of
// the descriptor inddicated in the security
// info value are actually applied to each registry key.
//
// Parameters: RootKey - Registry key
// pSD - Security Descriptor
//
// Return: ERROR_SUCCESS if successful
//
// Comments:
//
// History: Date Author Comment
// 7/19/95 ericflo Created
// 6/16/96 bobday Stolen directly from USERENV
//
//*************************************************************
DWORD ApplySecurityToRegistryTree(HKEY RootKey, PSECURITY_DESCRIPTOR pSD)
{
DWORD Error;
DWORD SubKeyIndex;
LPTSTR SubKeyName;
HKEY SubKey;
DWORD cchSubKeySize = MAX_PATH + 1;
//
// First apply security
//
Error = RegSetKeySecurity(RootKey, DACL_SECURITY_INFORMATION, pSD);
if (Error != ERROR_SUCCESS) {
return Error;
}
//
// Open each sub-key and apply security to its sub-tree
//
SubKeyIndex = 0;
SubKeyName = GlobalAlloc (GPTR, cchSubKeySize * sizeof(TCHAR));
if (!SubKeyName) {
return GetLastError();
}
while (TRUE) {
//
// Get the next sub-key name
//
Error = RegEnumKey(RootKey, SubKeyIndex, SubKeyName, cchSubKeySize);
if (Error != ERROR_SUCCESS) {
if (Error == ERROR_NO_MORE_ITEMS) {
//
// Successful end of enumeration
//
Error = ERROR_SUCCESS;
} else {
}
break;
}
//
// Open the sub-key
//
Error = RegOpenKeyEx(RootKey,
SubKeyName,
0,
WRITE_DAC | KEY_ENUMERATE_SUB_KEYS | READ_CONTROL,
&SubKey);
if (Error != ERROR_SUCCESS) {
break;
}
//
// Apply security to the sub-tree
//
Error = ApplySecurityToRegistryTree(SubKey, pSD);
//
// We're finished with the sub-key
//
RegCloseKey(SubKey);
//
// See if we set the security on the sub-tree successfully.
//
if (Error != ERROR_SUCCESS) {
break;
}
//
// Go enumerate the next sub-key
//
SubKeyIndex ++;
}
GlobalFree (SubKeyName);
return Error;
}
//*************************************************************
//
// MakeKeyOrTreeSecure()
//
// Purpose: Sets the attributes on the registry key and possibly sub-keys
// such that Administrators and the OS can delete it and Everyone
// else has read permission only (OR general read/write access)
//
// Parameters: RootKey - Key to set security on
// fWrite - Allow write (or just read)
//
// Return: (BOOL) TRUE if successful
// FALSE if an error occurs
//
// Comments:
//
// History: Date Author Comment
// 11/6/95 ericflo Created
// 06/16/96 bobday Ported from MakeFileSecure in USERENV
//
//*************************************************************
BOOL MakeKeyOrTreeSecure (HKEY RootKey, BOOL fWrite)
{
SECURITY_DESCRIPTOR sd;
SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY;
SID_IDENTIFIER_AUTHORITY authWorld = SECURITY_WORLD_SID_AUTHORITY;
PACL pAcl = NULL;
PSID psidSystem = NULL, psidAdmin = NULL, psidEveryone = NULL;
DWORD cbAcl, aceIndex;
ACE_HEADER * lpAceHeader;
BOOL bRetVal = FALSE;
DWORD Error;
DWORD dwAccess;
if (fWrite) {
dwAccess = KEY_ALL_ACCESS;
} else {
dwAccess = KEY_READ;
}
//
// Get the system sid
//
if (!AllocateAndInitializeSid(&authNT, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &psidSystem)) {
goto Exit;
}
//
// Get the Admin sid
//
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0,
0, 0, 0, 0, &psidAdmin)) {
goto Exit;
}
//
// Get the World sid
//
if (!AllocateAndInitializeSid(&authWorld, 1, SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0, &psidEveryone)) {
goto Exit;
}
//
// Allocate space for the ACL
//
cbAcl = (3 * GetLengthSid (psidSystem)) +
(3 * GetLengthSid (psidAdmin)) + sizeof(ACL) +
(6 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
pAcl = (PACL) GlobalAlloc(GMEM_FIXED, cbAcl);
if (!pAcl) {
goto Exit;
}
if (!InitializeAcl(pAcl, cbAcl, ACL_REVISION)) {
goto Exit;
}
//
// Add Aces. Non-inheritable ACEs first
//
aceIndex = 0;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, KEY_ALL_ACCESS, psidSystem)) {
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, KEY_ALL_ACCESS, psidAdmin)) {
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, dwAccess, psidEveryone)) {
goto Exit;
}
//
// Now the inheritable ACEs
//
if (fWrite) {
dwAccess = GENERIC_ALL;
} else {
dwAccess = GENERIC_READ;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidSystem)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidAdmin)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, dwAccess, psidEveryone)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
//
// Put together the security descriptor
//
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
goto Exit;
}
if (!SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE)) {
goto Exit;
}
//
// Set the security
//
Error = ApplySecurityToRegistryTree(RootKey, &sd);
if (Error == ERROR_SUCCESS) {
bRetVal = TRUE;
}
Exit:
if (psidSystem) {
FreeSid(psidSystem);
}
if (psidAdmin) {
FreeSid(psidAdmin);
}
if (psidEveryone) {
FreeSid(psidEveryone);
}
if (pAcl) {
GlobalFree (pAcl);
}
return bRetVal;
}
void FixWindowsProfileSecurity( void )
{
HKEY hkeyWindows;
HKEY hkeyShellExt;
DWORD Error;
Error = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
TEXT("Software\\Microsoft\\Windows"),
0,
WRITE_DAC | KEY_ENUMERATE_SUB_KEYS | READ_CONTROL,
&hkeyWindows);
if (Error == ERROR_SUCCESS)
{
MakeKeyOrTreeSecure(hkeyWindows, TRUE);
RegCloseKey(hkeyWindows);
}
Error = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions"),
0,
WRITE_DAC | KEY_ENUMERATE_SUB_KEYS | READ_CONTROL,
&hkeyShellExt);
if (Error == ERROR_SUCCESS)
{
MakeKeyOrTreeSecure(hkeyShellExt, FALSE);
RegCloseKey(hkeyShellExt);
}
}
void FixUserProfileSecurity( void )
{
HKEY hkeyPolicies;
DWORD Error;
Error = RegOpenKeyEx( HKEY_CURRENT_USER,
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Policies"),
0,
WRITE_DAC | KEY_ENUMERATE_SUB_KEYS | READ_CONTROL,
&hkeyPolicies);
if (Error == ERROR_SUCCESS)
{
MakeKeyOrTreeSecure(hkeyPolicies, FALSE);
RegCloseKey(hkeyPolicies);
}
}
void FixPoliciesSecurity( void )
{
HKEY hkeyPolicies;
DWORD Error, dwDisp;
Error = RegCreateKeyEx( HKEY_CURRENT_USER,
TEXT("Software\\Policies"),
0,
NULL,
REG_OPTION_NON_VOLATILE,
WRITE_DAC | KEY_ENUMERATE_SUB_KEYS | READ_CONTROL,
NULL,
&hkeyPolicies,
&dwDisp);
if (Error == ERROR_SUCCESS)
{
MakeKeyOrTreeSecure(hkeyPolicies, FALSE);
RegCloseKey(hkeyPolicies);
}
}
void SetSystemBitOnCAPIDir(void)
{
HRESULT hr;
TCHAR szPath[MAX_PATH];
TCHAR szAppData[MAX_PATH];
DWORD FileAttributes;
if (S_OK == SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szAppData)){
//
// It is better to use tcscpy and tcscat. This is just a temp fix and it is build with
// Unicode anyway. Not worth to include extra header file.
// We do not check error case here. This is just a best effort. If we got error, so what?
// MAX_PATH should be enough for these DIRs. Not worth to take care of \\?\ format.
//
lstrcpyn(szPath, szAppData, ARRAYSIZE(szPath));
StrCatBuff(szPath, TEXT("\\Microsoft\\Protect"), ARRAYSIZE(szPath));
FileAttributes = GetFileAttributes(szPath);
if ((FileAttributes != -1) && ((FileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0)) {
FileAttributes |= FILE_ATTRIBUTE_SYSTEM;
SetFileAttributes(szPath, FileAttributes);
}
lstrcpyn(szPath, szAppData, ARRAYSIZE(szPath));
StrCatBuff(szPath, TEXT("\\Microsoft\\Crypto"), ARRAYSIZE(szPath));
FileAttributes = GetFileAttributes(szPath);
if ((FileAttributes != -1) && ((FileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0)) {
FileAttributes |= FILE_ATTRIBUTE_SYSTEM;
SetFileAttributes(szPath, FileAttributes);
}
}
}
void SetScreensaverOnFriendlyUI()
{
if (IsOS(OS_FRIENDLYLOGONUI) &&
IsOS(OS_FASTUSERSWITCHING))
{
HKEY hkey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop"), 0, KEY_SET_VALUE, &hkey) == ERROR_SUCCESS)
{
TCHAR szTemp[MAX_PATH];
RegSetValueEx(hkey, TEXT("ScreenSaveActive"), 0, REG_SZ, (BYTE*)TEXT("1"), sizeof(TEXT("1")));
if (RegQueryValueEx(hkey, TEXT("SCRNSAVE.EXE"), NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
{
// if the user dosen't already have a screensaver set, then choose one for them!
if (ExpandEnvironmentStrings(TEXT("%SystemRoot%\\System32\\logon.scr"), szTemp, ARRAYSIZE(szTemp)))
{
RegSetValueEx(hkey, TEXT("SCRNSAVE.EXE"), 0, REG_SZ, (BYTE*)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
}
}
RegCloseKey(hkey);
}
}
}
#define OTHERSIDS_EVERYONE 1
#define OTHERSIDS_POWERUSERS 2
BOOL MakeFileSecure (LPTSTR lpFile, DWORD dwOtherSids)
{
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa;
SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY;
SID_IDENTIFIER_AUTHORITY authWORLD = SECURITY_WORLD_SID_AUTHORITY;
PACL pAcl = NULL;
PSID psidSystem = NULL, psidAdmin = NULL, psidUsers = NULL, psidPowerUsers = NULL;
PSID psidEveryOne = NULL;
DWORD cbAcl, aceIndex;
ACE_HEADER * lpAceHeader;
BOOL bRetVal = FALSE;
BOOL bAddPowerUsersAce=TRUE;
BOOL bAddEveryOneAce=FALSE;
DWORD dwAccMask;
//
// Get the system sid
//
if (!AllocateAndInitializeSid(&authNT, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &psidSystem)) {
goto Exit;
}
//
// Get the Admin sid
//
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0,
0, 0, 0, 0, &psidAdmin)) {
goto Exit;
}
//
// Get the users sid
//
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_USERS,
0, 0, 0, 0, 0, 0, &psidUsers)) {
goto Exit;
}
//
// Allocate space for the ACL
//
cbAcl = (2 * GetLengthSid (psidSystem)) +
(2 * GetLengthSid (psidAdmin)) +
(2 * GetLengthSid (psidUsers)) +
sizeof(ACL) +
(6 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
//
// Get the power users sid, if required.
// Don't fail if you don't get because it might not be available on DCs??
//
bAddPowerUsersAce = TRUE;
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, 0, &psidPowerUsers)) {
bAddPowerUsersAce = FALSE;
}
if (bAddPowerUsersAce)
cbAcl += (2 * GetLengthSid (psidPowerUsers)) + (2 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
//
// Get the EveryOne sid, if required.
//
if (dwOtherSids & OTHERSIDS_EVERYONE) {
bAddEveryOneAce = TRUE;
if (!AllocateAndInitializeSid(&authWORLD, 1, SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0, &psidEveryOne)) {
goto Exit;
}
}
if (bAddEveryOneAce)
cbAcl += (2 * GetLengthSid (psidEveryOne)) + (2 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
pAcl = (PACL) GlobalAlloc(GMEM_FIXED, cbAcl);
if (!pAcl) {
goto Exit;
}
if (!InitializeAcl(pAcl, cbAcl, ACL_REVISION)) {
goto Exit;
}
//
// Add Aces. Non-inheritable ACEs first
//
aceIndex = 0;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidSystem)) {
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidAdmin)) {
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*GENERIC_READ | GENERIC_EXECUTE,*/ psidUsers)) {
goto Exit;
}
if (bAddPowerUsersAce) {
//
// By default give read permissions, otherwise give modify permissions
//
dwAccMask = (dwOtherSids & OTHERSIDS_POWERUSERS) ? (FILE_ALL_ACCESS ^ (WRITE_DAC | WRITE_OWNER)):
(GENERIC_READ | GENERIC_EXECUTE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*dwAccMask,*/ psidPowerUsers)) {
goto Exit;
}
}
if (bAddEveryOneAce) {
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*GENERIC_READ | GENERIC_EXECUTE,*/ psidEveryOne)) {
goto Exit;
}
}
//
// Now the inheritable ACEs
//
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidSystem)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidAdmin)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*GENERIC_READ | GENERIC_EXECUTE,*/ psidUsers)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
if (bAddPowerUsersAce) {
aceIndex++;
dwAccMask = (dwOtherSids & OTHERSIDS_POWERUSERS) ? (FILE_ALL_ACCESS ^ (WRITE_DAC | WRITE_OWNER)):
(GENERIC_READ | GENERIC_EXECUTE);
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*dwAccMask,*/ psidPowerUsers)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
}
if (bAddEveryOneAce) {
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, /*GENERIC_READ | GENERIC_EXECUTE,*/ psidEveryOne)) {
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
}
//
// Put together the security descriptor
//
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
goto Exit;
}
if (!SetSecurityDescriptorDacl(&sd, TRUE, pAcl, FALSE)) {
goto Exit;
}
//
// Set the security
//
if (SetFileSecurity (lpFile, DACL_SECURITY_INFORMATION, &sd)) {
bRetVal = TRUE;
} else {
}
Exit:
if (psidSystem) {
FreeSid(psidSystem);
}
if (psidAdmin) {
FreeSid(psidAdmin);
}
if (psidUsers) {
FreeSid(psidUsers);
}
if ((bAddPowerUsersAce) && (psidPowerUsers)) {
FreeSid(psidPowerUsers);
}
if ((bAddEveryOneAce) && (psidEveryOne)) {
FreeSid(psidEveryOne);
}
if (pAcl) {
GlobalFree (pAcl);
}
return bRetVal;
}
#ifdef SHMG_DBG
void SHMGLogErrMsg(char *szErrMsg, DWORD dwError)
{
DWORD dwBytesWritten = 0;
char szMsg[256];
static HANDLE hLogFile = 0;
if (!hLogFile) {
hLogFile = CreateFile(_T("shmgrate.log"), GENERIC_WRITE,
FILE_SHARE_WRITE, 0,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
}
sprintf(szMsg, "%s : (%X)\r\n", szErrMsg, dwError);
WriteFile(hLogFile, szMsg, strlen(szMsg), &dwBytesWritten, 0);
}
#endif
void
FixHtmlHelp(
void
)
{
TCHAR AppDataPath[MAX_PATH*2];
TCHAR HtmlHelpPath[MAX_PATH];
SHMGLogErrMsg("FixHtmlHelp Called",0);
if (SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, AppDataPath) == S_OK &&
LoadString( GetModuleHandle(NULL), IDS_HTML_HELP_DIR, HtmlHelpPath, sizeof(HtmlHelpPath)/sizeof(WCHAR) ) > 0)
{
_tcscat( AppDataPath, HtmlHelpPath );
if (CreateDirectory( AppDataPath, NULL )) {
if (!MakeFileSecure(AppDataPath,OTHERSIDS_EVERYONE|OTHERSIDS_POWERUSERS))
SHMGLogErrMsg("Could not apply security attributes", 0);
}
else
SHMGLogErrMsg("Could not create the directory", GetLastError());
}
else
SHMGLogErrMsg("Could not get APPDATA path", GetLastError());
}
// Add the SP1 "Configure Programs" shortcut to the common start menu
// On a clean install, this is handled by syssetup.inf, but on a
// RTM->SP1 upgrade, we need to do it by hand.
//
void AddConfigurePrograms()
{
if (!IsOS(OS_ANYSERVER))
{
TCHAR szPath[MAX_PATH];
if (GetSystemDirectory(szPath, ARRAYSIZE(szPath)) &&
PathAppend(szPath, TEXT("XPSP1RES.DLL")))
{
HINSTANCE hinstSp1 = LoadLibraryEx(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (hinstSp1)
{
TCHAR szTitle[MAX_PATH];
LoadString(hinstSp1, IDS_APPWIZ_CONFIGUREPROGRAMS, szTitle, ARRAYSIZE(szTitle));
CreateLinkFile(CSIDL_COMMON_STARTMENU, NULL, szTitle,
TEXT("control.exe appwiz.cpl,,3"),
TEXT("moricons.dll"), -114,
NULL, 0, SW_SHOWNORMAL, TEXT("@xpsp1res.dll,-10078"));
if (S_OK == SHGetFolderPath(NULL, CSIDL_COMMON_STARTMENU, NULL, SHGFP_TYPE_CURRENT, szPath) &&
PathAppend(szPath, szTitle) &&
PathAddExtension(szPath, TEXT(".lnk")))
{
SHSetLocalizedName(szPath, L"xpsp1res.dll", IDS_APPWIZ_CONFIGUREPROGRAMS);
}
}
FreeLibrary(hinstSp1);
}
}
}