// Copyright (c) 1997-1999 Microsoft Corporation // // confirmation page // // 12-22-97 sburns #include "headers.hxx" #include "ConfirmationPage.hpp" #include "common.hpp" #include "resource.h" #include "ProgressDialog.hpp" #include "ds.hpp" #include "state.hpp" #include "GetCredentialsDialog.hpp" #include "postop.hpp" #include void PromoteThreadProc(ProgressDialog& progress); ConfirmationPage::ConfirmationPage() : DCPromoWizardPage( IDD_CONFIRMATION, IDS_CONFIRMATION_PAGE_TITLE, IDS_CONFIRMATION_PAGE_SUBTITLE), needToKillSelection(false) { LOG_CTOR(ConfirmationPage); } ConfirmationPage::~ConfirmationPage() { LOG_DTOR(ConfirmationPage); } void ConfirmationPage::OnInit() { LOG_FUNCTION(ConfirmationPage::OnInit); // Since the multi-line edit control has a bug that causes it to eat // enter keypresses, we will subclass the control to make it forward // those keypresses to the page as WM_COMMAND messages // This workaround from phellyar. // NTRAID#NTBUG9-232092-2000/11/22-sburns multiLineEdit.Init(Win::GetDlgItem(hwnd, IDC_MESSAGE)); } int ConfirmationPage::Validate() { LOG_FUNCTION(ConfirmationPage::Validate); // this function should never be called, as we override OnWizNext. ASSERT(false); return 0; } static String GetMessage() { LOG_FUNCTION(GetMessage); String message; State& state = State::GetInstance(); String netbiosName; State::RunContext context = state.GetRunContext(); if ( context == State::BDC_UPGRADE or context == State::PDC_UPGRADE) { netbiosName = state.GetComputer().GetDomainNetbiosName(); } else { netbiosName = state.GetNewDomainNetbiosName(); } switch (state.GetOperation()) { case State::REPLICA: { message = String::format( IDS_CONFIRM_MESSAGE_REPLICA, state.GetReplicaDomainDNSName().c_str()); if (state.ReplicateFromMedia()) { message += String::format( IDS_CONFIRM_MESSAGE_REPLICATE_FROM_MEDIA, state.GetReplicationSourcePath().c_str()); } break; } case State::FOREST: { message = String::format( IDS_CONFIRM_MESSAGE_FOREST, state.GetNewDomainDNSName().c_str(), netbiosName.c_str()); break; } case State::TREE: { message = String::format( IDS_CONFIRM_MESSAGE_TREE, state.GetNewDomainDNSName().c_str(), netbiosName.c_str(), state.GetParentDomainDnsName().c_str()); break; } case State::CHILD: { message = String::format( IDS_CONFIRM_MESSAGE_CHILD, state.GetNewDomainDNSName().c_str(), netbiosName.c_str(), state.GetParentDomainDnsName().c_str()); break; } case State::DEMOTE: { String domain = state.GetComputer().GetDomainDnsName(); if (state.IsLastDCInDomain()) { message = String::format( IDS_CONFIRM_MESSAGE_DEMOTE_LAST_DC, domain.c_str()); } else { message = String::format( IDS_CONFIRM_MESSAGE_DEMOTE, domain.c_str()); } break; } case State::ABORT_BDC_UPGRADE: { message = String::format( IDS_CONFIRM_ABORT_BDC_UPGRADE, netbiosName.c_str()); break; } case State::NONE: default: { ASSERT(false); break; } } return message; } bool ConfirmationPage::OnSetActive() { LOG_FUNCTION(ConfirmationPage::OnSetActive); ASSERT(State::GetInstance().GetOperation() != State::NONE); State& state = State::GetInstance(); String message = GetMessage(); State::Operation operation = state.GetOperation(); switch (operation) { case State::REPLICA: case State::FOREST: case State::TREE: case State::CHILD: { // write the path options into the text box String pathText = String::format( IDS_CONFIRM_PATHS_MESSAGE, state.GetDatabasePath().c_str(), state.GetLogPath().c_str(), state.GetSYSVOLPath().c_str()); message += pathText; if (state.ShouldInstallAndConfigureDns()) { message += String::load(IDS_CONFIRM_INSTALL_DNS); } if (operation != State::REPLICA) { if (state.ShouldAllowAnonymousAccess()) { // Only show the anon access message in forest, tree, child // 394387 message += String::load(IDS_CONFIRM_DO_RAS_FIXUP); } message += String::load(IDS_DOMAIN_ADMIN_PASSWORD); } break; } case State::DEMOTE: case State::ABORT_BDC_UPGRADE: { // hide the path controls: do nothing break; } case State::NONE: default: { ASSERT(false); break; } } Win::SetDlgItemText(hwnd, IDC_MESSAGE, message); needToKillSelection = true; Win::PropSheet_SetWizButtons( Win::GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); if (state.RunHiddenUnattended()) { return ConfirmationPage::OnWizNext(); } return true; } void DoOperation( HWND parentDialog, ProgressDialog::ThreadProc threadProc, int animationResID) { LOG_FUNCTION(DoOperation); ASSERT(Win::IsWindow(parentDialog)); ASSERT(threadProc); ASSERT(animationResID > 0); // the ProgressDialog::OnInit actually starts the thread. ProgressDialog dialog(threadProc, animationResID); if ( dialog.ModalExecute(parentDialog) == static_cast(ProgressDialog::THREAD_SUCCEEDED)) { LOG(L"OPERATION SUCCESSFUL"); } else { LOG(L"OPERATION FAILED"); } } int DetermineAnimation() { LOG_FUNCTION(DetermineAnimation); State& state = State::GetInstance(); int aviID = IDR_AVI_DOMAIN; switch (state.GetOperation()) { case State::REPLICA: { aviID = IDR_AVI_REPLICA; break; } case State::DEMOTE: { aviID = IDR_AVI_DEMOTE; break; } case State::FOREST: case State::TREE: case State::CHILD: case State::ABORT_BDC_UPGRADE: case State::NONE: default: { // do nothing break; } } return aviID; } bool ConfirmationPage::OnWizNext() { LOG_FUNCTION(ConfirmationPage::OnWizNext); State& state = State::GetInstance(); DoOperation(hwnd, PromoteThreadProc, DetermineAnimation()); if (state.GetNeedsReboot()) { Win::PropSheet_RebootSystem(Win::GetParent(hwnd)); } int nextPage = IDD_FAILURE; if (!state.IsOperationRetryAllowed()) { nextPage = IDD_FINISH; } GetWizard().SetNextPageID(hwnd, nextPage); return true; } // noRoleMessageResId - resource ID of the string to use to format messages // when no role change error message is available. // // roleMessageResId - resource ID of the string to use to format messages when // a role change error message is available. // // "is available" => an operation results message has been set on the global // state object. String ComposeFailureMessageHelper( const Win::Error& error, unsigned noRoleMessageResId, unsigned roleMessageResId) { State& state = State::GetInstance(); String win32_message = error.GetMessage(); String opMessage = state.GetOperationResultsMessage(); String message; if ( error.GetHresult() == Win32ToHresult(ERROR_DS_CANT_ON_NON_LEAF) and state.GetOperation() == State::DEMOTE) { // supercede the meaningless error text for this situation. win32_message = String::load(IDS_DEMOTE_DOMAIN_HAS_DEPENDENTS); } if (error.GetHresult() == Win32ToHresult(ERROR_CANCELLED)) { // this message may be a failure message from the operation that was // taking place when the cancel request was received. In that case, // since the cancel has occurred, we don't care about this message opMessage.erase(); } if (error.GetHresult() == Win32ToHresult(ERROR_BAD_NETPATH)) { // n27117 win32_message = String::load(IDS_RAS_BAD_NETPATH); } if (opMessage.empty()) { message = String::format( noRoleMessageResId, win32_message.c_str()); } else { message = String::format( roleMessageResId, win32_message.c_str(), opMessage.c_str()); } return message; } void ComposeFailureMessage( const Win::Error& error, bool wasDisjoined, const String& originalDomainName) { LOG_FUNCTION(ComposeFailureMessage); String message = ComposeFailureMessageHelper( error, IDS_OPERATION_FAILED_NO_RESULT_MESSAGE, IDS_OPERATION_FAILED); if (wasDisjoined) { message += String::format(IDS_DISJOINED, originalDomainName.c_str()); } State& state = State::GetInstance(); if ( state.GetOperationResultsFlags() & DSROLE_IFM_RESTORED_DATABASE_FILES_MOVED) { message += L"\r\n\r\n" + String::load(IDS_MUST_RESTORE_IFM_FILES_AGAIN); } state.SetFailureMessage(message); } String GetSbsLimitMessage() { LOG_FUNCTION(GetSbsLimitMessage); static String SBSLIMIT_DLL(L"sbslimit.dll"); String message; HMODULE sbsDll = 0; HRESULT hr = Win::LoadLibraryEx( SBSLIMIT_DLL, LOAD_LIBRARY_AS_DATAFILE, sbsDll); if (FAILED(hr)) { LOG(L"Unable to load SBSLIMIT_DLL"); // fall back to a message of our own message = String::load(IDS_SBS_LIMITATION_MESSAGE); } else { // string 3 is the dcpromo message message = Win::LoadString(3, sbsDll); HRESULT unused = Win::FreeLibrary(sbsDll); ASSERT(SUCCEEDED(unused)); } return message; } // Check if this is a Small Business Server product; if so, present throw an // error, as SBS should only allow new forest & demote. Rather brutal to do // it in this fashion, but SBS users should not be using dcpromo directly. // 353854, 353856 void CheckSmallBusinessServerLimitations(HWND hwnd) throw (DS::Error) { LOG_FUNCTION(CheckSmallBusinessServerLimitations); ASSERT(Win::IsWindow(hwnd)); State& state = State::GetInstance(); State::Operation op = state.GetOperation(); switch (op) { case State::TREE: case State::CHILD: case State::REPLICA: { // Tree and child operations are not allowed with the SBS product. // Replica is allowed, if it is of a forest root domain. OSVERSIONINFOEX info; HRESULT hr = Win::GetVersionEx(info); BREAK_ON_FAILED_HRESULT(hr); if (info.wSuiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED) { if (op == State::REPLICA) { String domain = state.GetReplicaDomainDNSName(); // Since domain has been previously validated by calling // DsGetDcName, we don't anticipate that GetForestName will // have any difficulty. HRESULT hr = S_OK; String forest = GetForestName(domain, &hr); if (FAILED(hr)) { ShowDcNotFoundErrorDialog( hwnd, -1, domain, String::load(IDS_WIZARD_TITLE), String::format(IDS_DC_NOT_FOUND, domain.c_str()), false); throw DS::Error( hr, String::format( IDS_UNABLE_TO_DETERMINE_FOREST, domain.c_str()), String::load(IDS_WIZARD_TITLE)); } DNS_RELATE_STATUS compare = Dns::CompareNames(domain, forest); if (compare == DnsNameCompareEqual) { LOG(L"replica is of forest root, allowing promote"); break; } } // This machine is an SBS machine under restricted license. // Extract an error message from an SBS dll. LOG(L"Is SBS Restricted"); String message = GetSbsLimitMessage(); // do not call state.SetOperationResultsMessage with this // message, rather, include it in the thrown error. throw DS::Error( S_OK, // don't trigger cred retry message, String::load(IDS_SMALL_BUSINESS_LIMIT)); } break; } case State::DEMOTE: case State::FOREST: case State::ABORT_BDC_UPGRADE: case State::NONE: default: { // do nothing break; } } } // Picks the name of a domain controller suitable for the creation of a // replica. Since a server must be a member of the domain before it can be // made a replica of that domain, the server may also be joined to the domain // before the replica operation is attempted. // // We need to ensure that the domain controller used to join the domain is the // same domain controller used to replicate the domain. Also, since a machine // account for the server may already exist on one or more -- but not // necessarily all -- domain controllers, we need to pick a domain controller // that has that machine account. 406462 // // domainName - DNS domain name of domain for which a replica is to be found. // // resultDcName - receives the located name, or the empty string on failure. HRESULT GetJoinAndReplicaDcName(const String& domainName, String& resultDcName) { LOG_FUNCTION(GetJoinAndReplicaDcName); ASSERT(!domainName.empty()); resultDcName.erase(); HRESULT hr = S_OK; do { // determine the local computer's domain machine account name. This is the // name of the local computer, plus a "$" String netbiosName = Win::GetComputerNameEx(ComputerNameNetBIOS); String accountName = netbiosName + L"$"; LOG(accountName); // look for a domain controller that has a machine account for the local // computer. Not all domain controllers may have this account, due to // replication latency. DOMAIN_CONTROLLER_INFO* info = 0; hr = MyDsGetDcNameWithAccount( 0, accountName, UF_WORKSTATION_TRUST_ACCOUNT | UF_SERVER_TRUST_ACCOUNT, domainName, DS_DIRECTORY_SERVICE_REQUIRED | DS_FORCE_REDISCOVERY, info); BREAK_ON_FAILED_HRESULT(hr); ASSERT(info->DomainControllerName); if (info->DomainControllerName) { resultDcName = Computer::RemoveLeadingBackslashes(info->DomainControllerName); LOG(resultDcName); } ::NetApiBufferFree(info); if (!resultDcName.empty()) { return hr; } } while (0); // either there is no domain controller reachable with the required // account, or the account does not exist, or DsGetDcName returned an // empty name LOG(L"Falling back to non-account DsGetDcName"); return GetDcName(domainName, resultDcName); } void EvaluateRoleChangeState() throw (DS::Error) { LOG_FUNCTION(EvaluateRoleChangeState); int messageResId = 0; DSROLE_OPERATION_STATE opState = ::DsRoleOperationIdle; DSROLE_OPERATION_STATE_INFO* info = 0; HRESULT hr = MyDsRoleGetPrimaryDomainInformation(0, info); if (SUCCEEDED(hr) && info) { opState = info->OperationState; ::DsRoleFreeMemory(info); } else { throw DS::Error( hr, String::load(IDS_UNABLE_TO_DETERMINE_OP_STATE), String::load(IDS_WIZARD_TITLE)); } switch (opState) { case ::DsRoleOperationIdle: { // do nothing break; } case ::DsRoleOperationActive: { // a role change operation is underway messageResId = IDS_ROLE_CHANGE_IN_PROGRESS; break; } case ::DsRoleOperationNeedReboot: { // a role change has already taken place, need to reboot before // attempting another. messageResId = IDS_ROLE_CHANGE_NEEDS_REBOOT; break; } default: { ASSERT(false); break; } } if (messageResId) { throw DS::Error( S_OK, String::load(messageResId), String::load(IDS_WIZARD_TITLE)); } } // Verify that the current role of the machine is correct for the type of // operation we're about to attempt. Throw an exception if it is not. void DoubleCheckRoleChangeState() throw (DS::Error) { LOG_FUNCTION(DoubleCheckRoleChangeState); // Make sure that an operation is not in progress or pending reboot. EvaluateRoleChangeState(); State& state = State::GetInstance(); Computer& computer = state.GetComputer(); HRESULT hr = computer.Refresh(); if (FAILED(hr)) { throw DS::Error( hr, String::load(IDS_UNABLE_TO_DETERMINE_COMPUTER_CONFIG), String::load(IDS_WIZARD_TITLE)); } switch (state.GetOperation()) { case State::TREE: case State::CHILD: case State::REPLICA: case State::FOREST: case State::ABORT_BDC_UPGRADE: { // Make sure the machine is not already a DC. If the machine is // an NT4 DC finishing upgrade, then its role will be member // server, not domain controller. if (computer.IsDomainController()) { throw DS::Error( S_OK, String::load(IDS_MACHINE_IS_ALREADY_DC), String::load(IDS_WIZARD_TITLE)); } break; } case State::DEMOTE: { // Make sure the machine is still a DC if (!computer.IsDomainController()) { throw DS::Error( S_OK, String::load(IDS_MACHINE_IS_NOT_ALREADY_DC), String::load(IDS_WIZARD_TITLE)); } break; } case State::NONE: default: { ASSERT(false); // do nothing break; } } } // thread launched by ProgressDialog::OnInit. // CODEWORK: Man, this function has evolved into a real mess. void PromoteThreadProc(ProgressDialog& progress) { LOG_FUNCTION(PromoteThreadProc); // // Access to members of ProgressDialog is not, by default, threadsafe. // However, since the only members we access are atomic data types, this // is not a problem. Note also that calls to ProgressDialog Update // methods usually resolve to calls to SendMessage on UI elements of the // dialog. This too is threadsafe, as SendMessage is always executed // in the thread that created the window (tho it may block the calling // thread). // UINT message = ProgressDialog::THREAD_SUCCEEDED; bool retry = false; bool wasDisjoined = false; State& state = State::GetInstance(); String originalDomainName; // a reference, as we will refresh the object Computer& computer = state.GetComputer(); State::RunContext context = state.GetRunContext(); do { LOG(L"top of retry loop"); DisableConsoleLocking(); // clear the state of the operation attempt bool exceptionWasThrown = false; Win::Error errorThrown(0, 0); message = ProgressDialog::THREAD_SUCCEEDED; retry = false; state.SetOperationResultsMessage(String()); state.SetOperationResultsFlags(0); progress.UpdateText(IDS_STARTING); try { CheckSmallBusinessServerLimitations(progress.GetHWND()); // Double check that the role of the machine is still ok for the // operation to proceed. This is mostly a paranoid check, but there // have been cases during development where the promotion actually // succeeded, but reported a failure, and attempting the operation // again trashes the DS. Such problems indicate the presence of // other serious bugs, but if we can cheaply avoid zorching a DC, // then bully for us. // NTRAID#NTBUG9-345115-2001/03/23-sburns DoubleCheckRoleChangeState(); switch (state.GetOperation()) { case State::REPLICA: { // if we're using an answerfile, look for a replication partner // there. 107143 String replDc; if (state.UsingAnswerFile()) { replDc = state.GetAnswerFileOption( State::OPTION_REPLICATION_SOURCE); state.SetReplicationPartnerDC(replDc); } if (context != State::BDC_UPGRADE) { String replicaDnsDomainName = state.GetReplicaDomainDNSName(); if (!computer.IsJoinedToDomain(replicaDnsDomainName) ) { // need to join the domain we will replicate. Determine // the name of a domain controller to use for join and // replication. 270233 if (replDc.empty()) { // answerfile did not specify a dc. So pick one // ourselves. HRESULT hr = GetJoinAndReplicaDcName( replicaDnsDomainName, replDc); if (FAILED(hr)) { throw DS::Error( hr, IDS_JOIN_DOMAIN_FAILED); } state.SetReplicationPartnerDC(replDc); } if (computer.IsJoinedToDomain()) { originalDomainName = computer.GetDomainNetbiosName(); } progress.UpdateText(IDS_CHANGING_DOMAIN); // this will unjoin if necessary DS::JoinDomain( replicaDnsDomainName, replDc, state.GetUsername(), state.GetPassword(), state.GetUserDomainName()); if (ComputerWasRenamedAndNeedsReboot()) { // If we make it to this point, the machine was joined // to a domain, and the name changed as a side-effect, // and will need to be rebooted even if the promote // fails. Set a flag to note that fact. // NTRAID#NTBUG9-346120-2001/04/04-sburns state.SetNeedsReboot(); } HRESULT hr = computer.Refresh(); ASSERT(SUCCEEDED(hr)); if (!originalDomainName.empty()) { wasDisjoined = true; } } DS::CreateReplica(progress); } else { DS::UpgradeBDC(progress); } break; } case State::FOREST: case State::TREE: case State::CHILD: { if (context != State::PDC_UPGRADE) { if (computer.IsJoinedToDomain()) { // need to unjoin the domain we belong to originalDomainName = computer.GetDomainNetbiosName(); ASSERT(!originalDomainName.empty()); progress.UpdateText( String::format(IDS_DISJOINING_PROGRESS, originalDomainName.c_str())); if (!DS::DisjoinDomain()) { // the computer account was not removed. if (!state.RunHiddenUnattended()) { popup.Info( progress.GetHWND(), String::load(IDS_COULDNT_REMOVE_COMPUTER_ACCOUNT_TEXT)); } } if (ComputerWasRenamedAndNeedsReboot()) { // If we make it to this point, the machine was // disjoined from a domain, and the name changed as a // side-effect, and will need to be rebooted even if // the promote fails. Set a flag to note that fact. // NTRAID#NTBUG9-346120-2001/04/04-sburns state.SetNeedsReboot(); } HRESULT hr = computer.Refresh(); ASSERT(SUCCEEDED(hr)); wasDisjoined = true; } DS::CreateNewDomain(progress); } else { DS::UpgradePDC(progress); } break; } case State::ABORT_BDC_UPGRADE: { ASSERT(state.GetRunContext() == State::BDC_UPGRADE); DS::AbortBDCUpgrade(); break; } case State::DEMOTE: { DS::DemoteDC(progress); break; } case State::NONE: default: { ASSERT(false); message = ProgressDialog::THREAD_FAILED; } } // // At this point, the operation was successfully completed. // DoPostOperationStuff(progress); state.SetOperationResults(State::SUCCESS); state.SetNeedsReboot(); } catch (const Win::Error& err) { LOG(L"Exception caught"); exceptionWasThrown = true; errorThrown = err; LOG(L"catch completed"); } if (exceptionWasThrown) { LOG(L"handling exception"); // go interactive from now on state.ClearHiddenWhileUnattended(); // 22935 if ( state.GetRunContext() != State::PDC_UPGRADE and state.GetRunContext() != State::BDC_UPGRADE) { // re-enable console locking if not a downlevel upgrade 28496 EnableConsoleLocking(); } state.SetOperationResults(State::FAILURE); progress.UpdateText(String()); message = ProgressDialog::THREAD_FAILED; HRESULT errorThrownHresult = errorThrown.GetHresult(); if (!state.IsOperationRetryAllowed()) { // The operation failure was such that the user should not be // allowed to retry it. In this case, we skip our special-case // handling of known failure codes (as expressed by the other else // if clauses here), and just report the failure. // // NTRAID#NTBUG9-296872-2001/01/29-sburns retry = false; } else if ( errorThrownHresult == Win32ToHresult(ERROR_ACCESS_DENIED) or errorThrownHresult == Win32ToHresult(ERROR_LOGON_FAILURE) or errorThrownHresult == Win32ToHresult(ERROR_NOT_AUTHENTICATED) or errorThrownHresult == Win32ToHresult(RPC_S_SEC_PKG_ERROR) or errorThrownHresult == Win32ToHresult(ERROR_DS_DRA_ACCESS_DENIED) or errorThrownHresult == Win32ToHresult(ERROR_INVALID_PASSWORD) or errorThrownHresult == Win32ToHresult(ERROR_PASSWORD_EXPIRED) or errorThrownHresult == Win32ToHresult(ERROR_ACCOUNT_DISABLED) or errorThrownHresult == Win32ToHresult(ERROR_ACCOUNT_LOCKED_OUT) ) { // bad credentials. ask for new ones String failureMessage = ComposeFailureMessageHelper( errorThrown, IDS_OPERATION_FAILED_GET_CRED_NO_RESULT, IDS_OPERATION_FAILED_GET_CRED); GetCredentialsDialog dlg(failureMessage); if (dlg.ModalExecute(progress) == IDOK) { retry = true; // jump to top of operation loop continue; } LOG(L"credential retry canceled"); ComposeFailureMessage( errorThrown, wasDisjoined, originalDomainName); break; } else if (errorThrownHresult == Win32ToHresult(ERROR_DOMAIN_EXISTS)) { LOG(L"domain exists: prompting for re-install"); // ask if the user wishes to reinstall the domain. if ( popup.MessageBox( progress.GetHWND(), String::format( IDS_REINSTALL_DOMAIN_MESSAGE, state.GetNewDomainDNSName().c_str()), MB_YESNO | MB_ICONWARNING) == IDYES) { state.SetDomainReinstallFlag(true); retry = true; // jump to top of operation loop continue; } LOG(L"reinstall domain retry canceled"); } else if ( errorThrownHresult == Win32ToHresult(ERROR_DOMAIN_CONTROLLER_EXISTS)) { LOG(L"domain controller exists: prompting to force promote"); // ask if the user wants to re-install the domain controller if ( popup.MessageBox( progress.GetHWND(), String::format( IDS_REINSTALL_DOMAIN_CONTROLLER_MESSAGE, state.GetComputer().GetNetbiosName().c_str()), MB_YESNO | MB_ICONWARNING) == IDYES) { state.SetDomainControllerReinstallFlag(true); retry = true; // jump to the top of the operation loop continue; } LOG(L"reinstall domain controller retry canceled"); } // if we're retrying, then we should have jumped to the top of // the loop. ASSERT(!retry); ComposeFailureMessage( errorThrown, wasDisjoined, originalDomainName); Win::MessageBox( progress.GetHWND(), state.GetFailureMessage(), errorThrown.GetSummary(), // title the error was built with MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); } } while (retry); #ifdef DBG if (message == ProgressDialog::THREAD_FAILED) { ASSERT(state.GetOperationResultsCode() == State::FAILURE); } else { ASSERT(state.GetOperationResultsCode() == State::SUCCESS); } #endif LOG(L"posting message to progress window"); HRESULT hr = Win::PostMessage(progress.GetHWND(), message, 0, 0); ASSERT(SUCCEEDED(hr)); // do not call _endthread here, or stack will not be properly cleaned up } bool ConfirmationPage::OnCommand( HWND windowFrom, unsigned controlIdFrom, unsigned code) { bool result = false; switch (controlIdFrom) { case IDCANCEL: { // multi-line edit control eats escape keys. This is a workaround // from ericb, to forward the message to the prop sheet. Win::SendMessage( Win::GetParent(hwnd), WM_COMMAND, MAKEWPARAM(controlIdFrom, code), (LPARAM) windowFrom); break; } case IDC_MESSAGE: { switch (code) { case EN_SETFOCUS: { if (needToKillSelection) { // kill the text selection Win::Edit_SetSel(windowFrom, -1, -1); needToKillSelection = false; result = true; } break; } case MultiLineEditBoxThatForwardsEnterKey::FORWARDED_ENTER: { // our subclasses mutli-line edit control will send us // WM_COMMAND messages when the enter key is pressed. We // reinterpret this message as a press on the default button of // the prop sheet. // This workaround from phellyar. // NTRAID#NTBUG9-232092-2000/11/22-sburns HWND propSheet = Win::GetParent(hwnd); WORD defaultButtonId = LOWORD(Win::SendMessage(propSheet, DM_GETDEFID, 0, 0)); // we expect that there is always a default button on the prop sheet ASSERT(defaultButtonId); Win::SendMessage( propSheet, WM_COMMAND, MAKELONG(defaultButtonId, BN_CLICKED), 0); result = true; break; } } break; } default: { // do nothing break; } } return result; }