Function App slot health check failing even when its running fine - azure-devops

I have a script thats been used for a long time with no issues for checking the health status of a deployment slot
This is built into the deployment in dev ops
When I deploy the function via my release pipeline its fine, i.e. the slot starts and stays running with no errors
But the health check fails which in turn stops my deployment
The script is a standard ps template as shown below
param (
[string]$URI,
[string]$resultVarName,
[string]$Method = 'GET',
[string]$SuccessTextContent = 'Healthy',
[string]$Retries = 1,
[string]$SecondsDelay = 2,
[string]$TimeoutSec = 120
)
Write-Host "$Method ""$URI"" Retries: $Retries, SecondsDelay $SecondsDelay, TimeoutSec $TimeoutSec";
Function Req {
Param(
[Parameter(Mandatory=$True)]
[hashtable]$Params,
[int]$Retries = 1,
[int]$SecondsDelay = 2
)
$Params.Add('UserAgent', 'azagent powershell task')
$method = $Params['Method']
$url = $Params['Uri']
$cmd = { Write-Host "$method $url..." -NoNewline; Invoke-WebRequest #Params }
$retrycount = 0
$completed = $false
$response = $null
while (-not $completed) {
try {
$response = Invoke-Command $cmd -ArgumentList $Params
if ($response.StatusCode -ne 200) {
throw "Expecting reponse code 200, was: $($response.StatusCode)"
}
$completed = $true
} catch {
Write-Output "$(Get-Date -Format G): Request to $url failed. $_"
if ($retrycount -ge $Retries) {
$completed = $true
$totalCalls = $retrycount + 1
Write-Warning "Request to $url failed $totalCalls times in total."
} else {
Write-Warning "Request to $url failed. Retrying in $SecondsDelay seconds."
Start-Sleep $SecondsDelay
$retrycount++
}
}
}
Write-Host "Status: ($($response.StatusCode))"
return $response
}
$res = Req -Retries $Retries -SecondsDelay $SecondsDelay -Params #{ 'Method'=$Method;'Uri'=$URI;'TimeoutSec'=$TimeoutSec;'UseBasicParsing'=$true }
if($res.StatusCode -ne 200)
{
echo "##vso[task.setvariable variable=$resultVarName]Unhealthy"
Write-Error "Health check validation failure."
}
else
{
echo "##vso[task.setvariable variable=$resultVarName]Healthy"
Write-Host "Health check validation success."
}
Does anyone know how to fix this?
I get the error powershell-scripts\service-health-check.ps1 : [31;1mHealth check validation failure.

Related

Parameter set cannot be resolved using the specified named parameters in powershell script

I want to use this powershell script to import and export platforms from cyberark. However, its showing this error:
Parameter set cannot be resolved using the specified named parameters.
+ CategoryInfo : InvalidArgument: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : AmbiguousParameterSet
I am not very not very fimilar with powershell yet. Could someone please help me out. I would really appricate it. I tried many changes but nothing is really happening. I need to fix this error as soon as possible. Please go through the code and give me solution to fix it.
param
(
[Parameter(Mandatory = $true, HelpMessage = "Please enter your PVWA address (For example: https://pvwa.mydomain.com/PasswordVault)")]
#[ValidateScript({Invoke-WebRequest -UseBasicParsing -DisableKeepAlive -Uri $_ -Method 'Head' -ErrorAction 'stop' -TimeoutSec 30})]
[Alias("url")]
[String]$PVWAURL,
[Parameter(Mandatory = $false, HelpMessage = "Enter the Authentication type (Default:CyberArk)")]
[ValidateSet("cyberark", "ldap", "radius")]
[String]$AuthType = "cyberark",
# Use this switch to Import a Platform
[Parameter(ParameterSetName = 'Import', Mandatory = $true)][switch]$Import,
# Use this switch to Export a Platform
[Parameter(ParameterSetName = 'Export', Mandatory = $true)][switch]$Export,
# Use this switch to Import a Platform using a file
[Parameter(ParameterSetName = 'ImportFile', Mandatory = $true)][switch]$ImportFile,
# Use this switch to Export a Platform using a file
[Parameter(ParameterSetName = 'ExportFile', Mandatory = $true)][switch]$ExportFile,
[Parameter(ParameterSetName = 'ExportActive', Mandatory = $true)][switch]$ExportActive,
[Parameter(ParameterSetName = 'ExportAll', Mandatory = $true)][switch]$ExportAll,
[Parameter(ParameterSetName = 'Export', Mandatory = $true, HelpMessage = "Enter the platform ID to export")]
[Alias("id")]
[string]$PlatformID,
[Parameter(ParameterSetName = 'Import', Mandatory = $true, HelpMessage = "Enter the platform Zip path for import")]
[Parameter(ParameterSetName = 'Export', Mandatory = $true, HelpMessage = "Enter the platform Zip path to export")]
[Parameter(ParameterSetName = 'ImportFile', Mandatory = $false, HelpMessage = "Enter the platform Zip path for import")]
[Parameter(ParameterSetName = 'ExportFile', Mandatory = $true, HelpMessage = "Enter the platform Zip path to export")]
[Parameter(ParameterSetName = 'ExportActive', Mandatory = $true, HelpMessage = "Enter the platform Zip path to export")]
[Parameter(ParameterSetName = 'ExportAll', Mandatory = $true, HelpMessage = "Enter the platform Zip path to export")]
[string]$PlatformZipPath,
# Use this to specify where file to read is
[Parameter(ParameterSetName = 'ImportFile', Mandatory = $true, HelpMessage = "Enter the import file path for import")]
[Parameter(ParameterSetName = 'ExportFile', Mandatory = $true, HelpMessage = "Enter the export file path for export")]
[string]$listFile,
[Parameter(Mandatory = $false)]
$creds,
# Use this switch to Disable SSL verification (NOT RECOMMENDED)
[Parameter(Mandatory = $false)]
[Switch]$DisableSSLVerify
)
# Global URLS
# -----------
$URL_PVWAAPI = $PVWAURL + "/api"
$URL_Authentication = $URL_PVWAAPI + "/auth"
$URL_Logon = $URL_Authentication + "/$AuthType/Logon"
$URL_Logoff = $URL_Authentication + "/Logoff"
# URL Methods
# -----------
$URL_GetPlatforms = $URL_PVWAAPI + "/Platforms"
$URL_PlatformDetails = $URL_PVWAAPI + "/Platforms/{0}"
$URL_ExportPlatforms = $URL_PVWAAPI + "/Platforms/{0}/Export"
$URL_ImportPlatforms = $URL_PVWAAPI + "/Platforms/Import"
# Initialize Script Variables
# ---------------------------
$rstusername = $rstpassword = ""
$logonToken = ""
#region Functions
Function Test-CommandExists {
Param ($command)
$oldPreference = $ErrorActionPreference
$ErrorActionPreference = 'stop'
try { if (Get-Command $command) { RETURN $true } }
Catch { Write-Host "$command does not exist"; RETURN $false }
Finally { $ErrorActionPreference = $oldPreference }
} #end function test-CommandExists
# #FUNCTION# ======================================================================================================================
# Name...........: Join-ExceptionMessage
# Description....: Formats exception messages
# Parameters.....: Exception
# Return Values..: Formatted String of Exception messages
# =================================================================================================================================
Function Join-ExceptionMessage {
<#
.SYNOPSIS
Formats exception messages
.DESCRIPTION
Formats exception messages
.PARAMETER Exception
The Exception object to format
#>
param(
[Exception]$e
)
Begin {
}
Process {
$msg = "Source:{0}; Message: {1}" -f $e.Source, $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$msg += "`n`t->Source:{0}; Message: {1}" -f $e.Source, $e.Message
}
return $msg
}
End {
}
}
#endregion
Function import-platform {
param(
[string]$PlatformZipPath
)
If (Test-Path $PlatformZipPath) {
$zipContent = [System.IO.File]::ReadAllBytes($(Resolve-Path $PlatformZipPath))
$importBody = #{ ImportFile = $zipContent; } | ConvertTo-Json -Depth 3 -Compress
try {
$ImportPlatformResponse = Invoke-RestMethod -Method POST -Uri $URL_ImportPlatforms -Headers $logonHeader -ContentType "application/json" -TimeoutSec 2700 -Body $importBody
Write-Debug "Platform ID imported: $($ImportPlatformResponse.PlatformID)"
Write-Host "Retrieving Platform details"
# Get the Platform Name
$platformDetails = Invoke-RestMethod -Method Get -Uri $($URL_PlatformDetails -f $ImportPlatformResponse.PlatformID) -Headers $logonHeader -ContentType "application/json" -TimeoutSec 2700
If ($platformDetails) {
Write-Debug $platformDetails
Write-Host "$($platformDetails.Details.PolicyName) (ID: $($platformDetails.PlatformID)) was successfully imported and $(if($platformDetails.Active) { "Activated" } else { "Inactive" })"
Write-Host "Platform details:"
$platformDetails.Details | Select-Object PolicyID, AllowedSafes, AllowManualChange, PerformPeriodicChange, #{Name = 'AllowManualVerification'; Expression = { $_.VFAllowManualVerification } }, #{Name = 'PerformPeriodicVerification'; Expression = { $_.VFPerformPeriodicVerification } }, #{Name = 'AllowManualReconciliation'; Expression = { $_.RCAllowManualReconciliation } }, #{Name = 'PerformAutoReconcileWhenUnsynced'; Expression = { $_.RCAutomaticReconcileWhenUnsynched } }, PasswordLength, MinUpperCase, MinLowerCase, MinDigit, MinSpecial
}
} catch {
#Write-Error $_.Exception
#Write-Error $_.Exception.Response
#Write-Error $_.Exception.Response.StatusDescription
($_.ErrorDetails | ConvertFrom-Json | Select-Object -Property ErrorMessage)
"Error while attempting to export $PlatformZipPath"
""
}
}
} #end function Import
Function export-platform {
param(
[string]$PlatformID
)
try {
$exportURL = $URL_ExportPlatforms -f $PlatformID
Invoke-RestMethod -Method POST -Uri $exportURL -Headers $logonHeader -ContentType "application/zip" -TimeoutSec 2700 -OutFile "$PlatformZipPath\$PlatformID.zip" -ErrorAction SilentlyContinue
} catch {
#Write-Error $_.Exception.Response
#Write-Error $_.Exception.Response.StatusDescription
($_.ErrorDetails | ConvertFrom-Json | Select-Object -Property ErrorMessage)
"Error while attempting to export $PlatformID"
""
}
} #end function Import
Function Get-PlatformsList {
param(
[switch]$GetAll
)
$idList = #()
try {
If ($GetAll){
$url = $URL_GetPlatforms + "?PlatformType=Regular"
} else {
$url = $URL_GetPlatforms + "?Active=True&PlatformType=Regular"
}
$result = Invoke-RestMethod -Method GET -Uri $url -Headers $logonHeader -ErrorAction SilentlyContinue
foreach ($platform in $result.Platforms){
$idList += $platform.general.id
}
return $idList
} catch {
#Write-Error $_.Exception.Response
#Write-Error $_.Exception.Response.StatusDescription
($_.ErrorDetails | ConvertFrom-Json | Select-Object -Property ErrorMessage)
"Error while attempting to export $PlatformID"
""
}
} #end function Import
If (Test-CommandExists Invoke-RestMethod) {
If ($DisableSSLVerify) {
try {
Write-Warning "It is not Recommended to disable SSL verification" -WarningAction Inquire
# Using Proxy Default credentials if the Server needs Proxy credentials
[System.Net.WebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
# Using TLS 1.2 as security protocol verification
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11
# Disable SSL Verification
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $DisableSSLVerify }
} catch {
Write-Error "Could not change SSL validation"
Write-Error (Join-ExceptionMessage $_.Exception) -ErrorAction "SilentlyContinue"
return
}
} Else {
try {
Write-Debug "Setting script to use TLS 1.2"
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
} catch {
Write-Error "Could not change SSL settings to use TLS 1.2"
Write-Error (Join-ExceptionMessage $_.Exception) -ErrorAction "SilentlyContinue"
}
}
# Check that the PVWA URL is OK
If ($PVWAURL -ne "") {
If ($PVWAURL.Substring($PVWAURL.Length - 1) -eq "/") {
$PVWAURL = $PVWAURL.Substring(0, $PVWAURL.Length - 1)
}
} else {
Write-Host -ForegroundColor Red "PVWA URL can not be empty"
return
}
Write-Host "Export / Import Platform: Script Started" -ForegroundColor Cyan
#region [Logon]
# Get Credentials to Login
# ------------------------
$caption = "Export / Import Platform"
$msg = "Enter your User name and Password";
if ($Null -eq $creds) {$creds = $Host.UI.PromptForCredential($caption, $msg, "", "")}
if ($null -ne $creds) {
$rstusername = $creds.username.Replace('\', '');
$rstpassword = $creds.GetNetworkCredential().password
} else { return }
# Create the POST Body for the Logon
# ----------------------------------
$logonBody = #{ username = $rstusername; password = $rstpassword; concurrentSession = 'true' }
$logonBody = $logonBody | ConvertTo-Json
try {
# Logon
$logonToken = Invoke-RestMethod -Method Post -Uri $URL_Logon -Body $logonBody -ContentType "application/json"
} catch {
Write-Host -ForegroundColor Red $_.Exception.Response.StatusDescription
$logonToken = ""
}
If ($logonToken -eq "") {
Write-Host -ForegroundColor Red "Logon Token is Empty - Cannot login"
return
}
# Create a Logon Token Header (This will be used through out all the script)
# ---------------------------
$logonHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$logonHeader.Add("Authorization", $logonToken)
#endregion
switch ($PsCmdlet.ParameterSetName) {
"Import" {
import-platform $PlatformZipPath -error
}
"ImportFile" {
foreach ($line in Get-Content $listFile) {
Write-Debug "Trying to import $line"
if (![string]::IsNullOrEmpty($line)) { import-platform $line }
}
}
"Export" {
if (![string]::IsNullOrEmpty($PlatformID)) { export-platform $PlatformID}
}
"ExportFile" {
$null | Out-File -FilePath "$PlatformZipPath\_Exported.txt" -Force
foreach ($line in Get-Content $listFile) {
Write-Debug "Trying to export $line"
if (![string]::IsNullOrEmpty($line))
{
export-platform $line
("$PlatformZipPath\$line.zip").Replace("\\","\").Replace("/","\") | Out-File -FilePath "$PlatformZipPath\_Exported.txt" -Append
}
}
}
("ExportActive" -or "ExportAll") {
$platforms = Get-PlatformsList -GetAll:$(($PsCmdlet.ParameterSetName -eq "ExportAll"))
$null | Out-File -FilePath "$PlatformZipPath\_Exported.txt" -Force
foreach ($line in $platforms) {
Write-Debug "Trying to export $line"
if (![string]::IsNullOrEmpty($line)) {
export-platform $line
("$PlatformZipPath\$line.zip").Replace("\\","\").Replace("/","\") | Out-File -FilePath "$PlatformZipPath\_Exported.txt" -Append
}
}
}
}
# Logoff the session
# ------------------
Write-Host "Logoff Session..."
Invoke-RestMethod -Method Post -Uri $URL_Logoff -Headers $logonHeader -ContentType "application/json" | Out-Null
} else {
Write-Error "This script requires PowerShell version 3 or above"
}
Write-Host "Export / Import Platform: Script Finished" -ForegroundColor Cyan

How to handle a network mapping with retry and looping in powershell?

I would like to mapping a network. I need to use retry if the mapping fail. After maximum retry it will fail out and continue to another process. If the mapping pass, then continue to another process too. I've tried this way, but seems the retry is not working. Once the mapping fail, it does not do the network mapping process, only process the looping.
Anyone can give me idea please.
$Stoploop = $false
[int]$Retrycount = "0"
do {
try {
$net = New-Object -ComObject WScript.Network
$net.MapNetworkDrive("$Path", "\\$IP\$Folder", $False, "$Server\$user", "$pass")
$Message = "NetWork Mapping : " + $Path + " Mount Successful"
Write-Host $Message
$Stoploop = $true
CaptureLog "$Message`n "
}
catch {
if ($Retrycount -eq 15){
$Message_1 = "Could not mount after 15 retries." + " $_"
$Stoploop = $true
CaptureLog $Message_1
ErrorHandling $Message_1
}
else {
$Message = "NetWork Mapping : " + $Path + " Trying to mount..."
Write-Host $Message
CaptureLog $Message
Start-Sleep -Seconds 3
$Retrycount = $Retrycount + 1
}
}
}
while ($Stoploop -eq $false) {
}
Thank you so much for the advice. Appreciated!
Both comments are valid and useful (com object outside the loop and Get-PSDrive, New-PSDrive usage).
Now, I tested your script and it should do what you expect. The only case where it misses the point is when the network drive is already in use. To catch this with minimum modifications, you can simple add this check before your loop even starts:
if(Test-Path $Path) {
# if you want to unmap the drive, use: $net.RemoveNetworkDrive($Path, $true) and remove the $Stoploop = $true
$Message = "NetWork Drive " + $Path + " Already in use..."
Write-Host $Message
CaptureLog "$Message`n "
$Stoploop = $true
}
Now, if you want to simplify your script, you can do something like this:
$net = New-Object -ComObject WScript.Network
$retrycount = 0
if(Test-Path $Path) {
# if you want to unmap the drive, use: $net.RemoveNetworkDrive($Path, $true) and remove the $retrycount = 20
$Message = "NetWork Drive " + $Path + " Already in use..."
Write-Host $Message
CaptureLog "$Message`n "
$retrycount = 20
}
while($retrycount -lt 15) {
$retrycount += 1
try {
$net.MapNetworkDrive($Path, "\\$IP\$Folder", $False, "$Server\$user", $pass)
$Message = "NetWork Mapping (try $($retrycount)) - $($Path) Mount Successful"
Write-Host $Message
CaptureLog "$Message`n "
$retrycount = 100 # this will exit the loop and indicate successful mapping
} catch {
$Message = "NetWork Mapping (try $($retrycount)) - $($Path) Mount failed: $($_.Exception.Message.Replace("`n",''))"
Write-Host $Message
CaptureLog "$Message`n "
Start-Sleep -Seconds 3
}
}

Parsing Function return in PowerShell Script

I am trying to parse the response from one of the functions in PowerShell Script and based on the response I have to make some decision.
The function is returning JSON object successfully but not able to parse the response from the function. I need to check if validnodes count is 0 or not.
[String[]] $NodeList = 'a1572dev00e001','a1572dev00q001'
$Response = Get-Nodes
Write-Output "Response $Response"
$JSRes = $Response | ConvertFrom-Json
Write-Output "Parsing response $JSRes"
#$Result = "success"
if($JSRes.ValidNodes.Count -gt 0)
{
Write-Output "$JSRes.ValidNodes"
$Result = "success"
}
else
{
Write-Output "All nodes are Invalid"
Write-Output "Invalid Nodes: $JSRes.Invalid"
$Result = "failed"
$ErrorMessage = "All nodes are Invalid"
}
Write-Output $Result
#Function
function Get-Nodes
{
$ValidNodes=#()
$InvalidNodes=#()
foreach($Node in $NodeList)
{
if(Get-ADComputer -filter {Name -eq $Node})
{
$ValidNodes +=$Node
}
else
{
$InvalidNodes +=$Node
}
}
$JRes = #{"ValidNodes"=$ValidNodes;"Invalid"=$InvalidNodes} | ConvertTo-Json -Compress
Write-Output $JRes
return $JRes
}
Output:
Response {"ValidNodes":["a1572dev00e001","a1572dev00q001"],"Invalid":[]} {"ValidNodes":["
a1572dev00e001","a1572dev00q001"],"Invalid":[]}
Parsing response
All nodes are Invalid
Invalid Nodes:
failed
One issue is you are outputting the $Jres twice.
Write-Output $JRes
return $JRes
These effectively do the exact same thing. Next, you're using ConvertFrom-String when it seems you should be using ConvertFrom-Json
$JSON = $Response | ConvertFrom-String
Finally, you're trying to output $ValidNodes and $InvalidNodes that only exist in your function. Change these to $JSON.ValidNodes and $JSON.InvalidNodes
One more suggestion is to parameterize the Nodelist so you can just pass the nodes to the function.
#Function
function Get-Nodes
{
Param([string[]]$nodelist)
$ValidNodes=#()
$InvalidNodes=#()
foreach($Node in $NodeList)
{
if(Get-ADComputer -filter {Name -eq $Node})
{
$ValidNodes +=$Node
}
else
{
$InvalidNodes +=$Node
}
}
$JRes = #{"ValidNodes"=$ValidNodes;"Invalid"=$InvalidNodes} | ConvertTo-Json -Compress
$JRes
}
$Response = Get-Nodes a1572dev00e001,a1572dev00q001
Write-Output "Response $Response"
$JSON = $Response | ConvertFrom-Json
Write-Output "Parsing response $JSON"
if($JSON.ValidNodes.Count -gt 0)
{
Write-Output "Valid Nodes: $($JSON.ValidNodes)"
$Result = "success"
}
else
{
Write-Output "All nodes are Invalid"
Write-Output "Invalid Nodes: $($JSON.InValidNodes)"
$Result = "failed"
$ErrorMessage = "All nodes are Invalid"
}
Write-Output $Result

Recheck the URL thrice for web exception cases powershell

$status_wait =1
Do{
$https_request= invoke_webrequest -uri "https:\\"
$https_status = [int]http_request.statuscode
If($($_.categoryinfo.reason) -eq "webexception") {
Start-sleep -secounds 30
$status_wait+=1
}
Elseif($http_status -eq 200) {
Write-host "URL is up"
}
}Untill (status_wait -eq 3)
You are looking for something like this ?
#Remove-Variable * -ErrorAction SilentlyContinue
$url = "www.yourtargetsite.com"
function try-webrequest($url){
try{
return (Invoke-WebRequest -uri $url -ErrorAction Stop)
}catch{
return $_
}
}
$wr = try-webrequest $url
if ($wr.statuscode -ne 200){
If($wr.categoryinfo.reason -eq "webexception"){
$count = 1
while($count -lt 4){
"Retrying...$count"
$count++
if ((try-webrequest $url).statuscode -eq 200){break}
sleep -seconds 30
}
}else{
#In case you want to handle any other exception
"The link is down NOT due to webexception"
}
}else{
"The link's up"
}
PS: I have just started answering on stackoverflow. I'd love to hear your feedback.

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