Could not establish trust relationship for the SSL/TLS secure channel

Sometimes we run web services internally that don’t use a trusted SSL certificate. It’s not good practice, but in the real world this will be encountered.

In PowerShell, we often see this error come up when using Invoke-WebRequest or Invoke-RestMethod or even the [System.Net.WebClient] class. All of these rely on the .Net framework which is set up to validate SSL certificates, so an exception gets thrown when we try to connect to a site over SSL that isn’t trusted.

For a while, a lot of people created a class that implemented the ICertificatePolicy interface, provided a method that always returned true, and then set System.Net.ServicePointManager.CertificatePolicy to an instance of the new class. This is also the method I have used for a while, and in PowerShell it didn’t seem to complain. But I’ve recently found that this method is in fact deprecated.

It seems the correct way is to set the ServicePointManager.ServerCertificateValidationCallback property to a callback function. Ok, no problem. It’s almost the same thing.

Writing a Module

I started to think about this more while writing a module that I planned to use internally, primarily against a web service that we run with an invalid certificate. But I didn’t like the idea of turning off SSL validation for the entire PowerShell session, which could affect other calls made by the scripts using the modules (without it being obvious that SSL validation was being skipped), so I sought a solution where I could set and unset the validation at will, and preserve whatever existing setting was in place.

The idea was to export module functions that include a switch parameter like -SkipSslValidation and the function could turn off validation, make its calls, then restore validation (or whatever the previous value was).

I started with this answer from Dan Hunex, which allows for the override, and modified it a bit so that it keeps track of the previous value and lets you restore it just as easily.

Here is the class definition in C#:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;

    public static class SSLValidator
    {
        private static Stack<RemoteCertificateValidationCallback> funcs = new Stack<RemoteCertificateValidationCallback>();

        private static bool OnValidateCertificate(object sender, X509Certificate certificate, X509Chain chain,
                                                    SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }

        public static void OverrideValidation()
        {
            funcs.Push(ServicePointManager.ServerCertificateValidationCallback);
            ServicePointManager.ServerCertificateValidationCallback =
                OnValidateCertificate;
        }

        public static void RestoreValidation()
        {
            if (funcs.Count > 0) {
                ServicePointManager.ServerCertificateValidationCallback = funcs.Pop();
            }
        }
    }

Now when you call OverrideValidation() it stores the existing value in a stack. When you call RestoreValidation() it simply pops the last value off the stack and sets the callback again. Using a stack makes it easier to nest calls in the client code without worrying about whether you’re restoring validation when a previous call expects it to be off.

Using it in PowerShell

Adding a C# class definition is very straightforward in PowerShell using the Add-Type cmdlet, so in this example, $definition is a string consisting of the above code:

1
Add-Type $definiton

To use the new object:

1
2
3
4
5
Invoke-WebRequest https://badsslhost -UseBasicParsing # fail
[SSLValidator]::OverrideValidation()
Invoke-WebRequest https://badsslhost -UseBasicParsing # success
[SSLValidator]::RestoreValidation()
Invoke-WebRequest https://badsslhost -UseBasicParsing # fail

And in the context I was describing (a function):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Get-SomeResource {
[CmdletBinding()]
param(
    [System.Uri]
    $Uri ,

    [Switch]
    $IgnoreSslValidation
)
    try {
        if ($IgnoreSslValidation) {
            [SSLValidator]::OverrideValidation()
        }

        Invoke-WebRequest -Uri $Uri -UseBasicParsing

    } finally {
        if ($IgnoreSslValidation) {
            [SSLValidator]::RestoreValidation()
        }
    }
}

Test for Verbose in Powershell

I was looking for a way to determine whether I was in Verbose mode in Powershell. My web searches came up with various solutions that all suffer from problems. Most of them use $PSBoundParameters. The most obvious problem with this is that it only works when -Verbose was called directly on the script or function whose context you’re currently in. Since the Verbose state is inherited by child scopes, this is less than ideal.
[Continue reading]

Splatting with $PSBoundParameters and Default Values for Optional Parameters

Splatting is a really great feature in PowerShell that lets you take a hash of parameters and call a function or cmdlet with the hash instead of having to type out every parameter name and value. It seems natural then that if you're writing a wrapper … [Continue reading]

DNS Manager Can’t Set TXT Record to Expire

dns-txt-aging-static

I finally got DNS scavenging configured in our environment. It's working great, no big disasters. I was browsing the DNS manager (so much faster now that we got rid of those 6,000+ resource records we didn't need) and I noticed 2 TXT records that I … [Continue reading]

Get-DnsServerResourceRecord returns duplicate records when a sub-domain matching the zone exists

While writing a PowerShell script to fix some DNS records, I came across some very strange behavior. I was calling [cci theme="default" lang="powershell"]Get-DnsServerResourceRecord[/cci] and it was returning duplicate results for every record. One … [Continue reading]

Get Progress on DFS Replication Database Cloning Import

DFSR-Import-Progress

As I wrote in a previous post, I began using the new DFS Replication database cloning technique to speed up initial sync. Thanks to Ned Pyle's great How-To on the subject, I was able to tell exactly which events to look for in the event log to get an … [Continue reading]