简体   繁体   中英

Powershell script to get certificate expiry for a website remotely for multiple servers

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
}
}

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 eg:

"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

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!

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
    }       
}

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM