7

I am trying to create an script to get the certificate expiry date for an websites remotely for multiple servers. I have an script which is working for single server (Need to login into server and doing execution), I need to run this remotely for multiple servers. How can i modify this script to execute for multiple servers remotely. Please advice.

 $servers = get-content D:\Certificate.txt $DaysToExpiration = 60 #change this once it's working $expirationDate = (Get-Date).AddDays($DaysToExpiration) foreach ($server in $servers) { $sites = Get-Website | ? { $_.State -eq "Started" } | % { $_.Name } $certs = Get-ChildItem IIS:SSLBindings | ? { $sites -contains $_.Sites.Value } | % { $_.Thumbprint } Get-ChildItem CERT:LocalMachine/My | ? { $certs -contains $_.Thumbprint -and $_.NotAfter -lt $expirationDate } } 
3
  • get list of servers, loop on that list, do cert checking stuff. Commented Aug 31, 2016 at 15:02
  • Marc B, Yes i can know the logic but how to write that in script. I am sorry, i am new to Powershell. In learning stage. Commented Aug 31, 2016 at 15:03
  • Updated the script with Foreach command, When i execute this command no output is coming and no error showing as well, how to make it work ? please advice Commented Aug 31, 2016 at 17:04

4 Answers 4

9

Inspired by https://iamoffthebus.wordpress.com/2014/02/04/powershell-to-get-remote-websites-ssl-certificate-expiration/ I use following script:

$minimumCertAgeDays = 60 $timeoutMilliseconds = 10000 $urls = get-content .\check-urls.txt #disabling the cert validation check. This is what makes this whole thing work with invalid certs... [Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} foreach ($url in $urls) { Write-Host Checking $url -f Green $req = [Net.HttpWebRequest]::Create($url) $req.Timeout = $timeoutMilliseconds $req.AllowAutoRedirect = $false try {$req.GetResponse() |Out-Null} catch {Write-Host Exception while checking URL $url`: $_ -f Red} $certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString() #Write-Host "Certificate expires on (string): $certExpiresOnString" [datetime]$expiration = [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString()) #Write-Host "Certificate expires on (datetime): $expiration" [int]$certExpiresIn = ($expiration - $(get-date)).Days $certName = $req.ServicePoint.Certificate.GetName() $certPublicKeyString = $req.ServicePoint.Certificate.GetPublicKeyString() $certSerialNumber = $req.ServicePoint.Certificate.GetSerialNumberString() $certThumbprint = $req.ServicePoint.Certificate.GetCertHashString() $certEffectiveDate = $req.ServicePoint.Certificate.GetEffectiveDateString() $certIssuer = $req.ServicePoint.Certificate.GetIssuerName() if ($certExpiresIn -gt $minimumCertAgeDays) { Write-Host Cert for site $url expires in $certExpiresIn days [on $expiration] -f Green } else { Write-Host WARNING: Cert for site $url expires in $certExpiresIn days [on $expiration] -f Red Write-Host Threshold is $minimumCertAgeDays days. Check details:`nCert name: $certName -f Red Write-Host Cert public key: $certPublicKeyString -f Red Write-Host Cert serial number: $certSerialNumber`nCert thumbprint: $certThumbprint`nCert effective date: $certEffectiveDate`nCert issuer: $certIssuer -f Red } Write-Host rv req rv expiration rv certExpiresIn } 

Alternatively, you might find this advanced script useful:

  • you can switch between report output as Text, Html or PSObject
  • use the script with urls (parameter array) or with input file for urls or with pipeline input
  • improved stability: correctly handle missing certificates on HTTP connections
  • just put the code into a file like Check-ExpiringSslCerts.ps1

Here the advanced script code:

[CmdletBinding(DefaultParametersetname="URLs in text file")] Param( [ValidateSet('Text','Html','PSObject')] [string]$ReportType = 'Text', [int]$MinimumCertAgeDays = 60, [int]$TimeoutMilliseconds = 10000, [parameter(Mandatory=$false,ParameterSetName = "URLs in text file")] [string]$UrlsFile = '.\check-urls.txt', [parameter(Mandatory=$false,ParameterSetName = "List of URLs", ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [string[]]$Urls ) Begin { [string[]]$allUrls = @() $returnData = @() [bool]$ProcessedInputPipeLineByArrayItem = $false function CheckUrl ([string]$url, [array]$returnData) { [string]$details = $null if ($ReportType -eq "Html") { $stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($url) Write-Host "<tr><td>$stringHtmlEncoded</td>" } if ($ReportType -eq "Text") { Write-Host Checking $url } $req = [Net.HttpWebRequest]::Create($url) $req.Timeout = $timeoutMilliseconds $req.AllowAutoRedirect = $false try { $req.GetResponse() |Out-Null if ($req.ServicePoint.Certificate -eq $null) {$details = "No certificate in use for connection"} } catch { $details = "Exception while checking URL $url`: $_ " } if ($details -eq $null -or $details -eq "") { $certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString() #Write-Host "Certificate expires on (string): $certExpiresOnString" [datetime]$expiration = [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString()) #Write-Host "Certificate expires on (datetime): $expiration" [int]$certExpiresIn = ($expiration - $(get-date)).Days $certName = $req.ServicePoint.Certificate.GetName() $certPublicKeyString = $req.ServicePoint.Certificate.GetPublicKeyString() $certSerialNumber = $req.ServicePoint.Certificate.GetSerialNumberString() $certThumbprint = $req.ServicePoint.Certificate.GetCertHashString() $certEffectiveDate = $req.ServicePoint.Certificate.GetEffectiveDateString() $certIssuer = $req.ServicePoint.Certificate.GetIssuerName() if ($certExpiresIn -gt $minimumCertAgeDays) { if ($ReportType -eq "Html") { Write-Host "<td>OKAY</td><td>$certExpiresIn</td><td>$expiration</td><td>&nbsp;</td></tr>" } if ($ReportType -eq "Text") { Write-Host OKAY: Cert for site $url expires in $certExpiresIn days [on $expiration] -f Green } if ($ReportType -eq "PSObject") { $returnData += new-object psobject -property @{Url = $url; CheckResult = "OKAY"; CertExpiresInDays = [int]$certExpiresIn; ExpirationOn = [datetime]$expiration; Details = [string]$null} } } else { $details = "" $details += "Cert for site $url expires in $certExpiresIn days [on $expiration]`n" $details += "Threshold is $minimumCertAgeDays days. Check details:`n" $details += "Cert name: $certName`n" $details += "Cert public key: $certPublicKeyString`n" $details += "Cert serial number: $certSerialNumber`n" $details += "Cert thumbprint: $certThumbprint`n" $details += "Cert effective date: $certEffectiveDate`n" $details += "Cert issuer: $certIssuer" if ($ReportType -eq "Html") { Write-Host "<td>WARNING</td><td>$certExpiresIn</td><td>$expiration</td>" $stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($details) -replace "`n", "<br />" Write-Host "<tr><td>$stringHtmlEncoded</td></tr>" } if ($ReportType -eq "Text") { Write-Host WARNING: $details -f Red } if ($ReportType -eq "PSObject") { $returnData += new-object psobject -property @{Url = $url; CheckResult = "WARNING"; CertExpiresInDays = [int]$certExpiresIn; ExpirationOn = [datetime]$expiration; Details = $details} } rv expiration rv certExpiresIn } } else { if ($ReportType -eq "Html") { Write-Host "<td>ERROR</td><td>N/A</td><td>N/A</td>" $stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($details) -replace "`n", "<br />" Write-Host "<tr><td>$stringHtmlEncoded</td></tr>" } if ($ReportType -eq "Text") { Write-Host ERROR: $details -f Red } if ($ReportType -eq "PSObject") { $returnData += new-object psobject -property @{Url = $url; CheckResult = "ERROR"; CertExpiresInDays = $null; ExpirationOn = $null; Details = $details} } } if ($ReportType -eq "Text") { Write-Host } rv req return $returnData } #disabling the cert validation check. This is what makes this whole thing work with invalid certs... [Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} if ($ReportType -eq "Html") { Write-Host "<table><tr><th>URL</th><th>Check result</th><th>Expires in days</th><th>Expires on</th><th>Details</th></tr>" Add-Type -AssemblyName System.Web } } Process { if ($_ -ne $null) { CheckUrl $_ $returnData $ProcessedInputPipeLineByArrayItem = $true } } End { if ($ProcessedInputPipeLineByArrayItem -eq $false) { if ($Urls -eq $null) { $allUrls = get-content $UrlsFile } else { $allUrls = $Urls } foreach ($url in $allUrls) { $returnData = CheckUrl $url $returnData } } if ($ReportType -eq "Html") { Write-Host "</table>" } if ($ReportType -eq "PSObject") { return $returnData } } 

Output might look like e.g.:

"http://www.doma.com", "https://www.domb.com" | .\Check-ExpiringSslCerts.ps1 -ReportType PSObject | ft Url ExpirationOn CertExpiresInDays CheckResult Details --- ------------ ----------------- ----------- ------- http://www.doma.com ERROR No certificate in use for connection https://www.domb.com 18.11.2017 09:33:00 87 OKAY 
Sign up to request clarification or add additional context in comments.

3 Comments

This won't in Powershell Core 6 or Powershell 7+ due to HttpWebRequest being obsolete github.com/dotnet/runtime/issues/29301. Use TcpClient to get SslStream instead.
If you simply comment out this line: # $details = "Exception while checking URL $url`: $_ " Then the next block will print the correct information as $details is not initialized. I used this to verify an SLDAP connection. (which throws a handshake exception after SSL tunnel is established)
This solution is not compatible with Powershell Core
2

Taken mostly from https://gist.github.com/jstangroome/5945820, although with some changes. The following will obtain the certificate. $certificate.NotBefore and $certificate.NotAfter will then need to be checked.

function GetCertificate([string]$domain, [Int16]$port) { $certificate = $null $TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient $TcpClient.ReceiveTimeout = 1000 $TcpClient.SendTimeout = 1000 try { $TcpClient.Connect($domain, $port) $TcpStream = $TcpClient.GetStream() $Callback = { param($sender, $cert, $chain, $errors) return $true } $SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($TcpStream, $true, $Callback) try { $SslStream.AuthenticateAsClient($domain) $certificate = $SslStream.RemoteCertificate } finally { $SslStream.Dispose() } } finally { $TcpClient.Dispose() } if ($certificate) { if ($certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { $certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certificate } } return $certificate } 

1 Comment

Perfectly working
0

Put the whole code you've wrote in a script-block, in order to do so, just add at the beginning this code:

$sb = { 

and at the bottom of your code add:

} 

Once you have this script-block, you can run this script on the servers remotely using these commands:

$cred = Get-Credential $servers = get-content D:\Certificate.txt Invoke-Command -Credential $cred -ComputerName $servers -ScriptBlock $SB 

Hope it helped!

1 Comment

hi Amir, I am in vacation. I will try this and let you know
0

Your code snippets are helpful but they will throw an error on HTTP errors -- that is the nature of the underlying .NET HttpWebRequest object to panic on everything that's not code 200.

So, if you're testing, say, API endpoints that only accept POST HTTP verb, your script will fail as it will get a 405 error message. Alternatively, if your URL returns HTTP 404, your script will fail as well. Luckily, you can catch layer 7 errors and capture the required info anyway just patch your code in this manner:

try {$req.GetResponse() | Out-Null } catch { if ($_.Exception.InnerException.Status -eq 'ProtocolError') { # saving the info anyway since this is a L7 error, e.g.: $certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString() [datetime]$expiration [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString()) $certIssuer = $req.ServicePoint.Certificate.GetIssuerName() # ... } else { # this is a real error - timeout, DNS failure etc Write-Host "$url, $_" -ForegroundColor Red Continue } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.