15

I am trying to work with our Load Balancer via Powershell 3.0 and a REST API. However I am currently getting a failure no matter what I try if it is an https request, whether to our load balancer or to any other https site. I feel like I'm missing something obvious.

Here is the code that fails with https

try
{
    #fails
    #$location='https://www.bing.com'
    #fails
    #$location='https://www.google.com'
    #fails
    #$location='https://www.facebook.com'
    #fails
    #$location='https://www.ebay.com'
    #works
    #$location='http://www.bing.com'
    #works
    #$location='http://www.google.com'
    #fails (looks like Facebook does a redirect to https://)
    $location='http://www.facebook.com'
    #works
    #$location='http://www.ebay.com'
    $response=''
    $response = Invoke-WebRequest -URI $location
    $response.StatusCode
    $response.Headers
}
catch
{
    Write-Host StatusCode $response.StatusCode
    Write-Host $_.Exception
}

The error I get is:

System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.Management.Automation.PSInvalidOperationException: 
There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspa
ce type. The script block you attempted to invoke was: $true
   at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult)
   at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
   --- End of inner exception stack trace ---
   at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
   at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()

I was hoping this page and the suggestions towards the bottom including the one from Aaron D.) would make a difference but none of them made a difference.

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

and

function Ignore-SSLCertificates
{
    $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider
    $Compiler = $Provider.CreateCompiler()
    $Params = New-Object System.CodeDom.Compiler.CompilerParameters
    $Params.GenerateExecutable = $false
    $Params.GenerateInMemory = $true
    $Params.IncludeDebugInformation = $false
    $Params.ReferencedAssemblies.Add("System.DLL") > $null
    $TASource=@'
    namespace Local.ToolkitExtensions.Net.CertificatePolicy
    {
        public class TrustAll : System.Net.ICertificatePolicy
        {
            public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem)
            {
                return true;
            }
        }
    }
'@ 
    $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
    $TAAssembly=$TAResults.CompiledAssembly
    ## We create an instance of TrustAll and attach it to the ServicePointManager
    $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
    [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll
}

and

add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

I have tried switching to Invoke-RestCommand but to no avail as I get the same response.

It feels like this has to be something environmental because I can't believe the above doesn't work for anyone else, but I've tried it on a workstation and on a server with the same results (doesn't rule out environment completely but I know they were set up differently).

Any thoughts?

Community
  • 1
  • 1
mapeterson42
  • 351
  • 1
  • 2
  • 7
  • OK, so it definitely seems to be something configuration related. This `Invoke-RestMethod -Uri "https://gdata.youtube.com/feeds/api/videos?v=2&q=PowerShell"` works on Windows Server 2012 with a PSVersion of 3 0 -1 -1 Does not work on Windows Server 2008 R2 with a PS Version of 3 0 -1 -1 And also does not work on Windows 8.1 with a version of 4 0 -1 -1 – mapeterson42 Aug 05 '14 at 19:19

5 Answers5

34

This worked perfectly for me. The site defaults to TLS 1.0 and apparently PS doesn't work with that. I used this line:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

My PS scripts (so far all I've tested) have worked perfectly.

Castrohenge
  • 8,525
  • 5
  • 39
  • 66
Daniel Peel
  • 537
  • 5
  • 11
  • @daniel-peel - This answer worked for me but could you please embed the relevant blog post text into the answer as the link is invalid. – Castrohenge Jan 11 '17 at 16:47
  • @Castrohenge if you google the link, you can view the cached version of the page... it was simply another blog post about someone looking for something similar. I tried it.. and it worked... so I was referencing where I found the information. The page is no longer apparently available. I do not want to paste the whole page on here.. as it was quite long. I created a pastebin of the page... good for month from today http://pastebin.com/LQp8hsfq... if you feel it should be posted here.. let me know – Daniel Peel Jan 11 '17 at 21:13
20

The answer is do not do this to solve the SSL issue:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

If you do this, your first https request will work (it seems), however subsequent ones will not. Additionaly at that point you need to close out of the Powershell ISE, and reopen it and then try again (without that line).

This is alluded to in a sentence here http://social.technet.microsoft.com/Forums/windowsserver/en-US/79958c6e-4763-4bd7-8b23-2c8dc5457131/sample-code-required-for-invokerestmethod-using-https-and-basic-authorisation?forum=winserverpowershell - "And all subsequent runs produce this error :", but it wasn't clear what the solution to reset was.

mapeterson42
  • 351
  • 1
  • 2
  • 7
  • This doesn't work for me in POSH5, Win10. On the first call (and all subsequent) after setting this, I get: `Invoke-WebRequest : The underlying connection was closed: An unexpected error occurred on a send.` – VertigoRay Nov 11 '16 at 17:57
  • 12
    Explicitly setting the protocol to TLS 1.2 worked for me: `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12` – Daniel Hutmacher May 26 '18 at 15:05
  • I can confirm. Setting `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}` sent me on a wild goose chase. Would not recommend. – el2iot2 Aug 09 '19 at 01:47
6

I too was plagued by this for a really long time. It even affected Visual Studio as VS loaded my $PROFILE into it's domain when running NuGet restore.

Seeing your comment above made me realize that I had a custom callback script because of one of our vendors shipped a product with an invalid CN in it's ssl cert.

Long story short, I replaced my script delegate with a compiled c# object (removing the script runspace from the equation).

(separate code block for C# highlighting)

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

public static class CustomCertificateValidationCallback {
    public static void Install() 
    {
        ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidationCallback.CheckValidationResult;
    }

    public static bool CheckValidationResult(
        object sender, 
        X509Certificate certificate, 
        X509Chain chain, 
        SslPolicyErrors sslPolicyErrors)
    {
        // please don't do this. do some real validation with explicit exceptions.
        return true;
    }
}

In Powershell:

Add-Type "" # C# Code
[CustomCertificateValidationCallback]::Install()
Brandon LeBlanc
  • 562
  • 8
  • 12
1

Consolidating and condensing some of the above learnings, I have adopted the following approach:

Syntax colored and commented like the C# of yore:

// Piggyback in System.Net namespace to avoid using statement(s)
namespace System.Net 
{
    // Static class to make the ps call easy
    // Uses a short name that is unlikely to clash with real stuff...YMMV
    public static class Util 
    {
        // Static method for a static class
        public static void Init() 
        {
            // [optionally] clear any cruft loaded into this static scope
            ServicePointManager.ServerCertificateValidationCallback = null;

            // Append a dangerously permissive validation callback
            // using lambda syntax for brevity.
            ServicePointManager.ServerCertificateValidationCallback += 
                (sender, cert, chain, errs) => true;

            // Tell SPM to try protocols that have a chance 
            // of working against modern servers.
            // Word on the street is that these will be tried from "most secure" 
            // to least secure. Some people add em all!
            ServicePointManager.SecurityProtocol = 
                SecurityProtocolType.Tls | 
                SecurityProtocolType.Tls11 | 
                SecurityProtocolType.Tls12;
        }
    }
}

And now the real powershell highlighted version (no comments, but the same code)

Add-Type -Language CSharp @"
namespace System.Net {
public static class Util {
public static void Init() {
ServicePointManager.ServerCertificateValidationCallback = null;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errs) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
}}}"@
[System.Net.Util]::Init()

Obviously you can remove irrelevant whitespace, but you should be able to drop that into your session, and then Invoke-WebRequest at will.

Note that the

# Do not use IMHO!
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

approach seems quite incorrect for ps 5.1 (where i have tested this). Not sure where it came from, but I wish I had avoided it and saved the heartache.

el2iot2
  • 6,428
  • 7
  • 38
  • 51
  • Hey, thank you for this "Do not use" comment as I was struggling with this certificate stuff in PowerShell and this is exactly why it didn't work for me. After changing it to pure C# like you suggested it started to work! – badsamaritan Mar 13 '22 at 19:32
1

The below powershell script works for me to check post web request

add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

$uri = "XXXX"
$person = @{grant_type= 'user_password'
username = 'XXXX'
password = 'XXX'
}
   $body = (ConvertTo-Json $person)
   $hdrs = @{}
   $hdrs.Add("XXXX","XXXX")


Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType 'application/json' -Headers $hdrs
Gautam Sharma
  • 851
  • 8
  • 13