Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevecharon/221d81e1c0aea2aa34c2ddf9ff36ecb9 to your computer and use it in GitHub Desktop.
Save stevecharon/221d81e1c0aea2aa34c2ddf9ff36ecb9 to your computer and use it in GitHub Desktop.
PowerShell script to validate Terminal Services certificates.
<#
.SYNOPSIS
PowerShell script to validate the served
certificate on a Windows Server is valid.
.DESCRIPTION
This script checks the validation of the served certificate
on one or more Windows Servers, and returns an array of PSCustomObjects summarizing the
validation state of the certificate of each server.
If the certificate is self-signed or signed by an internal/private CA,
you must ensure that the appropriate root CA certificates are imported into your
local machine certificate store.
The object returned has five properties: NodeName, Found, CertStatus, Validated, and NodeCert.
Below is a description of each.
* NodeName: This is the name of the node from the `-NodeList` parameter of this script.
* Found: This will be True if the certificate was located on the target
server or workstation, and False if not. If the certificate is not found, this can
indicate that you need to adjust the -CertPaths parameter, but may also mean that
the script failed to connect to the node in question. Use the -Verbose flag
to see remote invocation related errors in STDERR.
* CertStatus: If validation of the certificate succeeds, this will be 'Valid'. Otherwise,
the validation error message will be set here. It is recommended to use the Validated
property over CertStatus to determine if validation succeeded, and use CertStatus to
determine the reason for the failure. Will be $null if the cert was not found.
* Validated: This will be $True if validation succeeds, and $False if it fails. It is
recommended to use the Validated property over CertStatus to determine if validation succeeded,
and use CertStatus to determine the reason for the failure. WIll be $null if the cert was
not found.
* NodeCert: A certificate object representing the found certificate on the node. Will be
$null if the cert is not found.
.PARAMETER NodeList
(Optional) An array of server names or IP addresses. If omitted, 'localhost' will be used.
.PARAMETER Credential
(Optional) A PSCredential object which will be used to authenticate to each remote server.
If omitted, the current session credentials will be used.
.PARAMETER CertPaths
(Optional) An array of paths in which to search for certificates. By default, this script will
search in 'cert:\LocalMachine\My' and '"cert:\LocalMachine\Remote Desktop"'. You should only use
certificate store paths.
.PARAMETER UseSSL
(Optional) Use SSL (HTTPS Transport) when communicating with a server or workstation. This is
useful if, for example, you are feeding in a list of IPs instead of server names (IPs can only
be used over HTTPS.
Note that WinRM must be properly configured to process requests over HTTPS on the target workstation
or server for this to work (quickconfig'ing WinRM is not sufficient).
.EXAMPLE
Verify-ServerCertificate.ps1
Validates the Server certificate on the local workstation or server using the current
PowerShell session credentials.
.EXAMPLE
Verify-ServerCertificate.ps1 -NodeList $nodeList -Credential $credential
Validates the Server certificate on a list of remote servers or workstations.
.EXAMPLE
Verify-ServerCertificate.ps1 -CertPaths 'cert:\LocalMachine\My'
Validates the Server certificate on the local workstation or server, overriding the
certificate search path list to only search the Personal certificate store.
#>
# TODO: Make this accept pipeline input for the NodeList
[CmdletBinding()]
Param(
[string[]]$NodeList,
[PSCredential]$Credential,
[string[]]$CertPaths = @(
'cert:\LocalMachine\My',
'"cert:\LocalMachine\Remote Desktop"'
),
[switch]$UseSSL
)
function Remote-Command {
Param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$Command,
[PSCredential]$Credential,
[string[]]$ComputerName,
[object[]]$ArgumentList,
[switch]$UseSSL
)
$cmd = New-Object System.Text.StringBuilder( "Invoke-Command" )
if ( $Credential ) { [void]$cmd.Append( ' -Credential $Credential' ) }
if ( $ComputerName ) { [void]$cmd.Append( " -ComputerName {0}" -f ( $ComputerName -Join ',' ) ) }
if ( $ArgumentList ) { [void]$cmd.Append( " -ArgumentList {0}" -f ( $ArgumentList -Join ',' ) ) }
if ( $UseSSL ) { [void]$cmd.Append( " -UseSSL") }
[void]$cmd.Append( " -Command {{{0}}}" -f $Command )
Write-Verbose $( "Executing remote command:{1}{1}{0}" -f $cmd.ToString(), "`r`n" )
Invoke-Expression $cmd.ToString()
}
if ( -Not $NodeList ) {
Write-Verbose 'NodeList not specified, using localhost'
$NodeList = 'localhost'
}
Write-Verbose "The following certificate store paths will be checked:"
$CertPaths | Foreach-Object {
Write-Verbose $( " - {0}" -f $_ )
}
# Sort the node list
$SortedNodeList = $NodeList | Sort-Object
# Allow errors temporarily
$OldErrorActionPreference = $ErrorActionPreference
if ( $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent ) { $ErrorActionPreference = "Continue" }
else { $ErrorActionPreference = "SilentlyContinue" }
# Get the configured RDP cert for every node in the list and sort
Write-Verbose 'Retrieving Server certificate information from each node, this may take some time...'
$FoundCerts = Remote-Command -ComputerName $NodeList -Credential $Credential -ArgumentList $CertPaths -UseSSL:$UseSSL {
#should be adapted to reflect proper location for server cert instead of TS-Cert
$RdpThumbprint = ( wmic /namespace:\\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Get SSLCertificateSHA1Hash | Select-Object -Index 2 )
Get-ChildItem -Path $args | Where-Object { $_.Thumbprint.Trim().ToLower() -Match $RdpThumbprint.Trim().ToLower() }
}
# Reinstate the previous ErrorActionPreference
$ErrorActionPreference = $OldErrorActionPreference
# Compile and summarize the data for return
$NodeStatusList = New-Object System.Collections.Generic.List[PSCustomObject]
$SortedNodeList | Foreach-Object {
$NodeStatusHash = @{
"NodeName" = $_;
"Found" = $False;
"CertStatus" = $null;
"Validated" = $null;
"NodeCert" = $null;
}
# Check that the Remote-Command completed for this node
$NodeCert = $FoundCerts | Where-Object -Match -Property PSComputerName -Value $_
if ( $NodeCert ) {
Write-Verbose $( "Validating certificate for {0}" -f $_ )
$NodeStatusHash["Found"] = $True
$validate = Test-Certificate -Policy SSL -Cert $NodeCert[0]
if ( $validate ) {
$NodeStatusHash["CertStatus"] = "Valid"
$NodeStatusHash["Validated"] = $True
$NodeStatusHash["NodeCert"] = $NodeCert[0]
}
else {
Write-Verbose "Certificate validation failed!"
$NodeStatusHash["CertStatus"] = $Error[0].Exception
$NodeStatusHash["Validated"] = $False
}
} else {
Write-Verbose $( "Could not find certificate for {0}" -f $_ )
}
$NodeStatusList.Add( [PSCustomObject]$NodeStatusHash )
Write-Verbose "`r`n"
}
#We want PRTG compatible xml output here instead of the pscustomobject
#maybe use a function for that?
$NodeStatusList
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment