Powershell in a Post-TLS1.1 World

I was trying to install PowerCLI on a new server in a new environment today and I encountered all sorts of error messages when PowerShell tried to install the required NuGet provider:

PS C:\Windows\system32> Find-Module -Name VMware.PowerCLI
WARNING: Unable to download from URI 'https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409' to ''.
WARNING: Unable to download the list of available providers. Check your internet connection.
PackageManagement\Install-PackageProvider : No match was found for the specified search criteria for the provider 'NuGet'. The package provider 
requires 'PackageManagement' and 'Provider' tags. Please check if the specified package has the tags.
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\\PSModule.psm1:7405 char:21
+ ... $null = PackageManagement\Install-PackageProvider -Name $script:N ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (Microsoft.Power...PackageProvider:InstallPackageProvider) [Install-PackageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackageProvider

PackageManagement\Import-PackageProvider : No match was found for the specified search criteria and provider name 'NuGet'. Try 
'Get-PackageProvider -ListAvailable' to see if the provider exists on the system.
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\\PSModule.psm1:7411 char:21
+ ... $null = PackageManagement\Import-PackageProvider -Name $script:Nu ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (NuGet:String) [Import-PackageProvider], Exception
+ FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.ImportPackageProvider

WARNING: Unable to download from URI 'https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409' to ''.
WARNING: Unable to download the list of available providers. Check your internet connection.
PackageManagement\Get-PackageProvider : Unable to find package provider 'NuGet'. It may not be imported yet. Try 'Get-PackageProvider 
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\\PSModule.psm1:7415 char:30
+ ... tProvider = PackageManagement\Get-PackageProvider -Name $script:NuGet ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Microsoft.Power...PackageProvider:GetPackageProvider) [Get-PackageProvider], Exception
+ FullyQualifiedErrorId : UnknownProviderFromActivatedList,Microsoft.PowerShell.PackageManagement.Cmdlets.GetPackageProvider

Find-Module : NuGet provider is required to interact with NuGet-based repositories. Please ensure that '' or newer version of NuGet 
provider is installed.
At line:1 char:1
+ Find-Module -Name VMware.PowerCLI
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Find-Module], InvalidOperationException
+ FullyQualifiedErrorId : CouldNotInstallNuGetProvider,Find-Module

I made it very angry, and I didn’t know why! After some searching, I stumbled on a solution on the Microsoft Community site. The issue is that PowerShell 5.1 defaults to only enabling SSL3 and TLS 1.0 for secure HTTP connections. You have probably noticed a lot of recent warnings on various websites about services removing support for TLS 1.0 and 1.1, and SSL3 has been disabled for many for years. Microsoft is no slacker here, and go.microsoft.com has dropped support for SSL3 and TLS 1.0 (probably TLS 1.1, too, but I didn’t check). Thus the Provider list at the URL cannot be accessed and the NuGet install fails.

PS C:\ProgramData\Documents> [Net.ServicePointManager]::SecurityProtocol
Ssl3, Tls

You can fix this by specifying Tls12 as the SecurityProtocol, but it only persists in this session, for this user. Thankfully, PowerShell has a well documented series of profile loads, so you can make the change once for all users on the server. You can choose whichever level works best for you. I chose $PsHome\Profile.ps1 which affects All Users, All Hosts. If you choose a global file like that, launch a PowerShell session as administrator (if you weren’t aware, there’s a Ctrl-modifier to avoid right-clicking!) so that you have the rights to edit the target file. If not, just substitute the file below with your choice.

This snippet will check for the existence of the file and create it if needed, then populate it with our one line change and comment telling us why. Finally, it opens the file so you can inspect it and adjust if you need to. Note that running it again will append the same lines, which isn’t harmful but may result in a little confusion for the next person to peek at it. Hello, future self!

$ProfileFile = "${PsHome}\Profile.ps1"

if (! (Test-Path $ProfileFile)) {
New-Item -Path $ProfileFile -Type file -Force
''                                                                                | Out-File -FilePath $ProfileFile -Encoding ascii -Append
'# It is 2018, SSL3 and TLS 1.0 are no good anymore'                              | Out-File -FilePath $ProfileFile -Encoding ascii -Append
'[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12' | Out-File -FilePath $ProfileFile -Encoding ascii -Append

notepad $ProfileFile

If you enter [Net.ServicePointManager]::SecurityProtocol in the current window, you’ll get the same Ssl3, Tls result you saw before. The profile is only loaded at startup. Open a new powershell instance on the server – as any user, even – and run it again. You should see the new setting:

PS C:\windows\system32> [Net.ServicePointManager]::SecurityProtocol

Now you are ready to use PowerShell to connect to modern web servers, whether it’s to install NuGet, use Invoke-WebRequest, or any other HTTPS connection. Enjoy!