I have a C# program which is installed on Windows Server 2016, and connects to Exchange Online via MS Graph. However, the program stops working for several hours whenever the server is rebooted. Having set up a Powershell script to investigate the problem (and do the same thing as the C# program), it seems that the error is caused by the Security Protocol resetting when the server is rebooted - under normal circumstances it defaults to TLS1.2, but following a reboot it defaults to "Ssl3, Tls".
MS Graph currently requires the use of TLS 1.2 (Microsoft Graph Service: Retiring TLS 1.0 and 1.1 and preparing for TLS 1.2 in US Gov Cloud).
Microsoft best practices recommend that .NET applications should NOT hard code any particular version of TLS. Instead, .NET applications should use whichever TLS version the client operating system supports – this means that the application will automatically take advantage of newer versions of TLS as they become available (Transport Layer Security (TLS) best practices with the .NET Framework).
So how do I get the server operating system to continue to default to TLS1.2 following server reboots? (If I'm not supposed to hard-code it into the C# program). ...
The C# program gives the following error when it is run after the server is rebooted:
System.Net.HttpRequestException: An error occurred while sending the request. -->
System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. -->
System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
The following is the relevant section of the Powershell script that does the same thing as the C# program.
function Get-AccessToken
{
param(
[string] $AppId,
[string] $TenantName,
[string] $AppSecret)
$Scope = "https://graph.microsoft.com/.default"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Create body
$Body = @{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'client_credentials'
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
Body = $Body
Uri = $Url
}
# Request the token!
$Request = Invoke-RestMethod @PostSplat
return $Request
}
#ClientID = "..<redacted>.."
#DirectoryID = "..<redacted>.."
#ClientSecret = "..<redacted>.."
#MailboxName = "..<redacted>.."
#MailboxFolderName = "Inbox"
[Net.ServicePointManager]::SecurityProtocol
Write-Output "Get-AccessToken"
$Credential = Get-AccessToken -AppId $ClientID - TenantName $DirectoryID -AppSecret $ClientSecret
Normally, the Powershell script produces the following output:
Tls12
Get-AccessToken
For several hours following a server reboot, the Powershell script produces the following output:
Ssl3, Tls
Get-AccessToken
Invoke-RestMethod : The underlying connection was closed: An unexpected error occurred on a send.
At C:\Path\TestMSGraphGetEmails.ps1:37 char:16
+ $Request = Invoke-RestMethod @PostSplat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
As per Microsoft best practices (described above), the C# program and the Powershell script do not specify a version of TLS. They're running on Windows Server 2016 (which supports TLS1.2), with version 4.8 of the .NET framework installed (which uses TLS1.2 by default).
So how do I get the server operating system to continue to default to TLS1.2 following server reboots, like it normally does?