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

    using System.Collections.Generic;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;

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

        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:

Add-Type $definiton 

To use the new object:

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

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