-
-
Save jborean93/6c3d8b64aa994c2676c6cc249bbf8833 to your computer and use it in GitHub Desktop.
| # Copyright: (c) 2021, Jordan Borean (@jborean93) <[email protected]> | |
| # MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
| Function Get-DomainController { | |
| <# | |
| .SYNOPSIS | |
| Get the domain controller information. | |
| .DESCRIPTION | |
| Returns the name and additional information for the domain controller that matches the criteria specified. | |
| .PARAMETER DomainName | |
| The domain of the domain to search for. | |
| .PARAMETER ComputerName | |
| The name of the server to run the search from. | |
| .PARAMETER DomainGuid | |
| The GUID of the domain to find. This is used if the DC cannot be found by DomainName. | |
| .PARAMETER SiteName | |
| The name of the site where the DC should exist. | |
| .PARAMETER DirectoryServiceRequired | |
| Require the DC to support directory services. | |
| .PARAMETER DirectoryServicePreferred | |
| Prioritise DCs that support directory services over ones that do not. | |
| .PARAMETER GlobalCatalogRequired | |
| Require the DC to be a global catalog server for the forest of domains with this domain as the root. | |
| .PARAMETER PrimaryDCRequired | |
| Finds the DC that is the primary domain controller for the domain. | |
| .PARAMETER NoCache | |
| Forces cached information to be ignored. | |
| .PARAMETER UseCache | |
| Always use the cached information even when the function would normally refresh the data. | |
| .PARAMETER IpRequired | |
| The DC must have an IP address. | |
| .PARAMETER KdcRequired | |
| The DC must be a kerberos key distribution center. | |
| .PARAMETER TimeservRequired | |
| Requires the DC be currently running the Windows Time Service. | |
| .PARAMETER WritableRequired | |
| The DC must be writable and not a read only copy. | |
| .PARAMETER GoodTimeservPreferred | |
| Finds a DC that is a reliable time server. | |
| .PARAMETER AvoidSelf | |
| When calling from a domain controller, specified that the returned DC should not be the current host. | |
| .PARAMETER OnlyLdapNeeded | |
| Find a host that is an LDAP server and not necessarily a DC. | |
| .PARAMETER IsFlatName | |
| The -DomainName value is a flag name, e.g. DOMAIN. This cannot be combined with IsDnsName. | |
| .PARAMETER IsDnsName | |
| The -DomainName value is a DNS name, e.g. domain.com. This cannot be combined with IsFlagName. | |
| .PARAMETER TryNextClosestSite | |
| Attempt to find a DC in the same site but if nothing is found try the next closest site. | |
| .PARAMETER WebServiceRequired | |
| Requires the DC to be running the Active Directory web service. | |
| .PARAMETER Server2008OrLater | |
| DC must be running Windows Server 2008 or later. | |
| .PARAMETER Server2012OrLater | |
| DC must be running Windows Server 2012 or later. | |
| .PARAMETER Server2012R2OrLater | |
| DC must be running Windows Server 2012 R2 or later. | |
| .PARAMETER Server2016OrLater | |
| DC must be running Windows Server 2016 or later. | |
| .PARAMETER ReturnDnsName | |
| Returns the DNS names for Name and DomainName. This cannot be combined with ReturnFlatName. | |
| .PARAMETER ReturnFlatName | |
| Returns the flag name for Name and DOmain Name. This cannot be combined with ReturnDnsName. | |
| .NOTES | |
| This function uses the DsGetDcName API to find the information. | |
| #> | |
| [CmdletBinding()] | |
| param ( | |
| [Alias("Name")] | |
| [String] | |
| $DomainName, | |
| [String] | |
| $ComputerName, | |
| [Nullable[Guid]] | |
| [AllowNull()] | |
| $DomainGuid = $null, | |
| [String] | |
| $SiteName, | |
| [Switch] | |
| $DirectoryServiceRequired, | |
| [Switch] | |
| $DirectoryServicePreferred, | |
| [Switch] | |
| $GlobalCatalogRequired, | |
| [Switch] | |
| $PrimaryDCRequired, | |
| [Switch] | |
| $NoCache, | |
| [Switch] | |
| $UseCache, | |
| [Switch] | |
| $IpRequired, | |
| [Switch] | |
| $KdcRequired, | |
| [Switch] | |
| $TimeservRequired, | |
| [Switch] | |
| $WritableRequired, | |
| [Switch] | |
| $GoodTimeservPreferred, | |
| [Switch] | |
| $AvoidSelf, | |
| [Switch] | |
| $OnlyLdapNeeded, | |
| [Switch] | |
| $IsFlatName, | |
| [Switch] | |
| $IsDnsName, | |
| [Switch] | |
| $TryNextClosestSite, | |
| [Switch] | |
| $WebServiceRequired, | |
| [Switch] | |
| $Server2008OrLater, | |
| [Switch] | |
| $Server2012OrLater, | |
| [Switch] | |
| $Server2012R2OrLater, | |
| [Switch] | |
| $Server2016OrLater, | |
| [Switch] | |
| $ReturnDnsName, | |
| [Switch] | |
| $ReturnFlatName | |
| ) | |
| Add-Type -TypeDefinition @' | |
| using System; | |
| using System.ComponentModel; | |
| using System.Runtime.InteropServices; | |
| namespace NetApi | |
| { | |
| public class NativeHelpers | |
| { | |
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
| public struct DOMAIN_CONTROLLER_INFOW | |
| { | |
| public string DomainControllerName; | |
| public string DomainControllerAddress; | |
| public DCAddressType DomainControllerAddressType; | |
| public Guid DomainGuid; | |
| public string DomainName; | |
| public string DnsForestName; | |
| public DCFlags Flags; | |
| public string DcSiteName; | |
| public string ClientSiteName; | |
| } | |
| } | |
| public class NativeMethods | |
| { | |
| [DllImport("NetApi32.dll", CharSet = CharSet.Unicode)] | |
| private static extern Int32 DsGetDcNameW( | |
| string ComputerName, | |
| string DomainName, | |
| IntPtr DomainGuid, | |
| string SiteName, | |
| GetDcFlags Flags, | |
| out IntPtr DomainControllerInfo); | |
| [DllImport("NetApi32.dll")] | |
| private static extern Int32 NetApiBufferFree( | |
| IntPtr Buffer); | |
| public static DCInfo DsGetDcName(string domainName, string computerName = null, string siteName = null, | |
| GetDcFlags flags = GetDcFlags.None, Guid? domainGuid = null) | |
| { | |
| IntPtr rawInfo; | |
| IntPtr domainGuidPtr = IntPtr.Zero; | |
| if (string.IsNullOrWhiteSpace(domainName)) | |
| { | |
| domainName = null; | |
| } | |
| if (string.IsNullOrWhiteSpace(computerName)) | |
| { | |
| computerName = null; | |
| } | |
| if (string.IsNullOrWhiteSpace(siteName)) | |
| { | |
| siteName = null; | |
| } | |
| try | |
| { | |
| if (domainGuid != null) | |
| { | |
| byte[] guidBytes = ((Guid)domainGuid).ToByteArray(); | |
| domainGuidPtr = Marshal.AllocHGlobal(guidBytes.Length); | |
| Marshal.Copy(guidBytes, 0, domainGuidPtr, guidBytes.Length); | |
| } | |
| Int32 res = DsGetDcNameW(computerName, domainName, domainGuidPtr, siteName, flags, out rawInfo); | |
| if (res != 0) | |
| throw new Win32Exception(res); | |
| } | |
| finally | |
| { | |
| if (domainGuidPtr != IntPtr.Zero) | |
| Marshal.FreeHGlobal(domainGuidPtr); | |
| } | |
| try | |
| { | |
| var info = (NativeHelpers.DOMAIN_CONTROLLER_INFOW)Marshal.PtrToStructure(rawInfo, | |
| typeof(NativeHelpers.DOMAIN_CONTROLLER_INFOW)); | |
| return new DCInfo() | |
| { | |
| Name = info.DomainControllerName, | |
| Address = info.DomainControllerAddress, | |
| AddressType = info.DomainControllerAddressType, | |
| DomainGuid = info.DomainGuid, | |
| DomainName = info.DomainName, | |
| DnsForestName = info.DnsForestName, | |
| Flags = info.Flags, | |
| DcSiteName = info.DcSiteName, | |
| ClientSiteName = info.ClientSiteName, | |
| }; | |
| } | |
| finally | |
| { | |
| NetApiBufferFree(rawInfo); | |
| } | |
| } | |
| } | |
| public class DCInfo | |
| { | |
| public string Name { get; internal set; } | |
| public string Address { get; internal set; } | |
| public DCAddressType AddressType { get; internal set; } | |
| public Guid DomainGuid { get; internal set; } | |
| public string DomainName { get; internal set; } | |
| public string DnsForestName { get; internal set; } | |
| public DCFlags Flags { get; internal set; } | |
| public string DcSiteName { get; internal set; } | |
| public string ClientSiteName { get; internal set; } | |
| } | |
| public enum DCAddressType : uint | |
| { | |
| INetAddress = 1, | |
| NetbiosAddress = 2, | |
| } | |
| [Flags] | |
| public enum DCFlags : uint | |
| { | |
| Pdc = 0x00000001, | |
| Gc = 0x00000004, | |
| Ldap = 0x00000008, | |
| Ds = 0x00000010, | |
| Kdc = 0x00000020, | |
| Timeserv = 0x00000040, | |
| Closest = 0x00000080, | |
| Writable = 0x00000100, | |
| GoodTimeserv = 0x00000200, | |
| Ndnc = 0x00000400, | |
| SelectSecretDomain6 = 0x00000800, | |
| FullSecretDomain6 = 0x00001000, | |
| Ws = 0x00002000, | |
| Ds8 = 0x00004000, | |
| Ds9 = 0x00008000, | |
| Ds10 = 0x00010000, | |
| DsKeyList = 0x00020000, | |
| Ping = 0x000FFFFF, | |
| DnsController = 0x20000000, | |
| DnsDomain = 0x40000000, | |
| DnsForest = 0x80000000, | |
| } | |
| [Flags] | |
| public enum GetDcFlags : uint | |
| { | |
| None = 0x00000000, | |
| ForceRediscovery = 0x00000001, | |
| DirectoryServiceRequired = 0x00000010, | |
| DirectoryServicePreferred = 0x00000020, | |
| GcServerRequired = 0x00000040, | |
| PdcRequired = 0x00000080, | |
| BackgroundOnly = 0x00000100, | |
| IpRequired = 0x00000200, | |
| KdcRequired = 0x00000400, | |
| TimeservRequired = 0x00000800, | |
| WritableRequired = 0x00001000, | |
| GoodTimeservPreferred = 0x00002000, | |
| AvoidSelf = 0x00004000, | |
| OnlyLdapNeeded = 0x00008000, | |
| IsFlatName = 0x00010000, | |
| IsDnsName = 0x00020000, | |
| TryNextClosestSite = 0x00040000, | |
| DirectoryService6Required = 0x00080000, | |
| WebServiceRequired = 0x00100000, | |
| DirectoryService8Required = 0x00200000, | |
| DirectoryService9Required = 0x00400000, | |
| DirectoryService10Required = 0x00800000, | |
| ReturnDnsName = 0x40000000, | |
| ReturnFlatName = 0x80000000, | |
| } | |
| } | |
| '@ | |
| [NetApi.GetDcFlags]$flags = [NetApi.GetDcFlags]::None | |
| if ($DirectoryServiceRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryServiceRequired | |
| } | |
| if ($DirectoryServicePreferred) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryServicePreferred | |
| } | |
| if ($GlobalCatalogRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::GcServerRequired | |
| } | |
| if ($PrimaryDCRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::PdcRequired | |
| } | |
| if ($NoCache) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::ForceRediscovery | |
| } | |
| if ($UseCache) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::BackgroundOnly | |
| } | |
| if ($IpRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::IpRequired | |
| } | |
| if ($KdcRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::KdcRequired | |
| } | |
| if ($TimeservRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::TimeservRequired | |
| } | |
| if ($WritableRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::WritableRequired | |
| } | |
| if ($GoodTimeservPreferred) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::GoodTimeservPreferred | |
| } | |
| if ($AvoidSelf) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::AvoidSelf | |
| } | |
| if ($OnlyLdapNeeded) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::OnlyLdapNeeded | |
| } | |
| if ($IsFlatName) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::IsFlatName | |
| } | |
| if ($IsDnsName) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::IsDnsName | |
| } | |
| if ($TryNextClosestSite) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::TryNextClosestSite | |
| } | |
| if ($WebServiceRequired) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::WebServiceRequired | |
| } | |
| if ($Server2008OrLater) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryService6Required | |
| } | |
| if ($Server2012OrLater) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryService8Required | |
| } | |
| if ($Server2012R2OrLater) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryService9Required | |
| } | |
| if ($Server2016OrLater) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::DirectoryService10Required | |
| } | |
| if ($ReturnDnsName) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::ReturnDnsName | |
| } | |
| if ($ReturnFlatName) { | |
| $flags = [UInt32]$flags -bor [UInt32][NetApi.GetDcFlags]::ReturnFlatName | |
| } | |
| if ($IsDnsName -and $IsFlatName) { | |
| Write-Error -Message "Cannot specify IsDnsName and IsFlatName together" -Category InvalidArgument | |
| return | |
| } | |
| if ($ReturnDnsName -and $ReturnFlatName) { | |
| Write-Error -Message "Cannot specify ReturnDnsName and ReturnFlatName together" -Category InvalidArgument | |
| return | |
| } | |
| [NetApi.NativeMethods]::DsGetDcName( | |
| $DomainName, | |
| $ComputerName, | |
| $SiteName, | |
| $flags, | |
| $DomainGuid) | |
| } |
@jborean93 According to the docs of DsGetDcName DomainName can be NULL, I have modified locally and it works. Can you adapt as well? Then I guess the default realm of the host will be used.
Thanks @michael-o for letting me know, I've updated the code to make -DomainName a non-mandatory parameter.
Thanks @michael-o for letting me know, I've updated the code to make
-DomainNamea non-mandatory parameter.
Thank you, interesting. I have patched locally identially to ComputerName and I wonder why you have added more logic to it:
diff --git "a/.\\Get-DomainController.ps1.bak" "b/.\\Get-DomainController.ps1"
index 5830486..fdd9610 100644
--- "a/.\\Get-DomainController.ps1.bak"
+++ "b/.\\Get-DomainController.ps1"
@@ -218,11 +218,19 @@ namespace NetApi
private static extern Int32 NetApiBufferFree(
IntPtr Buffer);
- public static DCInfo DsGetDcName(string domainName = null, string computerName = null, string siteName = null,
+ public static DCInfo DsGetDcName(string domainName, string computerName = null, string siteName = null,
GetDcFlags flags = GetDcFlags.None, Guid? domainGuid = null)
{
IntPtr rawInfo;
IntPtr domainGuidPtr = IntPtr.Zero;
+ if (string.IsNullOrWhiteSpace(domainName))
+ {
+ domainName = null;
+ }
+ if (string.IsNullOrWhiteSpace(computerName))
+ {
+ computerName = null;
+ }
try
{
if (domainGuid != null)
@@ -343,7 +351,6 @@ namespace NetApi
}
'@
- $domainNameValue = if ($DomainName) { $DomainName } else { [NullString]::Value }
$computerNameValue = if ($ComputerName) { $ComputerName } else { [NullString]::Value }
$siteNameValue = if ($SiteName) { $SiteName } else { [NullString]::Value }
[NetApi.GetDcFlags]$flags = [NetApi.GetDcFlags]::None
@@ -451,7 +458,7 @@ namespace NetApi
}
[NetApi.NativeMethods]::DsGetDcName(
- $domainNameValue,
+ $DomainName,
$computerNameValue,
$siteNameValue,
$flags,.bak is my version.
I think it is easier to just do the empty string -> null conversion inside the .NET method than having people try and understand the purpose of [NullString]::Value. The only time I use the latter is if I need to differentiate between null and an empty string.
I think it is easier to just do the empty string -> null conversion inside the .NET method than having people try and understand the purpose of
[NullString]::Value. The only time I use the latter is if I need to differentiate between null and an empty string.
I see, but the shouldn't this be consistent for computerName and siteName as well? Now it is not.
Looks like I did computerName but missed siteName.
This is gold, thank you very much!