IS there a way we can setup public domain montoring? Meaning something that will check each customer’s public DNS and check the expiry date and then send an alert, say 30 data in advance of expiration. I know Hudu has this functionality, I was wondering if we can do it here? I am wanting to consolidate my tools as much as I can. Too many different tools to monitor my customers. Here is some of what I have: Migrating to Syncro from Atera, SaaSAlerts (now a Kaseya product, YUK!), Hudu for documentation, OpenDNS (Cisco Umbrella for DNS filtering).
Its pretty basic but might work for Certificate Expiration use case:
Simple SSL Certificate Expiration Check
Have not looked at Domain itself expiring, I think this is different. Someone can correct me if I am wrong.
Domain Expiry can be done with this. Using example.com and set the warning limit beyond its current renewal period.
# Check-DomainExpiry.ps1
# Checks WHOIS data for one or more domains and logs a warning if expiry is within 90 days.
#
# Usage:
# .\Check-DomainExpiry.ps1
# .\Check-DomainExpiry.ps1 -Domains "example.com","another.com"
# .\Check-DomainExpiry.ps1 -DaysWarning 60 -LogFile "C:\Logs\domain-expiry.log"
param(
[string[]]$Domains = @("example.com"), # Add your domains here, or pass via -Domains
[int] $DaysWarning = 185, # Warn when expiry is within this many days
[string] $LogFile = "$PSScriptRoot\domain-expiry.log"
)
Import-Module $env:SyncroModule
# -----------------------------------------------------------------------
# Logging helper
# -----------------------------------------------------------------------
function Write-Log {
param(
[string]$Message,
[ValidateSet("INFO","WARN","ERROR")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogFile -Value $line
switch ($Level) {
"WARN" { Write-Host $line -ForegroundColor Yellow }
"ERROR" { Write-Host $line -ForegroundColor Red }
default { Write-Host $line }
}
}
# -----------------------------------------------------------------------
# WHOIS query — sends a raw TCP query to whois.iana.org (port 43)
# then follows the referral to the TLD-specific WHOIS server
# -----------------------------------------------------------------------
function Get-WhoisData {
param([string]$Domain)
# Step 1: Ask IANA for the correct WHOIS server for this TLD
$tld = $Domain.Split(".")[-1]
$ianaServer = "whois.iana.org"
$rawIana = Invoke-WhoisQuery -Server $ianaServer -Query $tld
# Extract the referral whois server
$whoisServer = ($rawIana -split "`n" |
Where-Object { $_ -match "^\s*whois:\s+" } |
Select-Object -First 1) -replace "^\s*whois:\s+", "" | ForEach-Object { $_.Trim() }
if (-not $whoisServer) {
Write-Log "Could not determine WHOIS server for TLD .$tld" "ERROR"
return $null
}
# Step 2: Query the TLD-specific WHOIS server
return Invoke-WhoisQuery -Server $whoisServer -Query $Domain
}
function Invoke-WhoisQuery {
param(
[string]$Server,
[string]$Query
)
try {
$tcp = New-Object System.Net.Sockets.TcpClient($Server, 43)
$stream = $tcp.GetStream()
$writer = New-Object System.IO.StreamWriter($stream)
$reader = New-Object System.IO.StreamReader($stream)
$writer.WriteLine($Query)
$writer.Flush()
$response = $reader.ReadToEnd()
$tcp.Close()
return $response
} catch {
Write-Log "WHOIS query failed for '$Query' on server '$Server': $_" "ERROR"
return $null
}
}
# -----------------------------------------------------------------------
# Parse expiry date from raw WHOIS text
# -----------------------------------------------------------------------
function Get-ExpiryDate {
param([string]$WhoisData)
# Common field names used by different registrars
$patterns = @(
"Registry Expiry Date:\s*(.+)",
"Expiry Date:\s*(.+)",
"Expiration Date:\s*(.+)",
"paid-till:\s*(.+)",
"expire:\s*(.+)",
"expires:\s*(.+)",
"Registrar Registration Expiration Date:\s*(.+)"
)
foreach ($pattern in $patterns) {
$match = [regex]::Match($WhoisData, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
if ($match.Success) {
$rawDate = $match.Groups[1].Value.Trim()
# Try parsing — [datetime]::Parse handles ISO 8601 and most common formats
try {
return [datetime]::Parse($rawDate)
} catch {
# Try stripping any trailing timezone label (e.g. "2025-06-01T00:00:00 UTC")
$cleanDate = $rawDate -replace "\s*(UTC|GMT|Z)$", ""
try { return [datetime]::Parse($cleanDate) } catch { continue }
}
}
}
return $null
}
# -----------------------------------------------------------------------
# Main loop
# -----------------------------------------------------------------------
Write-Log "=== Domain expiry check started ==="
foreach ($domain in $Domains) {
$domain = $domain.Trim().ToLower()
Write-Log "Checking domain: $domain"
$whoisData = Get-WhoisData -Domain $domain
if (-not $whoisData) {
Write-Log "No WHOIS data returned for $domain" "ERROR"
continue
}
$expiryDate = Get-ExpiryDate -WhoisData $whoisData
if (-not $expiryDate) {
Write-Log "Could not parse expiry date for $domain" "ERROR"
continue
}
$daysUntilExpiry = ($expiryDate - (Get-Date)).Days
$strDomainMessage = "DOMAIN WARNING: $domain expires on $($expiryDate.ToString('yyyy-MM-dd'))"
if ($daysUntilExpiry -lt 0) {
Write-Log "DOMAIN EXPIRED: $domain expired on $($expiryDate.ToString('yyyy-MM-dd')) ($([Math]::Abs($daysUntilExpiry)) days ago)" "ERROR"
} elseif ($daysUntilExpiry -le $DaysWarning) {
Write-Log "EXPIRY WARNING: $domain expires on $($expiryDate.ToString('yyyy-MM-dd')) — only $daysUntilExpiry days remaining!" "WARN"
Log-Activity -Message $strDomainMessage -EventName "Domain Maintenance"
Rmm-Alert -Category 'Domain Maintenance' -Body $strDomainMessage
} else {
Write-Log "OK: $domain expires on $($expiryDate.ToString('yyyy-MM-dd')) ($daysUntilExpiry days remaining)"
}
}
Write-Log "=== Domain expiry check complete ==="
Thanks! Will try this and advise!
could be improved to take runtime values which is easy enough or you could use a single list from a website or on the script to then run on one end point to raise alerts as needed.
This can also be done in Hudu. Under the Customer > Websites.
The script worked and it sent a ticket. Do you know how I would also populate the Expiry date into a Custom field on the customer? I tried adding this Set-Customer-Field -Name “Domain Expiry Date” -Value $expiryDate and it didn’t work, like I am adding the bitlocker key on the asset custom field. An example would be here:
Here is the script for the bit-locker key…
Hey @MackeyInfoTech I don’t believe you can set a Customer Custom asset field via script. It’s not within scope of their scripting engine. You would have to set it via API.
I would recommend either:
-
using Hudu’s domain tool for this, it’s purpose built and works well.
-
if you need more customization that hudu offers, use a custom workflow automation tool like hirebumblebee.com to build this out completely, which can make using the power of Syncro’s API a breeze.
I would use a device asset field and we tend to have a “management“ asset that we put these things against. You could also pull down a list of domains from a web url and update that or have a hashtable for domains per client in the script…… there are other ways as well.
Asset field would need to be created and then pull it in. A customer asset may be able to be pulled by the api within the script.
Would also be worth creating a ticket as well for the work to be carried out. I may build it out a bit but would be the weekend.
Glad it is working in principle for you.
Thanks, yes, I am liking the idea of a “management” asset that I can perform these type of tasks. I think I will have it run a check monthly and update the results in the management asset custom field.
Also in IT glue. Domain Tracker and SSL TRacker
Check-DomainExpiry.ps1
Checks WHOIS data for one or more domains and logs a warning if expiry is within X days.
Outputs ONLY domains expiring within the next 90 days to Syncro Asset Field (sorted soonest first).
param(
[string]$Domains = @(
“domain.com”,
“domain2.com”,
“domain3.com”,
“domain4.com”,
“domain5.com”,
“domain6.com”,
“domain7.us”,
“domain8.com”,
“domain9.com”,
“domain10.com”
),
[int] $DaysWarning = 960,
[string] $LogFile = “C:\ProgramData\DomainExpiry\domain-expiry.log”
)
Import-Module $env:SyncroModule
Logging helper
function Write-Log {
param(
[string]$Message,
[ValidateSet(“INFO”,“WARN”,“ERROR”)]
[string]$Level = “INFO”
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogFile -Value $line
switch ($Level) {
"WARN" { Write-Host $line -ForegroundColor Yellow }
"ERROR" { Write-Host $line -ForegroundColor Red }
default { Write-Host $line }
}
}
WHOIS query
function Get-WhoisData {
param([string]$Domain)
$tld = $Domain.Split(".")[-1]
$ianaServer = "whois.iana.org"
$rawIana = Invoke-WhoisQuery -Server $ianaServer -Query $tld
$whoisServer = ($rawIana -split "`n" |
Where-Object { $_ -match "^\s*whois:\s+" } |
Select-Object -First 1) -replace "^\s*whois:\s+", "" |
ForEach-Object { $_.Trim() }
if (-not $whoisServer) {
Write-Log "Could not determine WHOIS server for TLD .$tld" "ERROR"
return $null
}
return Invoke-WhoisQuery -Server $whoisServer -Query $Domain
}
function Invoke-WhoisQuery {
param(
[string]$Server,
[string]$Query
)
try {
$tcp = New-Object System.Net.Sockets.TcpClient($Server, 43)
$stream = $tcp.GetStream()
$writer = New-Object System.IO.StreamWriter($stream)
$reader = New-Object System.IO.StreamReader($stream)
$writer.WriteLine($Query)
$writer.Flush()
$response = $reader.ReadToEnd()
$tcp.Close()
return $response
}
catch {
Write-Log "WHOIS query failed for '$Query' on server '$Server': $_" "ERROR"
return $null
}
}
Parse expiry date
function Get-ExpiryDate {
param([string]$WhoisData)
$patterns = @(
"Registry Expiry Date:\s*(.+)",
"Expiry Date:\s*(.+)",
"Expiration Date:\s*(.+)",
"paid-till:\s*(.+)",
"expire:\s*(.+)",
"expires:\s*(.+)",
"Registrar Registration Expiration Date:\s*(.+)"
)
foreach ($pattern in $patterns) {
$match = [regex]::Match($WhoisData, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
if ($match.Success) {
$rawDate = $match.Groups[1].Value.Trim()
try {
return [datetime]::Parse($rawDate)
}
catch {
$cleanDate = $rawDate -replace "\s*(UTC|GMT|Z)$", ""
try { return [datetime]::Parse($cleanDate) } catch { continue }
}
}
}
return $null
}
Main
Write-Log “=== Domain expiry check started ===”
$DomainResults = @()
foreach ($domain in $Domains) {
$domain = $domain.Trim().ToLower()
Write-Log "Checking domain: $domain"
$whoisData = Get-WhoisData -Domain $domain
if (-not $whoisData) {
Write-Log "No WHOIS data returned for $domain" "ERROR"
continue
}
$expiryDate = Get-ExpiryDate -WhoisData $whoisData
if (-not $expiryDate) {
Write-Log "Could not parse expiry date for $domain" "ERROR"
continue
}
$daysUntilExpiry = ($expiryDate - (Get-Date)).Days
$DomainResults += [PSCustomObject]@{
Domain = $domain
ExpiryDate = $expiryDate
Days = $daysUntilExpiry
}
$line = "$domain expires on $($expiryDate.ToString('yyyy-MM-dd'))"
if ($daysUntilExpiry -lt 0) {
Write-Log "DOMAIN EXPIRED: $line" "ERROR"
}
elseif ($daysUntilExpiry -le $DaysWarning) {
Write-Log "EXPIRY WARNING: $line" "WARN"
Log-Activity -Message "DOMAIN WARNING: $line" -EventName "Domain Maintenance"
Rmm-Alert -Category 'Domain Maintenance' -Body "DOMAIN WARNING: $line"
}
else {
Write-Log "OK: $line"
}
}
Write-Log “=== Domain expiry check complete ===”
Asset Field Output: ONLY expiring within next 90 days (3 months)
$DaysOutputWindow = 90
$ExpiringSoon = $DomainResults |
Where-Object { $.Days -ge 0 -and $.Days -le $DaysOutputWindow } |
Sort-Object ExpiryDate
if (-not $ExpiringSoon -or $ExpiringSoon.Count -eq 0) {
$AssetText = “None expiring within $DaysOutputWindow days”
}
else {
$AssetText = ($ExpiringSoon | ForEach-Object {
“$($.Domain) expires on $($.ExpiryDate.ToString(‘yyyy-MM-dd’))”
}) -join “`n”
}
Set-Asset-Field -Name “Domain_Expirations_Within_90_Days” -Value $AssetText
Write-Log “Set-Asset-Field wrote $($AssetText.Length) chars to Domain_Expirations”


