There is an updated version of this script here.
In my opinion, all customers should be encrypting connections to their SQL Servers to secure data in-transit, but that’s a discussion for another day. Today, I wanted to discuss how you can use PowerShell to lookup details on the certificate being used for that encryption.
For this example, we have three SQL Server instances each configured slightly different for encrypted communication. Using the SQL Server Configuration Manager, we can see the first instance is configured to use a certificate named “deathstar.govlab.corp (SHA256)”.
The FORCE ENCRYPTION option is set to YES to make it mandatory for all connections to use this certificate.
You’ll then need to use the Certificates manager (via MMC snap-in) to see important details about the certificate such as the expiration date.
Since the we are using encryption, we need to verify the SQL Server service account has been granted READ permission on the certificate’s private key. Without it, the service will fail to startup.
Now that’s just the details for the first instance. We would need to repeat those verification steps for the other two instances. This is where PowerShell can be extremely helpful; one script to grab those details for every instance on the server.
If we zoom into the SQL2019 instance, we can see all the details in one easy to read place. The top rows (outlined in red) show how SQL Server is configured. The middle rows (outlined in yellow) show the details of the certificate such as the name, thumbprint, and expiration date. The bottom rows (outlined in green) show the SQL Server service account and the permissions granted on the private key.
For the remaining instances, you can quickly see both are configured differently. The default instance does not have the FORCE ENCRYPTION option turned on, and the service account has been granted FULLCONTROL instead of the minimum requirement of READ.
The detail for the SQL2017 instance is blank which means encryption has not been configured yet.
To retrieve the permissions on the private key using PowerShell, you will need to open the PowerShell window as administrator. If not, the script will still run but the PrivateKeyPermmission field will be blank.
This is just another reason I think all DBAs should be using PowerShell for their everyday administration tasks. The entire PowerShell script is below. Feel free to modify it as you see fit for your environment.
#Define variables [string]$hostname = $env:COMPUTERNAME [bool]$isEncrypted = $false [bool]$isForceEncryption = $false #Get a list of all SQL instances $instanceNames = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server").InstalledInstances #Loop through each instance foreach ($instance in $instanceNames) { if ($instance -eq "MSSQLSERVER") { $sqlServer = $hostname $regPath = $instance } else { $sqlServer = "$hostname`\$instance" $regPath = "MSSQL`$$($instance)" } #Reset variables before checking the next instance [bool]$isEncrypted = $false [bool]$isForceEncryption = $false [string]$thumbprint = $Null $certificate = $null $pvkAcl = $Null #Pull the SQL certificate info from the registry [string]$instanceId = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance).$instance [string]$thumbprint = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server\$instanceId\MSSQLServer\SuperSocketNetLib" -Name Certificate).Certificate #Only run this section if there is a certificate in use if($thumbprint){ [bool]$isEncrypted = $true try { [bool]$isForceEncryption = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server\$instanceId\MSSQLServer\SuperSocketNetLib" -Name ForceEncryption).ForceEncryption $certificate = Get-ChildItem CERT:\LocalMachine\My | Where-Object Thumbprint -eq $thumbprint } catch { throw $_ } #Get the ACLs on the Private Key (This requires PowerShell to be run as Administrator) try { $serviceAccount = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\$($regPath)").ObjectName $acl = (Get-Acl "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$(([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)).Key.UniqueName)").Access $pvkAcl = $acl | Where-Object {$_.IdentityReference -eq "$($serviceAccount)"} } catch { #If PowerShell is not running as Administrator, then we need to catch the error but then continue on $pvkAcl = $Null } } #Output the results [PSCustomObject]@{ Hostname = $hostname SqlServer = $sqlServer IsEncrypted = $isEncrypted IsForceEncryption = $isForceEncryption Subject = $certificate.Subject FriendlyName = $certificate.FriendlyName Thumbprint = $thumbprint IssueDate = $certificate.NotBefore ExpirationDate = $certificate.NotAfter ServiceAccount = $serviceAccount PrivateKeyPermission = $(if($pvkAcl) {"$($pvkAcl.IdentityReference) | $($pvkAcl.AccessControlType) | $($pvkAcl.FileSystemRights)"} else { $Null }) } }
The link below outlines the steps needed to encrypt data across communication channels, including the requirements for creating a certificate.
How can this be done on a remote server? This seems oriented towards localhost. Can it be done on a remote server without PowerShell Remoting?
It could be adapted using Invoke-Command, but that still requires PowerShell remoting to be enabled and appropriate firewall ports opened.
Check out the updated version and see if it fits your needs.
https://www.patrickkeisler.com/2023/11/get-certificate-details-for-sql-encryption-in-transit-using-powershell-updated/
to run the script remotly:
$HostAddress = “ServernName”
$applicationCredentialUserName = “username”
$applicationCredentialPassword = “password”
$applicationCredentialPassword2 = ConvertTo-SecureString $applicationCredentialPassword -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($applicationCredentialUserName, $applicationCredentialPassword2)
$Session = New-PSSession -ComputerName $HostAddress -Credential $Credential
# Define the script block to run remotely
$ScriptBlock = {
# Paste the content of your original script here
# Define variables
[string]$hostname = $env:COMPUTERNAME
[bool]$isEncrypted = $false
[bool]$isForceEncryption = $false
# … (rest of your script)
}
# Run the script block remotely
$RemoteResults = Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
# Display or process the results obtained remotely
$RemoteResults | Format-Table -AutoSize
Awesome script 🙂
Did you ever worked on a certificate rotation script ?
Like, scan for a cert in the store with friendlyname with a higher yearnumber at the end of the friendlyname or an expirationdate later than the current cert?
No I have not, but I think it wouldn’t be that difficult since you can read the certificate details like friendly name and expiry date.
Updated version that may interest you.
https://www.patrickkeisler.com/2023/11/get-certificate-details-for-sql-encryption-in-transit-using-powershell-updated/
Paul and Patrick Keisler here is what I am going to do Patrick’s excellent script.
07-Nov-2023 11:54 PM
Assuming you you have Server Admin righs perform the below where computername is the name of the remote server.
enter-pssession computername
paste his script end hit enter
I am going to write a Master PS Script to gather this information on Multiple Servers putting the results in a CSV file, which in turn can be put into an Excel file or imported into a database table.
I will get back to you with the PS code for same
Check out the udpated version and see if it fits your needs.
https://www.patrickkeisler.com/2023/11/get-certificate-details-for-sql-encryption-in-transit-using-powershell-updated/