AD_NTDS_DNS.ps1


<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 04d44f5b-2200-4357-a4e5-44f3849bc4ea
 
.AUTHOR Ashley McGlone (asmcglon)
 
.COMPANYNAME
Microsoft Corporation
 
.COPYRIGHT
(c) Microsoft Corporation. All rights reserved.
 
.TAGS
SDP, AD, NTDS, DNS, Inspection
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
ActiveDirectory
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
Requires Windows Server 2008 R2 DC or above
Requires permissions to access each target server, especially across domains.
 
FOR SDP ALERTS:
Find all of the PowerShell "write-warning" cmdlets.
These show you where the alerts should go for SDP rules.
 
To view the results files either open the XML in IE, or type:
PS C:\> Import-CliXml AD_NTDS_xxx.xml | Out-GridView
 
#>


<#
 
.DESCRIPTION
SDP AD NTDS DNS Inspection
 
#>
 
Param()


#requires -version 2.0
#requires -module ActiveDirectory

###############################################################################
# SDP AD NTDS DNS Inspection
# Ashley McGlone (asmcglon)
# Microsoft Premier Field Engineer
# July 2014
#
# Requires Windows Server 2008 R2 DC or above
# Requires permissions to access each target server, especially across domains.
#
### FOR SDP ALERTS
### Find all of the PowerShell "write-warning" cmdlets.
### These show you where the alerts should go for SDP rules.
#
### To view the results files either open the XML in IE, or type:
### PS C:\> Import-CliXml AD_NTDS_xxx.xml | Out-GridView
#
###############################################################################


###############################################################################
# Helper Functions
###############################################################################

Function Get-ParentDN {
param($DistinguishedName)
    $dn = $DistinguishedName -split ','
    $dn[1..($dn.count -1)] -join ','
}

###############################################################################
# Get All NTDS Settings and Connections
###############################################################################

Function Get-ADNTDSSetting {
param ($ConfigNC)
    Get-ADObject -Filter 'objectClass -eq "nTDSDSA"' -Searchbase $ConfigNC `
      -Properties CanonicalName, distinguishedName, objectGUID |
     Select-Object CanonicalName, distinguishedName, objectGUID, `
      @{name='DNSAlias';expression={"$($_.objectGUID).$MSDCS"}}, `
      @{name='Server';expression={$_.distinguishedName.Split([string[]]',CN=',[StringSplitOptions]::None)[1]}}
}

Function Get-ADNTDSConnection {
param ($ConfigNC)
    Get-ADObject -Filter "objectClass -eq 'nTDSConnection'" -Searchbase $ConfigNC `
      -Properties fromServer, objectGUID, name, distinguishedName, options, whenCreated, whenChanged |
     Select-Object fromServer, objectGUID, name, distinguishedName, options, `
      @{name='IsGenerated';expression={If (($_.options -band 1) -eq 1) {$true} Else {$false}}}, `
      @{name='To';expression={$_.distinguishedName.Split([string[]]',CN=',[StringSplitOptions]::None)[2]}}, `
      @{name='From';expression={$_.FromServer.Split([string[]]',CN=',[StringSplitOptions]::None)[1]}}, `
      whenCreated, whenChanged
}

###############################################################################
# Data collection for NTDS Settings objects
###############################################################################

Function Trace-ADNTDSSetting {
param (
    $NTDSSetting,
    $GlobalCatalog
)

    ForEach ($DSA in $NTDSSetting) {

        # Get parent server container object
        $ParentDN = Get-ParentDN $DSA.distinguishedName
        Add-Member -InputObject $DSA -MemberType NoteProperty -Name ParentDN -Value $ParentDN

        # Get the DC distinguishedName from the serverReference attribute
        $ServerDN = Get-ADObject -Identity $ParentDN -Properties serverReference | Select-Object -ExpandProperty ServerReference
        Add-Member -InputObject $DSA -MemberType NoteProperty -Name ServerDN -Value $ServerDN

        # Get the true DNSHostName from the computer object
        Try {
            $DCComputerObject = Get-ADObject -Identity $ServerDN -Properties DNSHostName, whenCreated, whenChanged, servicePrincipalName -Server "$($GlobalCatalog):3268"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerDNSHostName -Value $DCComputerObject.DNSHostName
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerWhenCreated -Value $DCComputerObject.whenCreated
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerWhenChanged -Value $DCComputerObject.whenChanged
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerSPN -Value $DCComputerObject.servicePrincipalName
        } Catch {
            # Alert for either object not found or GC inaccessible
            Write-Warning "Error querying global catalog for computer object: $ServerDN`n $_`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerDNSHostName -Value $null
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerWhenCreated -Value $null
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerWhenChanged -Value $null
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerSPN -Value $null
        }

        # Check for the replication SPN
        $e351SPNCount = ($DSA.ComputerSPN | Where-Object {$_ -like "E3514235-4B06-11D1-AB04-00C04FC2DCD2*"} | Measure-Object).Count
        If ($e351SPNCount -gt 0) {
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerSPNe351 -Value $true
        } Else {
            # Alert for missing e351 SPN
            Write-Warning "e351 SPN not found for DC: $($DSA.Server)`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name ComputerSPNe351 -Value $false
        }

        Try {
            # Get the actual IP information directly from the server via WMI
            $WMIIPConfig = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=true' -ComputerName $DSA.ComputerDNSHostName -Property IPAddress -ErrorAction Stop
            # Get the IP address(es). At a minimum there should be one NIC with both IPv4 and IPv6 addresses.
            # Loop through in case the server is multi-homed and there is more than one NIC.
            $IPAddresses = @()
            ForEach ($Address in $WMIIPConfig) {
                $IPAddresses += @($WMIIPConfig.IPAddress)
            }
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIIPs -Value $IPAddresses
        } Catch {
            # Alert for WMI connection issue
            Write-Warning "Unable to contact WMI on DC: $($DSA.Server)`n $_`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIIPs -Value $null
        }

        Try {
            # Get the name information directly from the server via WMI
            $WMIName = Get-WmiObject Win32_ComputerSystem -ComputerName $DSA.ComputerDNSHostName -Property Name, Domain -ErrorAction Stop
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIComputerName -Value $WMIName.Name
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIDomainName -Value $WMIName.Domain
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIDNSHostName -Value "$($WMIName.Name).$($WMIName.Domain)"
        } Catch {
            # Alert for WMI connection issue
            Write-Warning "Unable to contact WMI on DC: $($DSA.Server)`n $_`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIComputerName -Value $null
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIDomainName -Value $null
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name WMIDNSHostName -Value $null
        }
        
        # Get the DNS IP that comes back from the CNAME lookup.
        # This shortcuts the hostname glue record in the middle.
        # If this works, then both the CNAME and A/AAAA are good.
        $NetDNSLookup = $null
        Try {
            $NetDNSLookup = [System.Net.DNS]::GetHostEntry($DSA.DNSAlias)
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name NetDNSLookup -Value @($NetDNSLookup.AddressList)
        } Catch {
            # Alert for DNS resolution error
            Write-Warning "Error resolving DC replication CNAME: $($DSA.Server) $($DSA.DNSAlias)`n $_`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name NetDNSLookup -Value $null
        }
        
        # Do the IP(s) returned from .NET DNS GetHostEntry match the ones from WMI on the DC?
        # Note that IPv6 addresses may come back with interface notation at the end and break the match.
        # Bind via IP will use NTLM.
        ForEach ($NetIP in $DSA.NetDNSLookup) {
            $BindResult = $null
            $NetIPVerification = $null
            Try {
                $BindResult = ([ADSI]"LDAP://$NetIP/RootDSE").serverName.ToString()
            } Catch {
                # Alert for binding error
                Write-Warning "Error binding to DC via IP (NTLM): $($DSA.Server) $NetIP`n $_`n"
                $BindResult = '*** DC UNREACHABLE ***'
            }
            $NetIPVerification = New-Object -TypeName PSObject -Property @{
                NetIP = $NetIP.ToString()
                WMIIPContains = $( If ($DSA.WMIIPs -contains $NetIP.ToString()) {$true} Else {$false} )
                BindIPNTLM = $BindResult
                BindIPNTLMMatchesNTDSSetting = $($BindResult -eq $DSA.ParentDN)
            }

            # Alert for IP mismatch
            If ($DSA.WMIIPs -notcontains $NetIP.ToString()) {
                Write-Warning "DNS IP result not found in WMI IP result: $($NetIP.ToString())`n"
            }

            # Alert for connecting to a DC other than expected
            If ($NetIPVerification.BindIPNTLMMatchesNTDSSetting -eq $false) {
                Write-Warning "Name mismatch returned from IP NTLM bind.`n Expected: $($DSA.ParentDN)`n Returned: $BindResult`n"
            }

        } # End ForEach NetIP
        Add-Member -InputObject $DSA -MemberType NoteProperty -Name BindIPNTLM -Value $NetIPVerification

        # Bind via name using kerberos.
        $BindResult = $null
        Try {
            $BindResult = ([ADSI]"LDAP://$($DSA.ComputerDNSHostName)/RootDSE").serverName.ToString()
        }
        Catch {
            # Alert for binding error
            Write-Warning "Error binding to DC via FQDN (Kerberos): $($DSA.Server) $($DSA.ComputerDNSHostName)`n $_`n"
            $BindResult = '*** DC UNREACHABLE ***'
        }
        Add-Member -InputObject $DSA -MemberType NoteProperty -Name BindNameKerberos -Value $BindResult
        If ($BindResult -eq $DSA.ParentDN) {
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name BindNameKerberosMatchesNTDSSetting -Value $true
        } Else {
            # Alert for connecting to DC other than expected
            Write-Warning "Name mismatch returned from Kerberos bind.`n Expected: $($DSA.ParentDN)`n Returned: $BindResult`n"
            Add-Member -InputObject $DSA -MemberType NoteProperty -Name BindNameKerberosMatchesNTDSSetting -Value $false
        }

        # Return the updated object
        $DSA

    }

}

###############################################################################
# Main
###############################################################################

#. .\TS_nslookup.ps1
#. .\utils_CTS.ps1

Start-Transcript -Path .\AD_NTDS_Log.txt -Force

Import-Module ActiveDirectory
$Forest = Get-ADForest
#$Domain = Get-ADDomain
$RootDSE= Get-ADRootDSE
$GC     = Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty HostName

$ForestDNSRoot = $Forest.RootDomain
$MSDCS = "_msdcs.$ForestDNSRoot"
$ConfigNC = $RootDSE.ConfigurationNamingContext.ToString()
#$DomainDNSName = Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty Domain
$ComputerName = $ENV:COMPUTERNAME

# Get all replication configuration data
$AllnTDSDSAs = Get-ADNTDSSetting -ConfigNC $ConfigNC
$AllnTDSConnections = Get-ADNTDSConnection -ConfigNC $ConfigNC

# Filter the selections to this DC and its replicaton partners
$FilterednTDSConnections = @($AllnTDSConnections | Where-Object {$_.To -eq $ComputerName})
$ReplicationPartners = $FilterednTDSConnections | Select-Object -ExpandProperty From
$FilterednTDSDSAs = @($AllnTDSDSAs | Where-Object {$ReplicationPartners -contains $_.Server -or $_.Server -eq $ComputerName})

#### Uncomment this section to target ALL DCs in the forest
###$FilterednTDSDSAs = $AllnTDSDSAs
###$FilterednTDSConnections = $AllnTDSConnections

# Alert if no replication partners
If ($FilterednTDSConnections.Count -eq 0) {
    Write-Warning "No replication partners found for DC: $Computername`n"
}

# Alert if manual connection objects exist
If (@($FilterednTDSConnections | Where-Object {$_.IsGenerated -eq $false}).Count -gt 0) {
    Write-Warning "Manual connection objects found.`n"
}

# Collect NTDS Setting Data
$nTDSDSAs = Trace-ADNTDSSetting -NTDSSetting $FilterednTDSDSAs -GlobalCatalog $GC
$nTDSConnections = $FilterednTDSConnections

# Output data in scope
$nTDSDSAs | Export-Clixml -Path .\AD_NTDSSettings_DNS.xml -Force
$nTDSConnections | Export-Clixml -Path .\AD_NTDSConnections_DNS.xml -Force

# Since we already have it, output the larger dataset as well.
$AllnTDSDSAs | Export-Clixml -Path .\AD_NTDSSettings_DNS_ALL.xml -Force
$AllnTDSConnections | Export-Clixml -Path .\AD_NTDSConnections_DNS_ALL.xml -Force

###############################################################################
# End
###############################################################################

Stop-Transcript