// Copyright (c) 1997-1999 Microsoft Corporation // // Computer naming tool // // 12-1-97 sburns // 10-26-1999 sburns (redone) #include "headers.hxx" static const wchar_t* TCPIP_PARAMS_KEY = L"System\\CurrentControlSet\\Services\\Tcpip\\Parameters"; static const wchar_t* TCPIP_POLICY_KEY = L"Software\\Policies\\Microsoft\\System\\DNSclient"; static const wchar_t* NEW_HOSTNAME_VALUE = L"NV Hostname"; static const wchar_t* NEW_SUFFIX_VALUE = L"NV Domain"; // rather than make this a nested type inside the Computer class, I chose // to hide it completely as a private struct in the implementation file. struct ComputerState { bool isLocal; bool isDomainJoined; bool searchedForDnsDomainNames; String netbiosComputerName; String dnsComputerName; String netbiosDomainName; String dnsDomainName; String dnsForestName; NT_PRODUCT_TYPE realProductType; Computer::Role role; DWORD verMajor; DWORD verMinor; DWORD safebootOption; ComputerState() : isLocal(false), isDomainJoined(false), searchedForDnsDomainNames(false), netbiosComputerName(), dnsComputerName(), netbiosDomainName(), dnsDomainName(), dnsForestName(), realProductType(NtProductWinNt), role(Computer::STANDALONE_WORKSTATION), verMajor(0), verMinor(0), safebootOption(0) { } // implicit dtor used }; String Computer::RemoveLeadingBackslashes(const String& computerName) { LOG_FUNCTION2(Computer::RemoveLeadingBackslashes, computerName); static const String BACKSLASH(L"\\"); String s = computerName; if (s.length() >= 2) { if ((s[0] == BACKSLASH[0]) && (s[1] == BACKSLASH[0])) { // remove the backslashes s.erase(0, 2); } } return s; } // Removes leading backslashes and trailing whitespace, if present, and // returns the result. // // name - string from which leading backslashes and trailing whitespace is to // be stripped. static String MassageName(const String& name) { String result = Computer::RemoveLeadingBackslashes(name); result = result.strip(String::TRAILING, 0); return result; } Computer::Computer(const String& name) : ctorName(MassageName(name)), isRefreshed(false), state(0) { LOG_CTOR(Computer); } Computer::~Computer() { LOG_DTOR(Computer); delete state; state = 0; } Computer::Role Computer::GetRole() const { LOG_FUNCTION2(Computer::GetRole, GetNetbiosName()); ASSERT(isRefreshed); Role result = STANDALONE_WORKSTATION; if (state) { result = state->role; } LOG(String::format(L"role: %1!X!", result)); return result; } bool Computer::IsDomainController() const { LOG_FUNCTION2(Computer::IsDomainController, GetNetbiosName()); ASSERT(isRefreshed); bool result = false; switch (GetRole()) { case PRIMARY_CONTROLLER: case BACKUP_CONTROLLER: { result = true; break; } case STANDALONE_WORKSTATION: case MEMBER_WORKSTATION: case STANDALONE_SERVER: case MEMBER_SERVER: default: { // do nothing break; } } LOG( String::format( L"%1 a domain controller", result ? L"is" : L"is not")); return result; } String Computer::GetNetbiosName() const { LOG_FUNCTION(Computer::GetNetbiosName); ASSERT(isRefreshed); String result; if (state) { result = state->netbiosComputerName; } LOG(result); return result; } String Computer::GetFullDnsName() const { LOG_FUNCTION2(Computer::GetFullDnsName, GetNetbiosName()); ASSERT(isRefreshed); String result; if (state) { result = state->dnsComputerName; } LOG(result); return result; } // Updates the dnsDomainName and dnsForestName members of the supplied // ComputerState instance, if either of members are empty, and we have reason // to believe that it's appropriate to attempt to do so. // // Called by methods that are looking for the dnsDomainName and / or // dnsForestName to ensure that those values are present. They might not be // read when the Computer instance is refreshed (which is normally the case), // because the domain may have been upgraded since the time that the machine // was joined to that domain. // // state - ComputerState instance to update. void GetDnsDomainNamesIfNeeded(ComputerState& state) { LOG_FUNCTION(GetDnsDomainNamesIfNeeded); if ( // only applies if joined to a domain state.isDomainJoined // either name might be missing. && (state.dnsDomainName.empty() || state.dnsForestName.empty()) // should always be true, but just in case, && !state.netbiosDomainName.empty() // don't search again -- it's too expensive && !state.searchedForDnsDomainNames) { DOMAIN_CONTROLLER_INFO* info = 0; HRESULT hr = MyDsGetDcName( state.isLocal ? 0 : state.netbiosComputerName.c_str(), state.netbiosDomainName, DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME, info); if (SUCCEEDED(hr) && info) { if ((info->Flags & DS_DNS_DOMAIN_FLAG) && info->DomainName) { // we found a DS domain state.dnsDomainName = info->DomainName; ASSERT(info->DnsForestName); ASSERT(info->Flags & DS_DNS_FOREST_FLAG); if (info->DnsForestName) { state.dnsForestName = info->DnsForestName; } } ::NetApiBufferFree(info); } // flag the fact that we've looked, so we don't look again, as the // search is expensive. state.searchedForDnsDomainNames = true; } } String Computer::GetDomainDnsName() const { LOG_FUNCTION2(Computer::GetDomainDnsName, GetNetbiosName()); ASSERT(isRefreshed); String result; if (state) { GetDnsDomainNamesIfNeeded(*state); result = state->dnsDomainName; } LOG(result); return result; } String Computer::GetForestDnsName() const { LOG_FUNCTION2(Computer::GetForestDnsName, GetNetbiosName()); ASSERT(isRefreshed); String result; if (state) { GetDnsDomainNamesIfNeeded(*state); result = state->dnsForestName; } LOG(result); return result; } String Computer::GetDomainNetbiosName() const { LOG_FUNCTION2(Computer::GetDomainNetbiosName, GetNetbiosName()); ASSERT(isRefreshed); String result; if (state) { result = state->netbiosDomainName; } LOG(result); return result; } // I can't think why I would need to do this, so I'm not. // // static // String // canonicalizeComputerName(const String& computerName) // { // LOG_FUNCTION2(canonicalizeComputerName, computerName); // // if (ValidateNetbiosComputerName(computerName) == VALID_NAME) // { // String s(MAX_COMPUTERNAME_LENGTH, 0); // // NET_API_STATUS err = // I_NetNameCanonicalize( // 0, // const_cast(computerName.c_str()), // const_cast(s.c_str()), // s.length() * sizeof(wchar_t), // NAMETYPE_COMPUTER, // 0); // if (err == NERR_Success) // { // // build a new string without trailing null characters. // return String(s.c_str()); // } // } // // return String(); // } bool Computer::IsJoinedToDomain() const { LOG_FUNCTION2(Computer::IsJoinedToDomain, GetNetbiosName()); ASSERT(isRefreshed); bool result = false; if (state) { result = state->isDomainJoined; } LOG( String::format( L"%1 domain joined", result ? L"is" : L"is not")); return result; } bool Computer::IsJoinedToWorkgroup() const { LOG_FUNCTION2(Computer::IsJoinedToWorkgroup, GetNetbiosName()); ASSERT(isRefreshed); bool result = !IsJoinedToDomain(); LOG( String::format( L"%1 domain joined", result ? L"is" : L"is not")); return result; } bool Computer::IsJoinedToDomain(const String& domainDnsName) const { LOG_FUNCTION2(Computer::IsJoinedToDomain, domainDnsName); ASSERT(!domainDnsName.empty()); ASSERT(isRefreshed); bool result = false; if (!domainDnsName.empty()) { if (state && IsJoinedToDomain()) { String d1 = GetDomainDnsName().strip(String::TRAILING, L'.'); String d2 = String(domainDnsName).strip(String::TRAILING, L'.'); result = (d1.icompare(d2) == 0); } } LOG( String::format( L"%1 joined to %2", result ? L"is" : L"is NOT", domainDnsName.c_str())); return result; } bool Computer::IsLocal() const { LOG_FUNCTION2(Computer::IsLocal, GetNetbiosName()); ASSERT(isRefreshed); bool result = false; if (state) { result = state->isLocal; } LOG( String::format( L"%1 local machine", result ? L"is" : L"is not")); return result; } // Updates the following members of the ComputerState parameter with values // from the local machine: // // netbiosComputerName // dnsComputerName // verMajor // verMinor // // Note that the dnsComputerName may not have a value if tcp/ip is not // installed and properly configured. This is not considered an error // condition. // // Returns S_OK on success, or a failure code otherwise. // // state - the ComputerState instance to be updated. HRESULT RefreshLocalInformation(ComputerState& state) { LOG_FUNCTION(RefreshLocalInformation); state.netbiosComputerName = Win::GetComputerNameEx(ComputerNameNetBIOS); state.dnsComputerName = Win::GetComputerNameEx(ComputerNameDnsFullyQualified); HRESULT hr = S_OK; do { OSVERSIONINFO verInfo; hr = Win::GetVersionEx(verInfo); BREAK_ON_FAILED_HRESULT(hr); state.verMajor = verInfo.dwMajorVersion; state.verMinor = verInfo.dwMinorVersion; } while (0); return hr; } // Read the registry of a remote machine to determine the fully-qualified // DNS computer name of that machine, taking into account any policy-imposed // DNS suffix. // // On success, stores the result in the dnsComputerName member of the supplied // ComputerState instance, and returns S_OK; // // On failure, clears the dnsComputerName member, and returns a failure code. // Thre failure code (ERROR_FILE_NOT_FOUND) may indicate that the registry // value(s) are not present, which means that TCP/IP is not installed or is // not properly configured on the remote machine. // // remoteRegHKLM - HKEY previously opened to the HKEY_LOCAL_MACHINE hive of // the remote computer. // // state - ComputerState instance to be updated with the resulting name. HRESULT DetermineRemoteDnsComputerName( HKEY remoteRegHKLM, ComputerState& state) { state.dnsComputerName.erase(); String hostname; String suffix; String policySuffix; bool policyInEffect = false; HRESULT hr = S_OK; do { RegistryKey key; hr = key.Open(remoteRegHKLM, TCPIP_PARAMS_KEY); BREAK_ON_FAILED_HRESULT(hr); // Read these values without checking for failure, as empty string // is ok. hostname = key.GetString(L"Hostname"); suffix = key.GetString(L"Domain"); // We need to check to see if there is a policy-supplied dns suffix if (state.realProductType != NtProductLanManNt) { hr = key.Open(remoteRegHKLM, TCPIP_POLICY_KEY); if (SUCCEEDED(hr)) { hr = key.GetValue(L"PrimaryDnsSuffix", policySuffix); if (SUCCEEDED(hr)) { // a policy-supplied computer DNS domain name is in effect. policyInEffect = true; } } } } while (0); if (!hostname.empty()) { state.dnsComputerName = Computer::ComposeFullDnsComputerName( hostname, policyInEffect ? policySuffix : suffix); } return hr; } // Returns true if the computer represented by the provided ComputerState is // a domain controller booted in DS repair mode. Returns false otherwise. // // state - a "filled-in" ComputerState instance. bool IsDcInRepairMode(const ComputerState& state) { LOG_FUNCTION(IsDcInRepairMode); if ( state.safebootOption == SAFEBOOT_DSREPAIR && state.realProductType == NtProductLanManNt) { return true; } return false; } // Sets the following members of the supplied ComputerState struct, based on // the current values of the isLocal and netbiosComputerName members of the // same struct. Returns S_OK on success, or an error code on failure. // // role // isDomainJoined // // optionally sets the following, if applicable: // // dnsForestName // dnsDomainName // netbiosDomainName // // state - instance with isLocal and netbiosComputerName members previously // set. The members mentioned above will be overwritten. HRESULT DetermineRoleAndMembership(ComputerState& state) { LOG_FUNCTION(DetermineRoleAndMembership); HRESULT hr = S_OK; do { DSROLE_PRIMARY_DOMAIN_INFO_BASIC* info = 0; hr = MyDsRoleGetPrimaryDomainInformation( state.isLocal ? 0 : state.netbiosComputerName.c_str(), info); BREAK_ON_FAILED_HRESULT(hr); if (info->DomainNameFlat) { state.netbiosDomainName = info->DomainNameFlat; } if (info->DomainNameDns) { // not always present, even if the domain is NT 5. See note // for GetDnsDomainNamesIfNeeded. state.dnsDomainName = info->DomainNameDns; } if (info->DomainForestName) { // not always present, even if the domain is NT 5. See note // for GetDnsDomainNamesIfNeeded. state.dnsForestName = info->DomainForestName; } switch (info->MachineRole) { case DsRole_RoleStandaloneWorkstation: { state.role = Computer::STANDALONE_WORKSTATION; state.isDomainJoined = false; break; } case DsRole_RoleMemberWorkstation: { state.role = Computer::MEMBER_WORKSTATION; state.isDomainJoined = true; break; } case DsRole_RoleStandaloneServer: { state.role = Computer::STANDALONE_SERVER; state.isDomainJoined = false; // I wonder if we're really a DC booted in ds repair mode? if (IsDcInRepairMode(state)) { LOG(L"machine is in ds repair mode"); state.role = Computer::BACKUP_CONTROLLER; state.isDomainJoined = true; // the domain name will be reported as "WORKGROUP", which // is wrong, but that's the way the ds guys wanted it. } break; } case DsRole_RoleMemberServer: { state.role = Computer::MEMBER_SERVER; state.isDomainJoined = true; break; } case DsRole_RolePrimaryDomainController: { state.role = Computer::PRIMARY_CONTROLLER; state.isDomainJoined = true; break; } case DsRole_RoleBackupDomainController: { state.role = Computer::BACKUP_CONTROLLER; state.isDomainJoined = true; break; } default: { ASSERT(false); break; } } ::DsRoleFreeMemory(info); } while (0); if (FAILED(hr)) { // infer a best-guess on the role from the product type state.isDomainJoined = false; switch (state.realProductType) { case NtProductWinNt: { state.role = Computer::STANDALONE_WORKSTATION; break; } case NtProductServer: { state.role = Computer::STANDALONE_SERVER; break; } case NtProductLanManNt: { state.isDomainJoined = true; state.role = Computer::BACKUP_CONTROLLER; break; } default: { ASSERT(false); break; } } } return hr; } // Sets the isLocal member of the provided ComputerState instance to true if // the given name refers to the local computer (the computer on which this // code is executed). Sets the isLocal member to false if not, or on error. // Returns S_OK on success or an error code on failure. May also set the // following: // // netbiosComputerName // verMajor // verMinor // // Essentially the same as Win::IsLocalComputer; we repeat most of the code // here to avoid a possibly redundant call to NetWkstaGetInfo. // // state - instance of ComputerState to be modified. // // ctorName - the computer name with which an instance of Computer was // constructed. This may be any computer name form (netbios, dns, ip // address). HRESULT IsLocalComputer(ComputerState& state, const String& ctorName) { LOG_FUNCTION(IsLocalComputer); HRESULT hr = S_OK; bool result = false; do { if (ctorName.empty()) { // an unnamed computer always represent the local computer. result = true; break; } String localNetbiosName = Win::GetComputerNameEx(ComputerNameNetBIOS); if (ctorName.icompare(localNetbiosName) == 0) { result = true; break; } String localDnsName = Win::GetComputerNameEx(ComputerNameDnsFullyQualified); if (ctorName.icompare(localDnsName) == 0) { // the given name is the same as the fully-qualified dns name result = true; break; } // we don't know what kind of name it is. Ask the workstation service // to resolve the name for us, and see if the result refers to the // local machine. // NetWkstaGetInfo returns the netbios name for a given machine, given // a DNS, netbios, or IP address. WKSTA_INFO_100* info = 0; hr = MyNetWkstaGetInfo(ctorName, info); BREAK_ON_FAILED_HRESULT(hr); state.netbiosComputerName = info->wki100_computername; state.verMajor = info->wki100_ver_major; state.verMinor = info->wki100_ver_minor; ::NetApiBufferFree(info); if (state.netbiosComputerName.icompare(localNetbiosName) == 0) { // the given name is the same as the netbios name result = true; break; } } while (0); state.isLocal = result; return hr; } HRESULT Computer::Refresh() { LOG_FUNCTION(Computer::Refresh); // erase all the state that we may have set before isRefreshed = false; delete state; state = new ComputerState; HRESULT hr = S_OK; HKEY registryHKLM = 0; do { // First, determine if the computer this instance represents is the // local computer. hr = IsLocalComputer(*state, ctorName); BREAK_ON_FAILED_HRESULT(hr); // Next, based on whether the machine is local or not, populate the // netbios and dns computer names, and version information. if (state->isLocal) { // netbiosComputerName // dnsComputerName // version hr = RefreshLocalInformation(*state); BREAK_ON_FAILED_HRESULT(hr); } else { // IsLocalComputer has already set: // netbiosComputerName // version ASSERT(!state->netbiosComputerName.empty()); ASSERT(state->verMajor); } // We will need to examine the registry to determine the safeboot // option and real product type. hr = Win::RegConnectRegistry( state->isLocal ? String() : L"\\\\" + state->netbiosComputerName, HKEY_LOCAL_MACHINE, registryHKLM); BREAK_ON_FAILED_HRESULT(hr); hr = GetProductTypeFromRegistry(registryHKLM, state->realProductType); BREAK_ON_FAILED_HRESULT(hr); if (!state->isLocal) { // still need to get dnsComputerName, which we can do now that // we know the real product type. // The DNS Computer name can be determined be reading the remote // registry. hr = DetermineRemoteDnsComputerName(registryHKLM, *state); if (FAILED(hr) && hr != Win32ToHresult(ERROR_FILE_NOT_FOUND)) { // if the DNS registry settings are not present, that's ok. // but otherwise: BREAK_ON_FAILED_HRESULT(hr); } } // We'll need to know the safeboot option to determine the role // in the next step. hr = GetSafebootOption(registryHKLM, state->safebootOption); if (FAILED(hr) && hr != Win32ToHresult(ERROR_FILE_NOT_FOUND)) { // if the safeboot registry settings are not present, that's ok. // but otherwise: BREAK_ON_FAILED_HRESULT(hr); } // Next, determine the machine role and domain membership hr = DetermineRoleAndMembership(*state); BREAK_ON_FAILED_HRESULT(hr); } while (0); if (registryHKLM) { Win::RegCloseKey(registryHKLM); } if (SUCCEEDED(hr)) { isRefreshed = true; } return hr; } String Computer::ComposeFullDnsComputerName( const String& hostname, const String& domainSuffix) { LOG_FUNCTION2( Computer::ComposeFullDnsComputerName, String::format( L"hostname: %1 suffix: %2", hostname.c_str(), domainSuffix.c_str())); ASSERT(!hostname.empty()); // The domain name may be empty if the machine is unjoined... if (domainSuffix.empty() || domainSuffix == L".") { // "computername." return hostname + L"."; } // "computername.domain" return hostname + L"." + domainSuffix; } HRESULT Computer::GetSafebootOption(HKEY regHKLM, DWORD& result) { LOG_FUNCTION(GetSafebootOption); ASSERT(regHKLM); result = 0; HRESULT hr = S_OK; RegistryKey key; do { hr = key.Open( regHKLM, L"System\\CurrentControlSet\\Control\\SafeBoot\\Option"); BREAK_ON_FAILED_HRESULT(hr); hr = key.GetValue(L"OptionValue", result); BREAK_ON_FAILED_HRESULT(hr); } while (0); LOG(String::format(L"returning : 0x%1!X!", result)); return hr; } HRESULT Computer::GetProductTypeFromRegistry(HKEY regHKLM, NT_PRODUCT_TYPE& result) { LOG_FUNCTION(GetProductTypeFromRegistry); ASSERT(regHKLM); result = NtProductWinNt; HRESULT hr = S_OK; RegistryKey key; do { hr = key.Open( regHKLM, L"System\\CurrentControlSet\\Control\\ProductOptions"); BREAK_ON_FAILED_HRESULT(hr); String prodType; hr = key.GetValue(L"ProductType", prodType); BREAK_ON_FAILED_HRESULT(hr); LOG(prodType); // see ntos\rtl\prodtype.c, which uses case-insensitive unicode string // compare. if (prodType.icompare(L"WinNt") == 0) { result = NtProductWinNt; } else if (prodType.icompare(L"LanmanNt") == 0) { result = NtProductLanManNt; } else if (prodType.icompare(L"ServerNt") == 0) { result = NtProductServer; } else { LOG(L"unknown product type, assuming workstation"); } } while (0); LOG(String::format(L"prodtype : 0x%1!X!", result)); return hr; } String Computer::GetFuturePhysicalNetbiosName() { LOG_FUNCTION(Computer::GetFuturePhysicalNetbiosName); // the default future name is the existing name. String name = Win::GetComputerNameEx(ComputerNamePhysicalNetBIOS); RegistryKey key; HRESULT hr = key.Open(HKEY_LOCAL_MACHINE, REGSTR_PATH_COMPUTRNAME); if (SUCCEEDED(hr)) { hr = key.GetValue(REGSTR_VAL_COMPUTRNAME, name); } LOG_HRESULT(hr); LOG(name); return name; } String Computer::GetActivePhysicalNetbiosName() { LOG_FUNCTION(Computer::GetActivePhysicalNetbiosName); String result = Win::GetComputerNameEx(ComputerNamePhysicalNetBIOS); LOG(result); return result; } // see base\win32\client\compname.c bool Computer::IsDnsSuffixPolicyInEffect(String& policyDnsSuffix) { LOG_FUNCTION(Computer::IsDnsSuffixPolicyInEffect); bool policyInEffect = false; policyDnsSuffix.erase(); NT_PRODUCT_TYPE productType = NtProductWinNt; if (!::RtlGetNtProductType(&productType)) { // on failure, do nothing; assume workstation ASSERT(false); } // read the suffix policy setting from the registry. We used to skip // this for domain controllers in win2k, but .net server supports // dc rename and policy supplied dns suffixes for dcs. // NTRAID#NTBUG9-704838-2002/09/23-sburns RegistryKey key; HRESULT hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_POLICY_KEY); if (SUCCEEDED(hr)) { hr = key.GetValue(L"PrimaryDnsSuffix", policyDnsSuffix); if (SUCCEEDED(hr)) { // a policy-supplied computer DNS domain name is in effect. policyInEffect = true; } } LOG(policyInEffect ? L"true" : L"false"); LOG(policyDnsSuffix); return policyInEffect; } String Computer::GetActivePhysicalFullDnsName() { LOG_FUNCTION(Computer::GetActivePhysicalFullDnsName); // As a workaround to NTRAID#NTBUG9-216349-2000/11/01-sburns, compose our // own full DNS name from the hostname and suffix. String hostname = Win::GetComputerNameEx(ComputerNamePhysicalDnsHostname); String suffix = Win::GetComputerNameEx(ComputerNamePhysicalDnsDomain); String result = Computer::ComposeFullDnsComputerName(hostname, suffix); LOG(result); return result; } String Computer::GetFuturePhysicalFullDnsName() { LOG_FUNCTION(Computer::GetFuturePhysicalFullDnsName); String result = Computer::GetActivePhysicalFullDnsName(); RegistryKey key; HRESULT hr = S_OK; do { hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY); // may be that there are no dns name parameters at all, probably because // tcp/ip is not installed. So the future name == active name BREAK_ON_FAILED_HRESULT(hr); String hostname; hr = key.GetValue(NEW_HOSTNAME_VALUE, hostname); if (FAILED(hr)) { // no new hostname set (or we can't tell what it is). So the future // hostname is the active hostname. hostname = Win::GetComputerNameEx(ComputerNamePhysicalDnsHostname); } String suffix; hr = key.GetValue(NEW_SUFFIX_VALUE, suffix); if (FAILED(hr)) { // no new suffix set (or we can't tell what it is). So the future // suffix is the active suffix. suffix = Win::GetComputerNameEx(ComputerNamePhysicalDnsDomain); } // Decide which suffix -- local or policy -- is in effect String policyDnsSuffix; bool policyInEffect = Computer::IsDnsSuffixPolicyInEffect(policyDnsSuffix); result = Computer::ComposeFullDnsComputerName( hostname, policyInEffect ? policyDnsSuffix : suffix); } while (0); LOG_HRESULT(hr); LOG(result); return result; } // Implementation notes: please don't delete: // Init/Refresh // // if given name empty, // set is local = true // // if !given name empty, // Win::IsLocalComputer(given name), if same set is local = true // // connect to local/remote registry // // read safe boot mode // read real product type // // if local // get local dns name (getcomputernameex) // get local netbios name (getcomputernameex) // get version (getversion) // // if not local // call netwkstagetinfo, // get netbios name // get version // get dns name (account for policy, which requires real prod type) // // call dsrolegpdi (with netbios name if !local) // if succeeded // set role // set netbios domain name // set dns domain name // set dns forest name // set is joined // // if failed // infer role from real product type // // set is dc in restore mode // // // true role // is dc x // is dc in restore mode x // netbios name x // dns name x // is local machine x // dns domain name x // netbios domain name x // is joined to domain X x // is domain joined x // is workgroup joined x // dns forest name x // version x // is booted safe mode x // // // netwkstgetinfo(100) // version // if name is netbios or not // // registry: // real product type // safe boot mode // netbios name // dns name (remember to account for policy) // // dsrolegpdi // role (wrong if in safeboot) // netbios domain name // dns domain name // dns forest name (also available from dsgetdcname)