// Copyright (C) 2000 Microsoft Corporation // // diagnose domain controller not found problems, offer a popup dialog to // assail the user with the results. // // 9 October 2000 sburns // In order for clients of these functions to get the proper resources, the // clients need to include burnslib\inc\DiagnoseDcNotFound.rc in their // resources. For an example, see admin\dcpromo\exe\dcpromo.rc #include "headers.hxx" #include "DiagnoseDcNotFound.h" #include "DiagnoseDcNotFound.hpp" // Return a string of IP addresses, each separated by a CRLF, one address for // each DNS server specified in this machines TCP/IP protocol configuration. // On failure, the return value is a message that no address were found. String GetListOfClientDnsServerAddresses() { LOG_FUNCTION(GetListOfClientDnsServerAddresses); String result = String::load(IDS_NO_ADDRESSES); PIP_ARRAY pipArray = 0; DWORD bufSize = sizeof(IP_ARRAY); do { DNS_STATUS status = ::DnsQueryConfig( DnsConfigDnsServerList, DNS_CONFIG_FLAG_ALLOC, 0, 0, &pipArray, &bufSize); if (status != ERROR_SUCCESS || !pipArray || !pipArray->AddrArray) { LOG(String::format(L"DnsQueryConfig failed %1!d!", status)); break; } result = L""; PIP_ADDRESS pIpAddrs = pipArray->AddrArray; while (pipArray->AddrCount--) { result += String::format( L"%1!d!.%2!d!.%3!d!.%4!d!\r\n", * ( (PUCHAR) &pIpAddrs[pipArray->AddrCount] + 0 ), * ( (PUCHAR) &pIpAddrs[pipArray->AddrCount] + 1 ), * ( (PUCHAR) &pIpAddrs[pipArray->AddrCount] + 2 ), * ( (PUCHAR) &pIpAddrs[pipArray->AddrCount] + 3 ) ); } } while (0); Win::LocalFree(pipArray); LOG(result); return result; } // Return a string of DNS zone names derived from the given DNS domain name. // Each zone is separated by a CRLF. The last zone is the root zone, even if // the domain name is the empty string, or a fully-qualified domain name. // // example: if "foo.bar.com" is passed as the domain name, then the result is // "foo.bar.com // bar.com // com // . (the root zone)" // // domainDnsName - in, the DNS domain name. String GetListOfZones(const String domainDnsName) { LOG_FUNCTION2(GetListOfZones, domainDnsName); ASSERT(!domainDnsName.empty()); String result; String zone = domainDnsName; while (zone != L"." && !zone.empty()) { result += zone + L"\r\n"; zone = Dns::GetParentDomainName(zone); } result += String::load(IDS_ROOT_ZONE); LOG(result); return result; } // Return a formatted string describing the given error code, including the // corresponding error message, the error code in hex, and the symbolic name // of the error code (e.g. "DNS_RCODE_NAME_ERROR") // // errorCode - in, the DNS error code String GetErrorText(DNS_STATUS errorCode) { LOG_FUNCTION(GetErrorText); String result = String::format( IDS_DC_NOT_FOUND_DIAG_ERROR_CODE, GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); LOG(result); return result; } // For each SRV record in the given linked list of DnsQuery results, extract // the name of the machine. Compose a string of all the names, separated by // CRLFs. If no SRV records are found, then return a string saying (to the // effect) "none found" // // queryResults - in, the linked list of DNS_RECORDs -- the result of calling // DnsQuery. Should not be null. String GetListOfDomainControllers(DNS_RECORD* queryResults) { LOG_FUNCTION(GetListOfDomainControllers); ASSERT(queryResults); String result; if (queryResults) { DNS_RECORD* record = queryResults; while (record) { if (record->wType == DNS_TYPE_SRV) { // Extract the domain controller name from the RDATA result += String(record->Data.SRV.pNameTarget) + L"\r\n"; } record = record->pNext; } } if (result.empty()) { result = String::load(IDS_DC_NOT_FOUND_NO_RESULTS); } LOG(result); return result; } // Returns a text string describing the likely causes of DsGetDcName failing. // Performs DnsQuery(ies) and exmaines the results. // // domainName - in, the name of the domain for which a domain controller can't // be located. This name may be a netbios or DNS name, but the DNS // diagnostics portion of the result text will not be useful if the name is a // netbios name. // // nameIsNotNetbios - in, if the caller knows that the domain named in // the domainName parameter can't possibly be a netbios domain name, then this // value should be true. If the caller is not sure, then false should be // passed. // // helpTopic - out, the help topic link corresponding to the diagnostic result // (HtmlHelp is used to display the link) String DiagnoseDcNotFound( const String& domainName, bool nameIsNotNetbios, String& helpTopic) { LOG_FUNCTION2(DiagnoseDcNotFound, domainName); ASSERT(!domainName.empty()); String result = String::load(IDS_DC_NOT_FOUND_DIAG_NO_RESULTS); String message; helpTopic.erase(); // first possibility is that the name is a netbios name. Let's check. if ( domainName.length() > DNLEN || domainName.find_first_of(L".") != String::npos) { // Name is too long to be netbios, or contains dots. // // While it is technically possible for a netbios domain name to contain // dots, we've been prohibiting it since win2k, and an administrator // from before that would have to have been spectacularly unwise to have // chosen such a name. // // If I don't check for dots here, then as sure as it rains in Redmond, // someone will complain that a name with dots in it sure doesn't look // like a netbios name, so this code had better not imply that it is. nameIsNotNetbios = true; } if (!nameIsNotNetbios) { // i.e. name might be a netbios name message += String::format( IDS_DC_NOT_FOUND_NETBIOS_PREFACE, domainName.c_str()); } // attempt to find domain controllers for the domain with DNS String serverName = L"_ldap._tcp.dc._msdcs." + domainName; DNS_RECORD* queryResults = 0; DNS_STATUS status = MyDnsQuery( serverName, DNS_TYPE_SRV, DNS_QUERY_BYPASS_CACHE, queryResults); switch (status) { case DNS_ERROR_RCODE_SERVER_FAILURE: { // message F (message letters correspond to those in the spec) String zones = GetListOfZones(domainName); String addresses = GetListOfClientDnsServerAddresses(); message += String::format( IDS_DC_NOT_FOUND_DIAG_SERVER_FAILURE, domainName.c_str(), GetErrorText(status).c_str(), serverName.c_str(), addresses.c_str(), zones.c_str()); helpTopic = L"tcpip.chm::/sag_DNS_tro_dcLocator_messageF.htm"; break; } case DNS_ERROR_RCODE_NAME_ERROR: { // message E String zones = GetListOfZones(domainName); String addresses = GetListOfClientDnsServerAddresses(); message += String::format( IDS_DC_NOT_FOUND_NAME_ERROR, domainName.c_str(), GetErrorText(status).c_str(), serverName.c_str(), zones.c_str(), addresses.c_str()); helpTopic = L"tcpip.chm::/sag_DNS_tro_dcLocator_messageE.htm"; break; } case ERROR_TIMEOUT: { // message B String addresses = GetListOfClientDnsServerAddresses(); message += String::format( IDS_DC_NOT_FOUND_TIMEOUT, domainName.c_str(), GetErrorText(status).c_str(), serverName.c_str(), addresses.c_str()); helpTopic = L"tcpip.chm::/sag_DNS_tro_dcLocator_messageB.htm"; break; } case NO_ERROR: { if (queryResults) { // non-empty query results -- message Hb String dcs = GetListOfDomainControllers(queryResults); message += String::format( IDS_DC_NOT_FOUND_NO_ERROR_1, domainName.c_str(), serverName.c_str(), dcs.c_str()); helpTopic = L"tcpip.chm::/sag_DNS_tro_dcLocator_messageHa.htm"; break; } // empty query results -- message A // fall thru to default case } default: { // message A message += String::format( IDS_DC_NOT_FOUND_DEFAULT, domainName.c_str(), GetErrorText(status).c_str(), serverName.c_str()); helpTopic = L"tcpip.chm::/sag_DNS_tro_dcLocator_messageA.htm"; break; } } MyDnsRecordListFree(queryResults); if (!message.empty()) { result = message; } LOG(result); return result; } // Class for displaying the a "dc not found" error and offering to run a // diagnostic test to determine why the dc was not found. class DcNotFoundErrorDialog : public Dialog { public: // domainName - in, the name of the domain for which a domain controller // can't be located. This name may be a netbios or DNS name, but the DNS // diagnostics portion of the result text will not be useful if the name is // a netbios name. // // dialogTitle - in, the title of the error dialog. // // errorMessage - in, the error message to be displayed in the dialog // // domainNameIsNotNetbios - in, if the caller knows that the domain named // in the domainName parameter can't possibly be a netbios domain name, // then this value should be true. If the caller is not sure, then false // should be passed. // // userIsDomainSavvy - in, true if the end user is expected to be an // administrator or somesuch that might have an inkling what DNS is and how // to configure it. If false, then the function will preface the // diagnostic text with calming words that hopefully prevent the // non-administrator from weeping. DcNotFoundErrorDialog( const String& domainName, const String& dialogTitle, const String& errorMessage, bool domainNameIsNotNetbios, bool userIsDomainSavvy); virtual ~DcNotFoundErrorDialog(); protected: // Dialog overrides virtual bool OnCommand( HWND windowFrom, unsigned controlIDFrom, unsigned code); virtual void OnInit(); private: void HideDetails(); void ShowDetails(); void DiagnoseAndSetDetailsText(); String dialogTitle; bool diagnosticWasRun; String domainName; String errorMessage; bool domainNameIsNotNetbios; String helpTopicLink; bool detailsShowing; bool userIsDomainSavvy; LONG originalHeight; // not defined: no copying allowed DcNotFoundErrorDialog(const DcNotFoundErrorDialog&); const DcNotFoundErrorDialog& operator=(const DcNotFoundErrorDialog&); }; static const DWORD HELP_MAP[] = { 0, 0 }; DcNotFoundErrorDialog::DcNotFoundErrorDialog( const String& domainName_, const String& dialogTitle_, const String& errorMessage_, bool domainNameIsNotNetbios_, bool userIsDomainSavvy_) : Dialog(IDD_DC_NOT_FOUND, HELP_MAP), dialogTitle(dialogTitle_), diagnosticWasRun(false), domainName(domainName_), errorMessage(errorMessage_), userIsDomainSavvy(userIsDomainSavvy_), domainNameIsNotNetbios(domainNameIsNotNetbios_), detailsShowing(false) { LOG_CTOR(DcNotFoundErrorDialog); ASSERT(!domainName.empty()); ASSERT(!errorMessage.empty()); ASSERT(!dialogTitle.empty()); // fall back to a default title if (dialogTitle.empty()) { dialogTitle = String::load(IDS_DC_NOT_FOUND_TITLE); } } DcNotFoundErrorDialog::~DcNotFoundErrorDialog() { LOG_DTOR(DcNotFoundErrorDialog); } void DcNotFoundErrorDialog::OnInit() { LOG_FUNCTION(DcNotFoundErrorDialog::OnInit); Win::SetWindowText(hwnd, dialogTitle); Win::SetDlgItemText(hwnd, IDC_ERROR_MESSAGE, errorMessage); // save the full size of the dialog so we can restore it later. RECT fullRect; Win::GetWindowRect(hwnd, fullRect); originalHeight = fullRect.bottom - fullRect.top; HideDetails(); } // resize the window to hide the details portion void DcNotFoundErrorDialog::HideDetails() { LOG_FUNCTION(DcNotFoundErrorDialog::HideDetails); // find the location of the horizontal line HWND line = Win::GetDlgItem(hwnd, IDC_HORIZONTAL_LINE); RECT lineRect; Win::GetWindowRect(line, lineRect); // find the dimensions of the dialog RECT fullRect; Win::GetWindowRect(hwnd, fullRect); LONG shortHeight = lineRect.bottom - fullRect.top; Win::MoveWindow( hwnd, fullRect.left, fullRect.top, fullRect.right - fullRect.left, shortHeight, true); Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_DETAILS_TEXT), false); } // resize the window to show the diagnostic results void DcNotFoundErrorDialog::ShowDetails() { LOG_FUNCTION(DcNoFoundErrorDialog::ShowDetails); RECT fullRect; Win::GetWindowRect(hwnd, fullRect); Win::MoveWindow( hwnd, fullRect.left, fullRect.top, fullRect.right - fullRect.left, originalHeight, true); Win::EnableWindow(Win::GetDlgItem(hwnd, IDC_DETAILS_TEXT), true); } // Write the diagnostic text to a well-known file // (%systemroot%\debug\dcdiag.txt), and return the name of the file. Replaces // the file if it exists already. Return S_OK on success, or an error code on // failure. (On failure, the existence and contents of the file are not // guaranteed). The file is written in Unicode, as it may contain Unicode // text (like non-rfc domain names). // // contents - in, the text to be written. Should not be the empty string. // // filename - out, the name of the file that was written (on success), or // would have been written (on failure). HRESULT WriteLogFile(const String& contents, String& filename) { LOG_FUNCTION(WriteLogFile); ASSERT(!contents.empty()); filename.erase(); HRESULT hr = S_OK; HANDLE handle = INVALID_HANDLE_VALUE; do { String path = Win::GetSystemWindowsDirectory(); filename = path + L"\\debug\\dcdiag.txt"; hr = FS::CreateFile( filename, handle, GENERIC_WRITE, 0, CREATE_ALWAYS); BREAK_ON_FAILED_HRESULT(hr); hr = FS::Write(handle, contents); BREAK_ON_FAILED_HRESULT(hr); } while (0); Win::CloseHandle(handle); LOG_HRESULT(hr); return hr; } // Run the diagnostic, and populate the UI with the results. Also writes // the results to a file if the user is deemed easily spooked. void DcNotFoundErrorDialog::DiagnoseAndSetDetailsText() { LOG_FUNCTION(DcNotFoundErrorDialog::DiagnoseAndSetDetailsText); if (!diagnosticWasRun) { Win::WaitCursor wait; String details = DiagnoseDcNotFound( domainName, domainNameIsNotNetbios, helpTopicLink); if (!userIsDomainSavvy) { // The diagnosis will probably just frighten the poor user. So write // the diagnostic info to a file, and preface all the icky computer // lingo with a soothing message about just delivering the file to an // administrator. String logFilename; HRESULT hr = WriteLogFile(details, logFilename); if (SUCCEEDED(hr)) { details = String::format( IDS_DC_NOT_FOUND_SOOTHING_PREFACE_PARAM, logFilename.c_str()) + details; } else { details = String::load(IDS_DC_NOT_FOUND_SOOTHING_PREFACE) + details; } } Win::SetDlgItemText(hwnd, IDC_DETAILS_TEXT, details); diagnosticWasRun = true; } } bool DcNotFoundErrorDialog::OnCommand( HWND /* windowFrom */ , unsigned controlIDFrom, unsigned code) { // LOG_FUNCTION(DcNotFoundErrorDialog::OnCommand); switch (controlIDFrom) { case IDOK: case IDCANCEL: { if (code == BN_CLICKED) { HRESULT unused = Win::EndDialog(hwnd, controlIDFrom); ASSERT(SUCCEEDED(unused)); return true; } break; } case IDHELP: { if (code == BN_CLICKED) { DiagnoseAndSetDetailsText(); if (!helpTopicLink.empty()) { Win::HtmlHelp(hwnd, helpTopicLink, HH_DISPLAY_TOPIC, 0); } return true; } break; } case IDC_DETAILS_BUTTON: { if (code == BN_CLICKED) { int buttonLabelResId = IDS_SHOW_DETAILS_LABEL; if (detailsShowing) { HideDetails(); detailsShowing = false; } else { buttonLabelResId = IDS_HIDE_DETAILS_LABEL; DiagnoseAndSetDetailsText(); ShowDetails(); detailsShowing = true; } Win::SetDlgItemText( hwnd, IDC_DETAILS_BUTTON, String::load(buttonLabelResId)); } break; } default: { // do nothing break; } } return false; } void ShowDcNotFoundErrorDialog( HWND parent, int editResId, const String& domainName, const String& dialogTitle, const String& errorMessage, bool domainNameIsNotNetbios, bool userIsDomainSavvy) { LOG_FUNCTION(ShowDcNotFoundErrorDialog); ASSERT(Win::IsWindow(parent)); // show the error dialog with the given error message. DcNotFoundErrorDialog( domainName, dialogTitle, errorMessage, domainNameIsNotNetbios, userIsDomainSavvy).ModalExecute(parent); if (editResId != -1) { HWND edit = Win::GetDlgItem(parent, editResId); Win::SendMessage(edit, EM_SETSEL, 0, -1); Win::SetFocus(edit); } }