Created
August 7, 2020 09:06
-
-
Save moddingg33k/9df1e8914151d8e6f18f156667825dec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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