// Copyright (c) 1997-1999 Microsoft Corporation // // Post-operation shortcut (shell link) code // // 1 Dec 1999 sburns #include "headers.hxx" #include "ProgressDialog.hpp" #include "state.hpp" #include "resource.h" #include "..\dll\muiresource.h" // resIds for shortcuts // @@ need to make sure that, when deleting shortcuts, we consider the case // were the shortcuts may have been added by the adminpak from the 5.0 release // of the product, not by ourselves in a later release. // // This case is: promote with version 5.0, upgrade to later version, demote static const String SHORTCUT_DLL(L"dcpromo.dll"); struct ShortcutParams { // resource id of the link file name and also the link name in the menu. // These are from muiresource.h. The string resources themselves are bound // to dcpromo.dll int linkNameResId; // resource id of the description string (also the info tip string) for // the shortcut. These are from muiresource.h. The string resources // themselves are bound to dcpromo.dll int descResId; const wchar_t* target; const wchar_t* params; const wchar_t* iconDll; }; // "Add" from the point of view of promotion: these are removed on demotion. static ShortcutParams shortcutsToAdd[] = { { // Active Directory Sites and Services IDS_DS_SITE_LINK, IDS_DS_SITE_DESC, L"dssite.msc", L"", // the .msc file contains the proper icon, so we don't need to // specify a dll from whence to retrieve an icon. L"" }, { // Active Directory Users and Computers IDS_DS_USERS_LINK, IDS_DS_USERS_DESC, L"dsa.msc", L"", L"" }, { // Active Directory Domains and Trusts IDS_DS_DOMAINS_LINK, IDS_DS_DOMAINS_DESC, L"domain.msc", L"", L"" }, { // Domain Controller Security Policy // if you change this name, be sure to change the code in // PromoteConfigureToolShortcuts too IDS_DC_POLICY_LINK, IDS_DC_POLICY_DESC, L"dcpol.msc", L"", L"" }, { // Domain Security Policy // if you change this name, be sure to change the code in // PromoteConfigureToolShortcuts too IDS_DOMAIN_POLICY_LINK, IDS_DOMAIN_POLICY_DESC, L"dompol.msc", L"", L"" } }; // "Delete" from the point of view of promotion: these are added back again // on demotion. static ShortcutParams shortcutsToDelete[] = { { // Local Security Policy IDS_LOCAL_POLICY_LINK, IDS_LOCAL_POLICY_DESC, L"secpol.msc", L"/s", L"wsecedit.dll" } }; // Extracts the target of a shortcut: that to which the shortcut points. // Returns S_OK on success, and sets result to that target. On error, a COM // error code is returned and result is empty. // // shellLink - pointer to instance of object implementing IShellLink, which // has been associated with a shortcut file. // // result - receives the result -- the shortcut target path -- on sucess. HRESULT GetShortcutTargetPath( const SmartInterface& shellLink, String& result) { LOG_FUNCTION(GetShortcutTargetPath); ASSERT(shellLink); result.erase(); WCHAR target[MAX_PATH + 1]; // REVIEWED-2002/02/26-sburns correct byte count passed // remove superfluous '&' NTRAID#NTBUG9-540418-2002/03/12-sburns ::ZeroMemory(target, sizeof WCHAR * (MAX_PATH + 1)); // REVIEWED-2002/02/26-sburns passing correct character count. HRESULT hr = shellLink->GetPath(target, MAX_PATH, 0, SLGP_SHORTPATH); if (SUCCEEDED(hr)) { result = target; } return hr; } // Return true if the supplied target of a shortcut is such that it identifies // the shortcut as one of those installed on promote. Return false if not one // such. // // target - target path of the shortcut (i.e. path to that which the shortcut // points) bool IsAdminpakShortcut(const String& target) { LOG_FUNCTION2(IsAdminpakShortcut, target); // don't assert that target has a value. Some shortcuts don't, if they're // broken. // // ASSERT(!target.empty()); // If the target is of the form %systemroot%\Installer\{guid}\foo.ico, // then it is one of the adminpak dcpromo shortcuts. static String baseNames[] = { L"DTMgmt.ico", L"ADSSMgr.ico", L"ADMgr.ico", L"ADDcPol.ico", L"ADDomPol.ico" }; static String root(Win::GetSystemWindowsDirectory() + L"\\Installer\\{"); bool result = false; String prefix(target, 0, root.length()); if (root.icompare(prefix) == 0) { // the prefix matches. String leaf = FS::GetPathLeafElement(target); for (int i = 0; i < (sizeof(baseNames) / sizeof(String)) ; ++i) { if (leaf.icompare(baseNames[i]) == 0) { result = true; break; } } } LOG( String::format( L"%1 an adminpak shortcut", result ? L"is" : L"is not")); return result; } bool IsPromoteToolShortcut(const String& target) { LOG_FUNCTION2(IsPromoteToolShortcut, target); ASSERT(!target.empty()); // Check target against the values we used to create the shortcuts. The // values we used specified a fully-qualified path to the system32 folder, // and we will compare the target to the full path. String targetPrefix = Win::GetSystemDirectory() + L"\\"; for ( int i = 0; i < sizeof(shortcutsToAdd) / sizeof(ShortcutParams); ++i) { if (target.icompare(targetPrefix + shortcutsToAdd[i].target) == 0) { return true; } } return false; } // Return true if the given shortcut one of those installed on promote. // Return false if not one of those shortcuts, or on error. // // shellLink - smart interface pointer to an object implementing IShellLink. // // lnkPath - full file path of the shortcut (.lnk) file to be evaluated. bool ShouldDeleteShortcut( const SmartInterface& shellLink, const String& lnkPath) { LOG_FUNCTION2(ShouldDeleteShortcut, lnkPath); ASSERT(!lnkPath.empty()); ASSERT(shellLink); // Shortcut file names are localized, so we can't delete them based on // their names. idea: Open the shortcut, see what it's target is, // and based on that, determine if it's one we should delete. HRESULT hr = S_OK; bool result = false; do { // Load the shortcut file // REVIEWED-2002/02/26-sburns we open with minimum access, and are only // reading SmartInterface ipf; hr = ipf.AcquireViaQueryInterface(shellLink); BREAK_ON_FAILED_HRESULT(hr); // ISSUE-2002/02/26-sburns should we specify STGM_SHARE_DENY_WRITE? hr = ipf->Load(lnkPath.c_str(), STGM_READ); BREAK_ON_FAILED_HRESULT(hr); // Get the target lnkPath String target; hr = GetShortcutTargetPath(shellLink, target); BREAK_ON_FAILED_HRESULT(hr); if (IsAdminpakShortcut(target)) { result = true; break; } // Not an adminpak shortcut. Might be one of the ones created by // PromoteConfigureToolShortcuts (ourselves). if (IsPromoteToolShortcut(target)) { result = true; break; } // if we make it here, the shortcut is not one we should delete. } while (0); LOG( String::format( L"%1 delete shortcut", result ? L"should" : L"should not")); return result; } HRESULT CreateShortcut( const SmartInterface& shellLink, const String& destFolderPath, int linkNameResId, HINSTANCE linkNameResModule, int descResId, const String& target, const String& params, const String& iconDll) { LOG_FUNCTION2(CreateShortcut, target); ASSERT(shellLink); // the path should exist already: it's the one we grabbed at startup ASSERT(FS::PathExists(destFolderPath)); ASSERT(!target.empty()); ASSERT(linkNameResId); ASSERT(descResId); ASSERT(linkNameResModule); // params and iconDll may be empty HRESULT hr = S_OK; do { String sys32Folder = Win::GetSystemDirectory(); String targetPath = sys32Folder + L"\\" + target; // REVIEWED-2002/05/06-sburns we're using full paths to the target hr = shellLink->SetPath(targetPath.c_str()); BREAK_ON_FAILED_HRESULT(hr); hr = shellLink->SetWorkingDirectory(sys32Folder.c_str()); BREAK_ON_FAILED_HRESULT(hr); hr = shellLink->SetDescription( String::format( // MUI-aware shortcuts take a description that is really a // pointer to a resource dll and a resource ID. // NTRAID#NTBUG9-185055-2001/06/21-sburns L"@%%systemroot%%\\system32\\dcpromo.dll,-%1!d!", descResId).c_str()); BREAK_ON_FAILED_HRESULT(hr); hr = shellLink->SetArguments(params.c_str()); BREAK_ON_FAILED_HRESULT(hr); if (!iconDll.empty()) { hr = shellLink->SetIconLocation( (sys32Folder + L"\\" + iconDll).c_str(), 0); } SmartInterface ipf; hr = ipf.AcquireViaQueryInterface(shellLink); BREAK_ON_FAILED_HRESULT(hr); String destPath = destFolderPath + L"\\" + String::load(linkNameResId, linkNameResModule) + L".lnk"; // REVIEWED-2002/02/27-sburns we are composing a full path to the file ASSERT(FS::IsValidPath(destPath)); hr = ipf->Save(destPath.c_str(), TRUE); BREAK_ON_FAILED_HRESULT(hr); // MUI-aware shortcuts need a localized name. // NTRAID#NTBUG9-185055-2001/06/21-sburns hr = SHSetLocalizedName( const_cast(destPath.c_str()), L"%systemroot%\\system32\\dcpromo.dll", linkNameResId); BREAK_ON_FAILED_HRESULT(hr); } while (0); LOG_HRESULT(hr); return hr; } HRESULT DeleteShortcut( const String& folder, int linkNameResId, HINSTANCE linkNameResModule) { LOG_FUNCTION(DeleteShortcut); ASSERT(!folder.empty()); ASSERT(linkNameResId); ASSERT(linkNameResModule); HRESULT hr = S_OK; do { String linkPath = folder + L"\\" + String::load(linkNameResId, linkNameResModule) + L".lnk"; LOG(linkPath); if (FS::PathExists(linkPath)) { hr = Win::DeleteFile(linkPath); BREAK_ON_FAILED_HRESULT(hr); } } while (0); return hr; } // Remove the shortcuts to the DS administration tools that were installed on // promote. void DemoteConfigureToolShortcuts(ProgressDialog& dialog) { LOG_FUNCTION(DemoteConfigureToolShortcuts); HRESULT hr = S_OK; HMODULE dcpromoDll = 0; State& state = State::GetInstance(); do { String path = state.GetAdminToolsShortcutPath(); if (path.empty()) { // We were unable to determine the path at startup. hr = Win32ToHresult(ERROR_PATH_NOT_FOUND); break; } // (may) Need to init com for this thread. AutoCoInitialize coInit; hr = coInit.Result(); BREAK_ON_FAILED_HRESULT(hr); SmartInterface shellLink; hr = shellLink.AcquireViaCreateInstance( CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER); BREAK_ON_FAILED_HRESULT(hr); LOG(L"enumerating shortcuts"); FS::Iterator iter( path + L"\\*.lnk", FS::Iterator::INCLUDE_FILES | FS::Iterator::RETURN_FULL_PATHS); String current; while ((hr = iter.GetCurrent(current)) == S_OK) { if (ShouldDeleteShortcut(shellLink, current)) { LOG(String::format(L"Deleting %1", current.c_str())); // we don't bail out on an error here because we want to // try to delete as many shortcuts as possible. HRESULT unused = Win::DeleteFile(current); LOG_HRESULT(unused); } hr = iter.Increment(); BREAK_ON_FAILED_HRESULT(hr); } // add the shortcut(s) removed during promote hr = Win::LoadLibraryEx( SHORTCUT_DLL, LOAD_LIBRARY_AS_DATAFILE, dcpromoDll); BREAK_ON_FAILED_HRESULT2(hr, L"Unable to load dcpromo.dll"); for ( int i = 0; i < sizeof(shortcutsToDelete) / sizeof(ShortcutParams); ++i) { // don't break on error -- push on to attempt to create the // entire set. CreateShortcut( shellLink, path, shortcutsToDelete[i].linkNameResId, dcpromoDll, shortcutsToDelete[i].descResId, shortcutsToDelete[i].target, shortcutsToDelete[i].params, shortcutsToDelete[i].iconDll); } } while (0); Win::FreeLibrary(dcpromoDll); if (FAILED(hr)) { popup.Error( dialog.GetHWND(), hr, IDS_ERROR_CONFIGURING_SHORTCUTS); state.AddFinishMessage( String::load(IDS_SHORTCUTS_NOT_CONFIGURED)); } } // Take a domain name in canonical (dotted) form, e.g. domain.foo.com, and // translate it to the fully-qualified DN form, e.g. DC=domain,DC=foo,DC=com // // domainCanonical - in, domain name in canonical form // // domainDN - out, domain name in DN form HRESULT CannonicalToDn(const String& domainCanonical, String& domainDN) { LOG_FUNCTION2(CannonicalToDn, domainCanonical); ASSERT(!domainCanonical.empty()); domainDN.erase(); HRESULT hr = S_OK; do { if (domainCanonical.empty()) { hr = E_INVALIDARG; BREAK_ON_FAILED_HRESULT(hr); } // add a trailing '/' to signal DsCrackNames to do a syntactical // munge of the string, rather than hit the wire. // add 1 for the null terminator, 1 for the trailing '/' PWSTR name = new WCHAR[domainCanonical.length() + 2]; // REVIEWED-2002/02/27-sburns correct byte count passed. ::ZeroMemory(name, (domainCanonical.length() + 2) * sizeof WCHAR); domainCanonical.copy(name, domainCanonical.length()); name[domainCanonical.length()] = L'/'; DS_NAME_RESULT* nameResult = 0; hr = Win32ToHresult( ::DsCrackNames( // no handle: this is a string munge reinterpret_cast(-1), DS_NAME_FLAG_SYNTACTICAL_ONLY, DS_CANONICAL_NAME, DS_FQDN_1779_NAME, 1, &name, &nameResult)); delete[] name; BREAK_ON_FAILED_HRESULT(hr); ASSERT(nameResult); if (nameResult) { ASSERT(nameResult->cItems == 1); DS_NAME_RESULT_ITEM* items = nameResult->rItems; // if we don't get a struct back, then DsCrackNames is broken. ASSERT(items); if (items) { LOG(String::format(L"status : 0x%1!X!", items[0].status)); LOG(String::format(L"pName : %1", items[0].pName)); ASSERT(items[0].status == DS_NAME_NO_ERROR); if (items[0].pName) { domainDN = items[0].pName; } if (domainDN.empty()) { hr = E_FAIL; } } ::DsFreeNameResult(nameResult); } } while (0); LOG_HRESULT(hr); return hr; } // Create all the admin tools shortcuts that are needed after a promote. // // path - in, where to create the shortcuts // // shellLink - in, initialized shellLink interface to create the shortcuts // with. HRESULT PromoteCreateShortcuts( const String& path, SmartInterface& shellLink, HINSTANCE dcpromoDll) { LOG_FUNCTION(PromoteCreateShortcuts); ASSERT(!path.empty()); ASSERT(shellLink); ASSERT(dcpromoDll); HRESULT hr = S_OK; do { State& state = State::GetInstance(); // for the policy shortcuts, we will need to know the domain DN, so // determine that here. // NTRAID#NTBUG9-232442-2000/11/15-sburns String domainCanonical; State::Operation oper = state.GetOperation(); if ( oper == State::FOREST || oper == State::TREE || oper == State::CHILD) { domainCanonical = state.GetNewDomainDNSName(); } else if (oper == State::REPLICA) { domainCanonical = state.GetReplicaDomainDNSName(); } else { // we should not be calling this function on non-promote scenarios ASSERT(false); hr = E_FAIL; BREAK_ON_FAILED_HRESULT(hr); } String domainDn; bool skipPolicyShortcuts = false; hr = CannonicalToDn(domainCanonical, domainDn); if (FAILED(hr)) { LOG(L"skipping install of policy shortcuts"); skipPolicyShortcuts = true; } for ( int i = 0; i < sizeof(shortcutsToAdd) / sizeof(ShortcutParams); ++i) { // set the correct parameters for domain and dc security policy tools. String params; if (shortcutsToAdd[i].linkNameResId == IDS_DC_POLICY_LINK) { if (skipPolicyShortcuts) { continue; } params = String::format( L"/gpobject:\"LDAP://CN={%1},CN=Policies,CN=System,%2\"", STR_DEFAULT_DOMAIN_CONTROLLER_GPO_GUID, domainDn.c_str()); } else if (shortcutsToAdd[i].linkNameResId == IDS_DOMAIN_POLICY_LINK) { if (skipPolicyShortcuts) { continue; } params = String::format( L"/gpobject:\"LDAP://CN={%1},CN=Policies,CN=System,%2\"", STR_DEFAULT_DOMAIN_GPO_GUID, domainDn.c_str()); } else { params = shortcutsToAdd[i].params; } // don't break on errors -- push on to attempt to create the // entire set. CreateShortcut( shellLink, path, shortcutsToAdd[i].linkNameResId, dcpromoDll, shortcutsToAdd[i].descResId, shortcutsToAdd[i].target, params, shortcutsToAdd[i].iconDll); } } while (0); LOG_HRESULT(hr); return hr; } void PromoteConfigureToolShortcuts(ProgressDialog& dialog) { LOG_FUNCTION(PromoteConfigureToolShortcuts); dialog.UpdateText(String::load(IDS_CONFIGURING_SHORTCUTS)); HRESULT hr = S_OK; State& state = State::GetInstance(); HMODULE dcpromoDll = 0; do { String path = state.GetAdminToolsShortcutPath(); if (path.empty()) { // We were unable to determine the path at startup. hr = Win32ToHresult(ERROR_PATH_NOT_FOUND); break; } // Need to init com for this thread. AutoCoInitialize coInit; hr = coInit.Result(); BREAK_ON_FAILED_HRESULT(hr); SmartInterface shellLink; hr = shellLink.AcquireViaCreateInstance( CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER); BREAK_ON_FAILED_HRESULT(hr); hr = Win::LoadLibraryEx( SHORTCUT_DLL, LOAD_LIBRARY_AS_DATAFILE, dcpromoDll); BREAK_ON_FAILED_HRESULT2(hr, L"Unable to load dcpromo.dll"); // add the shortcuts to the ds administration tools PromoteCreateShortcuts(path, shellLink, dcpromoDll); // remove the shortcuts to local tools for ( int i = 0; i < sizeof(shortcutsToDelete) / sizeof(ShortcutParams); ++i) { // don't break on error -- push on to attempt to delete the // entire set. DeleteShortcut( path, shortcutsToDelete[i].linkNameResId, dcpromoDll); } } while (0); Win::FreeLibrary(dcpromoDll); if (FAILED(hr)) { popup.Error( dialog.GetHWND(), hr, IDS_ERROR_CONFIGURING_SHORTCUTS); state.AddFinishMessage( String::load(IDS_SHORTCUTS_NOT_CONFIGURED)); } }