Skip to content

Instantly share code, notes, and snippets.

@moddingg33k
Created August 7, 2020 09:06
Show Gist options
  • Save moddingg33k/9df1e8914151d8e6f18f156667825dec to your computer and use it in GitHub Desktop.
Save moddingg33k/9df1e8914151d8e6f18f156667825dec to your computer and use it in GitHub Desktop.
function Send-Ping()
{
[CmdletBinding(DefaultParametersetName="p1")]
Param
(
[Parameter(ParameterSetName="p1", Mandatory = $true, Position = 0, ValueFromPipeline = $true,
HelpMessage = "One or more remote hosts to ping.")]
[String[]] $RemoteHost,
[Parameter(ParameterSetName="p2", Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true,
HelpMessage = "One or more remote hosts to ping.")]
[Object[]] $DNSHostName
)
BEGIN
{
$PingSender = New-Object System.Net.NetworkInformation.Ping
$PingOptions = New-Object System.Net.NetworkInformation.PingOptions
$PingDataBuffer = [System.Text.Encoding]::ASCII.GetBytes("abcdefghijklmnopqrstuvwxyzABCDEF") # 32 bytes
$PingTimeout = 1000 # Millisekunden
$Output = New-Object System.Collections.ArrayList
}
PROCESS
{
switch ($PsCmdlet.ParameterSetName)
{
"p2" { $RemoteHost = $DNSHostName; break}
}
foreach ($CurrentHost in $RemoteHost)
{
try
{
$Status = $PingSender.Send($CurrentHost, $PingTimeout, $PingDataBuffer, $PingOptions) |
Add-Member -PassThru -MemberType NoteProperty -Name "RemoteHost" -Value $CurrentHost |
Select-Object RemoteHost, Address, Status, RoundtripTime
}
catch
{
$Status = [PSCustomObject]::New | Select-Object RemoteHost, Address, Status, RoundtripTime
$Status.RemoteHost = $CurrentHost
$Status.Address = ""
$Status.Status = [System.Net.NetworkInformation.IPStatus]::BadDestination
$Status.RoundtripTime = ""
}
$null = $Output.Add($Status)
}
}
END { return $Output }
}
function Test-LDAPPing()
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage="DC der via LDAP gepingt werden soll.")]
[String[]] $RemoteHost
)
BEGIN { $Output = New-Object System.Collections.ArrayList }
PROCESS
{
foreach ($CurrentHost in $RemoteHost)
{
# ADSI query für die RootDSE
$Root = [ADSI]"LDAP://$CurrentHost/RootDSE"
# Query ausführen und Ergebnis auswerten
$RootHost = @($Root.dnsHostName)[0]
$AliveStatus = (-not [String]::IsNullOrEmpty($RootHost))
# Objekt mit diversen Attributen zur Übermittlung des Ergebnisses des Aufrufes erstellen
$Status = [PSCustomObject]::New | Select-Object RemoteHost, DNSHostName, Success
$Status.RemoteHost = $CurrentHost
$Status.DNSHostName = $RootHost
$Status.Success = $AliveStatus
$null = $Output.Add($Status)
}
}
END { return $Output }
}
function Test-ADWSPing()
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage="DC der via ADWS gepingt werden soll.")]
[String[]] $RemoteHost
)
BEGIN { $Output = New-Object System.Collections.ArrayList }
PROCESS
{
foreach ($CurrentHost in $RemoteHost)
{
# Mittels des Cmdlet Get-ADRootDSE prüfen ob auf dem RemoteHost ADWS läuft
# Erzeugt einen Fehler wenn die WebServices nicht ausgeführt werden
$Root = [PSCustomObject]::New
try
{
$Root = Get-ADRootDSE -Server $CurrentHost -ErrorAction SilentlyContinue
$AliveStatus = $true
}
catch
{
$AliveStatus = $false
}
# Objekt mit diversen Attributen zur Übermittlung des Ergebnisses des Ausfrufes erstellen
$Status = New-Object PSObject | Select-Object RemoteHost, DNSHostName, Success
$Status.RemoteHost = $CurrentHost
$Status.DNSHostName = $Root.dnsHostName
$Status.Success = $AliveStatus
$null = $Output.Add($Status)
}
}
END { return $Output }
}
function Get-DCStatus()
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage="A DC to determine the status of via LDAP.")]
[String[]] $RemoteHost,
[Parameter(HelpMessage="An ADWS-enabled DC to get info about the target DC from.")]
[String] $Server,
[Parameter(HelpMessage="If true, a failed LDAPping will assume that all other tests will fail. Defaults to false.")]
[Switch] $FastFail,
[Parameter(HelpMessage="If true, ADWS ping test will not be performed.")]
[Switch] $SkipADWS
)
BEGIN { $Output = New-Object System.Collections.ArrayList }
PROCESS
{
foreach ($CurrentHost in $RemoteHost)
{
$Status = [PSCustomObject]::New | Select-Object Name, Site, Domain, Forest, IsReadOnly, PingStatus, PingTime, LDAPAlive, ADWSAlive
$Status.Name = $CurrentHost
$Status.ADWSAlive = "Skipped"
# Ping des $CurrentHost auswerten
$Ping = Send-Ping -RemoteHost $CurrentHost
$Status.PingStatus = $Ping.Status
$Status.PingTime = $Ping.RoundTripTime
# Prüfen ob LDAP auf $currentHost ordnungsgemäß funktioniert
$LDAPPing = Test-LDAPPing -RemoteHost $CurrentHost
$Status.LDAPAlive = $LDAPPing.Success
# Wenn LDAPPing fehlschlägt, wird ADWS mit hoher Wahrscheinlichkeit ebenfalls nicht funktionieren.
# ADWSPing in diesem Fall überspringen wenn der entsprechende Schalter gesetzt wurde
if ($FastFail -and $LDAPPing.Success -ne $true)
{
$null = $Output.Add($Status)
return
}
else
{
try
{
# Wenn nicht angegeben wurde von welchem DC die Informationen eingeholt werden sollen
# wird der jeweilige Ziel DC befragt
if ([String]::IsNullOrEmpty($Server)) {
$CurrentServer = $CurrentHost
}
else {
$CurrentServer = $Server
}
$DC = Get-ADDomainController -Identity $CurrentHost -Server $CurrentServer
$Status.Name = $DC.Name
$Status.Site = $DC.Site
$Status.Domain = $DC.Domain
$Status.Forest = $DC.Forest
$Status.IsReadOnly = $DC.IsReadOnly
}
catch
{
# If we didn't get an object back, we'll still add some info
$Status.Name = $CurrentHost
}
}
# Prüfen ob ADWS auf dem DC ordnungsgemäß funktioniert,
# außer es wurde per Schalter veranlasst diese Prüfung zu überspringen
if (-not $SkipADWS)
{
$ADWSPing = Test-ADWSPing -RemoteHost $CurrentHost
$Status.ADWSAlive = $ADWSPing.Success
}
$null = $Output.Add($Status)
}
}
END { return $Output }
}
function Get-DomainStatus()
{
[CmdletBinding()]
Param (
[Parameter(Position = 0, HelpMessage="The DNS root domain name of a domain to get the status of.")]
$DomainName,
[Parameter(HelpMessage="If true, a failed ping will assume that all other tests will fail. Defaults to false.")]
[Switch] $FastFail,
[Parameter(HelpMessage="If true, ADWS ping test will not be performed.")]
[Switch] $SkipADWS,
[Parameter(HelpMessage="If true, skips readOnly DC")]
[Switch] $WritableDCOnly
)
BEGIN { $Output = New-Object System.Collections.ArrayList }
PROCESS
{
# Let's populate $Domain using Get-ADDomain. We'll use a default if no $DomainName
if (-not [String]::IsNullOrEmpty($DomainName))
{
$Domain = Get-ADDomain $DomainName
$ADWSServer = (Get-ADDomainController -Discover -Service ADWS -DomainName $DomainName).HostName
}
else
{
$Domain = Get-ADDomain
$ADWSServer = (Get-ADDomainController -Discover -Service ADWS).HostName
}
# This is the ADWS Server we're going to talk to. Make sure it's not an array.
$ADWSServer = @($ADWSServer)[0]
# Let's enumerate all the DCs in the Domain
$FullDCs = @($Domain.ReplicaDirectoryServers)
$RODCs = @($Domain.ReadOnlyReplicaDirectoryServers)
# We're going to need some counts as well, to track our progress
$NumFullDCs = $FullDCs.Length
$NumRODCs = $RODCs.Length
$NumDCs = $NumFullDCs
if (-not $WritableDCOnly) { $NumDCs += $NumRODCs }
# Now we simply call Get-DCStatus for each of the DCs and output it
foreach ($FullDC in $FullDCs)
{
$Status = Get-DCStatus -RemoteHost $FullDC -Server $ADWSServer -FastFail:$FastFail -SkipADWS:$SkipADWS
$null = $Output.Add($Status)
}
if (-not $WritableDCOnly)
{
foreach ($RODC in $RODCs)
{
$Status = Get-DCStatus -RemoteHost $RODC -Server $ADWSServer -FastFail:$FastFail -SkipADWS:$SkipADWS
$null = $Output.Add($Status)
}
}
}
END { return $Output }
}
<#------------------------------------------------------------------------------------------------------------------#>
# This cmdlet will send all three ping requests to all DCs in a forest
# It will also retrieve the ADDomainController objects
function Get-ForestStatus() {
[CmdletBinding()]
Param ([Parameter(Position=0, ValueFromPipeline = $true, HelpMessage = "The DNS root domain name of a forest to get the status of.")]
$ForestName,
[Parameter(HelpMessage="If true, a failed ping will assume that all other tests will fail. Defaults to false.")]
[Switch] $FastFail,
[Parameter(HelpMessage="If true, ADWS ping test will not be performed.")]
[Switch] $SkipADWS)
PROCESS {
# This is remarkably like Get-DomainStatus. First we get an ADForest object
if ($ForestName) {
$Forest = Get-ADForest $ForestName
}
else {
$Forest = Get-ADForest
}
$ForestName = $Forest.Name
# Then we enumerate all of it's domains
$Domains = @($Forest.Domains)
$NumDomains = $Domains.Length
$Count = 0
# Then we call Get-DomainStatus for each of them
foreach ($Domain in $Domains) {
Write-Progress -Activity "Getting the status of forest $ForestName..." -PercentComplete (100 * $Count / $NumDomains) -Status "Testing Domain $Domain" -id 2
Get-DomainStatus $Domain -FastFail:$FastFail -SkipADWS:$SkipADWS
$Count++
}
# Done!
Write-Progress -Activity "Getting the status of forest $forestname..." -Completed -Status "Done!" -id 2
}
}
# This cmdlet tests replication for all DCs in a given domain
function Test-Replication
{
Param
(
[Parameter(Position = 0)]
[string] $DomainName = (Get-ADDomain).dnsRoot,
[string] $SourceDC = $env:COMPUTERNAME
)
$Output = New-Object System.Collections.ArrayList
# Let's get all the DCs in the domain and create a hash table to store status in
$AllDCs = Get-ADDomainController -Filter * -Server $DomainName
$ReplicatedStatus = @{}
# This is the DC where we'll make the change
$FirstDC = $AllDCs | Where-Object { $_.Name -eq $SourceDC.Split('.')[0] }
if ([Object]::Equals($FirstDC, $null)) {
$FirstDC = $AllDCs | Select-Object -First 1
}
$ChangeServer = $FirstDC.Name
$UsersContainer = "CN=Users," + $FirstDC.defaultPartition
# First we get the current time to use as a baseline
$ModificationTime = [DateTime]::Now
$Ticks = $ModificationTime.Ticks
# Then we'll insert that time into the Users container's wwwHomePage attribute
Set-ADObject $UsersContainer -Replace @{"wwwHomePage"="$Ticks"} -Server $ChangeServer
# It took some time to perform that operation, let's make sure we don't count it
$ChangeTime = [DateTime]::Now - $ModificationTime
# Now we'll check the object on the other DCs in $DomainName
do
{
# Let's make sure we only check once per second so we don't overload the DC
if ($Replicated -eq $false)
{
Start-Sleep -Seconds 1
}
$Replicated = $true
# Enumerate through all of the DCs in the domain
foreach ($DC in $AllDCs)
{
$DCPingable = $true
$CurrentDC = $DC.Name
# No point in checking the server we changed it on or if it's already replicated for this DC
if ($CurrentDC -eq $ChangeServer -or $ReplicatedStatus.containsKey($CurrentDC))
{
continue
}
# Let's make sure the DC is alive first...
elseif ((Test-Netconnection $CurrentDC).PingSucceeded -ne $True)
{
$DCPingable = $false
}
# Assuming it is, we'll check if the attribute has changed yet on the current DC
if ($DCPingable) {
$Object = Get-ADObject $UsersContainer -Properties wwwHomePage -Server $CurrentDC
}
# If it has not changed yet, set replicated to false so that we'll check again
if ($DCPingable -and $Object.wwwHomePage -ne $Ticks) {
$Replicated = $false
}
# The object has replicated or the DC isn't pingable, so let's output the results
else
{
# We'll store the replication status and create a new object with the results
$ReplicatedStatus.Add($CurrentDC,$true)
$Status = [PSCustomObject]::New | Select-Object Domain, Site, Source, Target, ReplicationInterval
$Status.Domain = $DomainName
$Status.Site = $DC.Site
$Status.Source = $ChangeServer
$Status.Target = $CurrentDC
# If the object was not pingable, we'll mark it differently
if($DCPingable) {
$Status.ReplicationInterval = [DateTime]::Now - $ModificationTime - $ChangeTime
}
else {
$Status.ReplicationInterval = "HostNotReachable"
}
# Return the $Status object
$null = $Output.Add($Status)
}
}
}
Until ($Replicated)
# Let's cleanup our mess in the Users container...
Set-ADObject $UsersContainer -Clear "wwwHomePage"
return $Output
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment