Global Test Zone Delegation Health

Microsoft.Windows.DNSServer.2016.Task.HealthCheck.ZoneDelegation.Global (Task)

Tests Zone delegation settings for each discovered Zone. At least one Windows 2016 and 1709+ DNS Server is required for execution of the task.

Element properties:

TargetMicrosoft.Windows.DNSServer.2016.Group
AccessibilityInternal
CategoryCustom
EnabledTrue
RemotableFalse
Timeout300

Member Modules:

ID Module Type TypeId RunAs 
PA ProbeAction Microsoft.Windows.DNSServer.2016.HtmlReporterPowershellProbe.PA Microsoft.Windows.DNSServer.2016.ActionAccount

Source Code:

<Task ID="Microsoft.Windows.DNSServer.2016.Task.HealthCheck.ZoneDelegation.Global" Accessibility="Internal" Target="Microsoft.Windows.DNSServer.2016.Group" Enabled="true" Timeout="300" Remotable="true">
<Category>Custom</Category>
<ProbeAction ID="PA" TypeID="Microsoft.Windows.DNSServer.2016.HtmlReporterPowershellProbe.PA" RunAs="Microsoft.Windows.DNSServer.2016.ActionAccount">
<ScriptName>Microsoft.Windows.Server.10.0.HealthCheck.ZoneDelegation.Global.Task.Script.ps1</ScriptName>
<ScriptBody><Script>

param ([String] $OutputFolder)
$SCRIPT_NAME = "HealthCheckZoneDelegationServer10.0.Global"
$ErrorActionPreference = "Stop"

Function Save-TaskHtmlReport
{
param (
[string] $path,
[string] $reportName,
[string] $html
)

if(Test-Path -Path $path -PathType Container)
{
try
{
$dt = Get-Date
$fileName = $reportName + "_" + $dt.ToString("dd-MM-yyyy_HH-mm-ss.ff")+'.htm'
$filePath = Join-Path -Path $path -ChildPath $fileName

$file = New-Item -Path $filePath -ItemType "file" -ErrorAction:Stop
Add-Content -Path $filePath -Value $html -ErrorAction:Stop
Write-Host "Report path: " $filePath
}
catch
{
Write-Host "Report writing failed, .html report will not be written to output folder. You can find it below."
Write-Host
Write-Host $resultHtml
}
}
else
{
Write-Host "Output folder is invalid. Html report will not be written to output folder"
}
}

Function Run-SCOMParameterizedTask
{
param (
[string] $taskName,
[string] $targetClass,
[string] $Parameter1,
[string] $Parameter2,
[string] $Parameter3,
[string] $OutputFolder,
[string] $ReportName


)
try
{
$task = Get-SCOMTask -Name $taskName
$class = Get-SCOMClass -Name $targetClass
$targets = Get-SCOMClassInstance -Class $class

if($targets -ne $null)
{
if($targets.Count -eq 1)
{
$selectedTarget = $targets
}
else
{
$selectedTarget = $targets[0]
}


$overrides = New-Object -TypeName Hashtable
if($Parameter1 -ne $null -and $Parameter1 -ne "") { $overrides.Add("Parameter1",$Parameter1) }
if($Parameter2 -ne $null -and $Parameter2 -ne "") { $overrides.Add("Parameter2",$Parameter2) }
if($Parameter3 -ne $null -and $Parameter3 -ne "") { $overrides.Add("Parameter3",$Parameter3) }

# very dirty trick - remove in next version by passing timeout into script
if ($taskName -eq 'Microsoft.Windows.DNSServer.2016.Task.HealthCheck.ConfiguredRootHints')
{
$calcTimeout = $targets.Count * 1000
$overrides.Add("TimeoutSeconds",$calcTimeout.ToString())
}
else
{
$calcTimeout = 250
}

if($overrides.Count -eq 0)
{
$taskResultSnapshot = Start-SCOMTask -Instance $selectedTarget -Task $task
}
else
{
$taskResultSnapshot = Start-SCOMTask -Instance $selectedTarget -Task $task -Override $overrides
}



for ($i = 1; $i -le $calcTimeout; $i++)
{
$taskResult = Get-SCOMTaskResult -Id $taskResultSnapshot.Id
if ($taskResult.Status -eq [Microsoft.EnterpriseManagement.Runtime.TaskStatus]::Started `
-or $taskResult.Status -eq [Microsoft.EnterpriseManagement.Runtime.TaskStatus]::Scheduled)
{
#Write-Host "Remote task " $taskResult.Status
Start-Sleep -Seconds 1
}
else
{
break
}
}

if($taskResult.Status -eq [Microsoft.EnterpriseManagement.Runtime.TaskStatus]::Succeeded)
{


if($OutputFolder -eq $null -or $OutputFolder -eq "")
{
# here we do not remove linebreaks
$xml = [xml]($taskResult.Output)
if($xml.DataItem.StdOut.InnerText -ne $null)
{
$str = $xml.DataItem.StdOut.InnerText
$str = $str.Replace("&amp;amp;lt;","&lt;")
$str = $str.Replace("&amp;amp;gt;","&gt;")
$str = $str.Replace("&amp;lt;","&lt;")
$str = $str.Replace("&amp;gt;","&gt;")
$resultHtml = $str
}
else
{
Write-Host "There are no Task Output"
}


#Write-Host "OutputFolder parameter is not specified, task report will not be written."
Write-Host $resultHtml
}
else
{
$xml = [xml]($taskResult.Output)
if($xml.DataItem.StdOut.InnerText -ne $null)
{
$str = $xml.DataItem.StdOut.InnerText
$str = $str.Replace("`n","");
$str = $str.Replace("`r","");
$str = $str.Replace("&amp;amp;lt;","&lt;")
$str = $str.Replace("&amp;amp;gt;","&gt;")
$str = $str.Replace("&amp;lt;","&lt;")
$str = $str.Replace("&amp;gt;","&gt;")
$resultHtml = $str
}
else
{
Write-Host "There are no Task Output"
}

Write-Host "OutputFolder: " $OutputFolder
try
{
Save-TaskHtmlReport -path $OutputFolder -reportName $ReportName -html $resultHtml
}
catch
{
Write-Host "Report writing failed, .html report will not be written to output folder. You can find it below."
Write-Host
Write-Host $resultHtml
}

}
}
else
{
if($taskResult.Status -eq [Microsoft.EnterpriseManagement.Runtime.TaskStatus]::Started)
{
Write-Host "Remote task was timed out. Report will not be written to output folder."
}
else
{
$res = $taskResult.Status
Write-Host "Remote task failed with status " $res
}

}
}
else
{
Write-Host 'No objects to perform complex tasks. Computer with DNS Server role required.'
}

}
catch
{
write-Host "Error!"
write-Host $_.Exception.Message
}
}

Function Import-GlobalTaskCmdlets()
{

$SCOMPowerShellKey = "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2"
$SCOMModulePath = (Get-ItemProperty $SCOMPowerShellKey).InstallDirectory
if ($true -eq [string]::IsNullOrEmpty($SCOMModulePath))
{
$ErrorMessage = "Path to SCOM cmdlets not found in registry."
Write-Host $ErrorMessage
exit
}

$SCOMModulePath = Join-Path $SCOMModulePath "OperationsManager"

try
{

Import-module $SCOMModulePath

}
catch [System.IO.FileNotFoundException]
{
$ErrorMessage = "SCOM cmdlets do not exist."
Write-Host $ErrorMessage
exit
}
catch
{
$ErrorMessage = $_.Exception.Message
Write-Host "Cannot import SCOM cmdlets"
Write-Host $ErrorMessage
exit
}


}

Import-GlobalTaskCmdlets


$global:stringOutput = "Messages: "
$global:htmlReport = ""

try
{
Enum RetStatus
{
Success
Failure
RpcServerIsUnavailable
AccessIsDenied
ZoneDoesNotExist
OperationIsNotSupported
RecordDoesNotExist
NotApplicable
ResolveDnsNameServerNotFound
ResolveDnsNameResolutionFailed
ResolveDnsNameTimeoutPeriodExpired
}


Enum ValidationType
{
Domain
Zone
ZoneAging
ZoneDelegation
Forwarder
RootHints
}
}
catch
{

}

$script:resultView =@{
"List" = "List"
;"Table" = "Table"
}

Function LogComment
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$message,

[int]$level = $script:logLevel.Verbose
)

#$stringBuilder.AppendLine($message);
$global:stringOutput += "`r`n" + $message
}

Function ExecuteCmdLet
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$cmdLetName,
[HashTable]$params = @{}
)
$cmdString=$cmdLetName;
$displayString=$cmdLetName;
$script:cmdLetReturnedStatus = [RetStatus]::Success;
if ($null -ne $params) {
foreach($key in $params.keys) {
if ($script:switchParamVal -eq $params[$key]) {
$cmdString +=" -$key ";
$displayString +=" -$key ";
} else {
$cmdString += " -$key `$params[`"$key`"]";
$displayString += " -$key $($params[$key])";
}
}
}
$cmdString += " -ErrorAction Stop 2&gt; `$null";
$displayString += " -ErrorAction Stop 2&gt; `$null";
LogComment $displayString $script:logLevel.Host;
$retObj = $null;
try {
$retObj = Invoke-Expression $cmdString;
} catch [Exception] {
if (Get-Member -InputObject $_.Exception -Name "Errordata")
{
# Handling DNS server module specific exceptions.
if (5 -eq $_.Exception.Errordata.error_Code) {
LogComment $("Caught error: Access is denied, considering it as current login creds don't have server read access.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::AccessIsDenied;
} elseif (1722 -eq $_.Exception.Errordata.error_Code) {
LogComment $("Caught error: The RPC server is unavailable, considering it as server is down.") `
$script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::RpcServerIsUnavailable;
} elseif (9601 -eq $_.Exception.Errordata.error_Code) {
LogComment $("Caught error: DNS zone does not exist, considering it as given server isn't hosting input zone.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::ZoneDoesNotExist;
} elseif (9611 -eq $_.Exception.Errordata.error_Code) {
LogComment $("Caught error: Invalid DNS zone type, considering it as we can't perform current operation on input zone.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::OperationIsNotSupported;
} elseif (9714 -eq $_.Exception.Errordata.error_Code) {
LogComment $("Caught error: DNS name does not exist, considering it as input record doesn't exist.") `
$script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::RecordDoesNotExist;
} else {
LogComment $("Caught error while executing '" + $displayString + "' with errorcode: " + $_.Exception.Errordata.error_Code) $script:logLevel.Error;
$script:cmdLetReturnedStatus = $([String]$_.Exception.Errordata.error_Code + ":" + $_.Exception.Errordata.error_WindowsErrorMessage);
#throw;
}
} elseif (Get-Member -InputObject $_ -Name "FullyQualifiedErrorId")
{
# Handling Resolve-DnsName specific errors.
if ($_.FullyQualifiedErrorId.Contains("DNS_ERROR_RCODE_NAME_ERROR")) {
LogComment $("Caught error: ResolveDnsNameResolutionFailed in Resolve-DnsName.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::ResolveDnsNameResolutionFailed;
} elseif ($_.FullyQualifiedErrorId.Contains("System.Net.Sockets.SocketException")) {
LogComment $("Caught error: ResolveDnsNameServerNotFound in Resolve-DnsName.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::ResolveDnsNameServerNotFound;
} elseif ($_.FullyQualifiedErrorId.Contains("ERROR_TIMEOUT")) {
LogComment $("Caught error: ResolveDnsNameTimeoutPeriodExpired in Resolve-DnsName.") $script:logLevel.Warning;
$script:cmdLetReturnedStatus = [RetStatus]::ResolveDnsNameServerNotFound;
} else {
LogComment $("Caught error while executing '" + $displayString + "' `n" + $_.Exception) $script:logLevel.Error;
$script:cmdLetReturnedStatus = $([String]$_.FullyQualifiedErrorId + ":" + $_.Exception);
throw;
}
} else
{
LogComment $("Caught error while executing '" + $displayString + "' `n" + $_.Exception) $script:logLevel.Error;
$script:cmdLetReturnedStatus = $($_.Exception);
throw;
}
}
if ($null -eq $retObj) {
LogComment "CmdLet returned with NULL..." $script:logLevel.Host;
}
return $retObj
}

Function Insert-ResultInObject
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[Object]$inputObj,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$resultName,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$resultVal
)
if (Get-Member -InputObject $inputObj -Name $resultName) {
$inputObj.$resultName = $resultVal;
} else {
$inputObj | Add-Member -memberType NoteProperty -name $resultName -value $resultVal;
}
return $inputObj;
}

Function Generate-Report
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[Object]$inputObj,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$contextName,
[String]$viewAs = $script:resultView.Table
)

$head = @'
&lt;!--mce:0--&gt;
'@

$header = "&lt;H1&gt;DNS Health Report for " + $contextName + "&lt;/H1&gt;";
$ouputFile = $contextName + ".html";
$inputObj = $inputObj | ? {$null -ne ($_ | gm -m properties)};
$report = $inputObj | ConvertTo-Html -Head $head -Body $header -As $viewAs

# Do the colour coding of Success &amp; Failure, basically a hack in HTML.
$success2Search = "&lt;td&gt;" + [RetStatus]::Success + "&lt;/td&gt;";
$success2Replace = "&lt;td style=`"color:green;font-weight:bold;`"&gt;" + [RetStatus]::Success + "&lt;/td&gt;";
$failure2Search = "&lt;td&gt;" + [RetStatus]::Failure + "&lt;/td&gt;";
$failure2Replace = "&lt;td style=`"color:red;font-weight:bold;`"&gt;" + [RetStatus]::Failure + "&lt;/td&gt;";

$report = $report -creplace $success2Search, $success2Replace;
$report = $report -creplace $failure2Search, $failure2Replace;

$global:htmlReport += $report
}

#
# Tests whether given DNS Server is able to resolve input DNS Name.
#
Function Test-DnsServerForInputDnsName
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$dnsName,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$dnsServer,
$rrType = "All"
)
$result = [RetStatus]::Failure;

try {
$retObj = ExecuteCmdLet "Resolve-DnsName" @{"Name" = $dnsName; "Type" = $rrType; "Server" = $dnsServer};
if ($null -eq $retObj) {
LogComment $("Resolve-DnsName for " + $dnsName + " failed on server " + $dnsServer + " with " + $script:cmdLetReturnedStatus) $script:logLevel.Error;
$result = $script:cmdLetReturnedStatus;
} else {
LogComment $("Name resolution of " + $dnsName + " passed on server " + $dnsServer);
$result = [RetStatus]::Success;
}

} catch {
LogComment $("Test-DnsServerForInputDnsName failed " + $_.Exception) $script:logLevel.Error;
$result = [RetStatus]::Failure;
}

return $result;
}

Function Test-DnsServerForInputZone
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$zoneName,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$dnsServer,
$remoteServer = "."
)
$result = [RetStatus]::Failure;

try {

$dnsServerIP = $null;
if (![Net.IPaddress]::TryParse($dnsServer, [ref]$dnsServerIP)) {
try {
#Resolve and get first IP
$dnsServerIP = [System.Net.Dns]::GetHostAddresses($dnsServer).IPAddressToString.Split(" ")[0];
} catch {
LogComment $("Exception while trying to get IP Address of " + $dnsServer + "`n" + $_.Exception) $script:logLevel.Error;
throw;
}
}

$retObj = ExecuteCmdLet "Test-DnsServer" @{"ComputerName" = $remoteServer; "ZoneName" = $zoneName; "IPAddress" = $dnsServerIP};
if ($null -eq $retObj) {
LogComment $("Test-DnsServer failed for " + $zoneName + " on server " + $dnsServer) $script:logLevel.Warning;
$result = $script:cmdLetReturnedStatus;
} else {
if (($retObj.Result -eq "Success") -or ($retObj.Result -eq "NotAuthoritativeForZone")) {
LogComment $("Test-DnsServer passed for " + $zoneName + " on server " + $dnsServer + " with Result: " + $retObj.Result);
$result = [RetStatus]::Success;
} else {
LogComment $("Test-DnsServer failed for " + $zoneName + " on server " + $dnsServer + " with Result: " + $retObj.Result) $script:logLevel.Warning;
$result = $retObj.Result;
}
}
} catch {
LogComment $("Test-DnsServerForInputDnsName failed " + $_.Exception) $script:logLevel.Error;
$result = [RetStatus]::Failure;
}

return $result;
}

Function Test-DnsServerForInputContext
{
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$dnsServer,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$context,
$remoteServer = "."
)
$result = [RetStatus]::Failure;

try {

$retObj = ExecuteCmdLet "Test-DnsServer" @{"ComputerName" = $remoteServer; "IPAddress" = $dnsServer; "Context" = $context};
if ($null -eq $retObj) {
LogComment $("Test-DnsServer failed for DnsServer: " + $dnsServer + " with context: " + $context) $script:logLevel.Warning;
$result = $script:cmdLetReturnedStatus;
} else {
if ($retObj.Result -eq "Success") {
LogComment $("Test-DnsServer Passed for DnsServer: " + $dnsServer + " with context: " + $context + " and Result: " + $retObj.Result);
$result = [RetStatus]::Success;
} else {
LogComment $("Test-DnsServer Failed for DnsServer: " + $dnsServer + " with context: " + $context + " and Result: " + $retObj.Result) $script:logLevel.Warning;
$result = $retObj.Result;
}
}
} catch {
LogComment $("Test-DnsServerForInputContext failed " + $_.Exception) $script:logLevel.Error;
$result = [RetStatus]::Failure;
}

return $result;
}

Function Get-HashtableFromString
{
param(
[string] $serializedHashtable
)

[Hashtable] $ht = New-Object -TypeName Hashtable

$kvStrings = $Parameter1 -split ";"

foreach($kvStr in $kvStrings)
{
if($kvStr -ne $null -and $kvStr -ne "")
{
$arr = $kvStr -split ","

$list = New-Object -TypeName System.Collections.Generic.List[string]
for($i=1;$i -lt $arr.Length; $i++)
{
if($arr[$i] -ne $null -and $arr[$i] -ne "") { $list.Add($arr[$i]) }
}
$ht.Add($arr[0],$list)
}
}

return $ht
}

Function Get-StringFromHashtable
{
param(
[Hashtable] $ht
)

[string]$result = ""

foreach($kv in $ht.GetEnumerator())
{
$result += ";"+ $kv.Key
if($kv.Value -ne $null)
{
foreach($value in $kv.Value)
{
$result += "," + $value
}
}
}

return $result
}
for ($tryIndex = 1; $tryIndex -le 10; $tryIndex++)
{
try{
$targets = Get-SCOMClass -Name 'Microsoft.Windows.DNSServer.2016.Zone' | Get-SCOMClassInstance
break
}
catch { Start-Sleep -Seconds 1 }
}

[Hashtable] $ht = New-Object -TypeName Hashtable

if($targets -ne $null)
{
if($targets.Count -eq 1)
{
$zoneName = $zone.'[Microsoft.Windows.Server.DNS.Zone].ZoneName'.Value
$compName = $targets.'[Microsoft.Windows.Computer].PrincipalName'.Value
if($ht.Contains($zoneName))
{
$ht[$zoneName].Add($compName)
}
else
{
$list = New-Object -TypeName System.Collections.Generic.List[string]
$list.Add($compName)
$ht.Add($zoneName,$list)
}
}
else
{
foreach($zone in $targets)
{
$zoneName = $zone.'[Microsoft.Windows.DNSServer.2016.Zone].ZoneName'.Value
$compName = $zone.'[Microsoft.Windows.Computer].PrincipalName'.Value
if($ht.Contains($zoneName))
{
$ht[$zoneName].Add($compName)
}
else
{
$list = New-Object -TypeName System.Collections.Generic.List[string]
$list.Add($compName)
$ht.Add($zoneName,$list)
}
}
}
}

$serializedHt = Get-StringFromHashtable $ht
#Write-Host $serializedHt
Run-SCOMParameterizedTask -taskName 'Microsoft.Windows.DNSServer.2016.Task.HealthCheck.ZoneDelegation' `
-targetClass 'Microsoft.Windows.DNSServer.2016.Healthcheck.TaskTarget' `
-Parameter1 $serializedHt `
-OutputFolder $OutputFolder `
-ReportName $SCRIPT_NAME</Script></ScriptBody>
<OutputFolder>c:\</OutputFolder>
<TimeoutSeconds>300</TimeoutSeconds>
</ProbeAction>
</Task>