Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Created October 20, 2025 07:33
Show Gist options
  • Select an option

  • Save PanosGreg/63b704adfb65c6c40ae349021215ed45 to your computer and use it in GitHub Desktop.

Select an option

Save PanosGreg/63b704adfb65c6c40ae349021215ed45 to your computer and use it in GitHub Desktop.
Functions to Get/Delete/Import a certificate from/on a windows service
# This file contains the following functions:
# Import-ServiceCertificate - Import a PFX certificate to a service cert store
# Get-ServiceCertificate - Get the certificates of a service
# Remove-ServiceCertificate - Delete a certificate from a service
#Requires -RunAsAdministrator
function Import-ServiceCertificate {
<#
.SYNOPSIS
Import a PFX certificate to the cert store of a windows service
.EXAMPLE
Import-ServiceCertificate -PfxPath C:\temp\my.pfx -PfxPass 123qwe -Service tapisrv
#>
[CmdletBinding()]
[OutputType([void])]
param (
[Parameter(Mandatory, HelpMessage = 'The path to the PFX Certificate File.')]
[ValidateScript({[Security.Cryptography.X509Certificates.X509Certificate2]::GetCertContentType($_)})]
[Alias('Path')]
[string]$PfxPath,
[Parameter(Mandatory, HelpMessage = 'The password of the PFX certificate.')]
[Alias('Password')]
[string]$PfxPass,
[Parameter(Mandatory, HelpMessage = 'The service that the certificate will be installed on.')]
[ValidateScript({(Get-Service -Name $_) -as [bool]})]
[Alias('Name')]
[string]$Service
)
$ErrorActionPreference = 'Stop'
# create a temporary certificate store to put the service certificate (this is done on the Cert PSDrive)
$TempStore = 'Cert:\LocalMachine\Temp' # <-- can only create custom stores in LocalMachine, not CurrentUser
if (-not (Test-Path $TempStore)) {New-Item -Path $TempStore -Verbose:$false | Out-Null}
# import the cert into the temp store (this is done on the Cert PSDrive)
$Pass = $PfxPass | ConvertTo-SecureString -AsPlainText -Force
$Cert = Import-PfxCertificate -File $PfxPath -Pass $Pass -CertStore $TempStore -Export -Verbose:$false
# copy the cert from the temp store to the service store (this is done on the Registry)
$Thumbprint = $Cert.Thumbprint
$Source = "HKLM\Software\Microsoft\SystemCertificates\Temp\Certificates\$Thumbprint"
$Destin = "HKLM\SOFTWARE\Microsoft\Cryptography\Services\$Service\SystemCertificates\My\Certificates\$Thumbprint"
reg copy $Source $Destin /s /f 2>&1 | where {$_ -is [Management.Automation.ErrorRecord]} | Write-Error -EA Stop
# clean up the temp store, delete the certs within, but leave the temp store (this is done on the PSDrive)
$Cert | Remove-Item -Verbose:$false -Force
}
function Get-ServiceCertificate {
<#
.SYNOPSIS
Gets the certificates of a windows service
.EXAMPLE
Get-ServiceCertificate -Name tapisrv
#>
[cmdletbinding()]
[OutputType([psobject])]
param (
[Parameter(Mandatory, HelpMessage = 'The service which we''ll get the certificates from.')]
[ValidateScript({(Get-Service -Name $_) -as [bool]})]
[Alias('Name')]
[string]$Service
)
$ErrorActionPreference = 'Stop'
# get all the certificate thumbprints of that service (this is done on the Registry)
$RegPath = 'HKLM:\SOFTWARE\Microsoft\Cryptography\Services\*\SystemCertificates\My\Certificates\'
$RegFilter = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Services\$Service\SystemCertificates\My\Certificates\*"
$SvcThumbs = (Get-ChildItem -Path $RegPath -Recurse | where Name -like $RegFilter).PSChildName
# exit if we did not find any certificates for that service
if (([array]$SvcThumbs).Count -eq 0) {
Write-Verbose "No certificates found for the $Service service"
return
}
# create a temporary certificate store to put the service certificate (this is done on the Cert PSDrive)
$TempStore = 'Cert:\LocalMachine\Temp' # <-- can only create custom stores in LocalMachine, not CurrentUser
if (-not (Test-Path $TempStore)) {New-Item -Path $TempStore -Verbose:$false | Out-Null}
# copy the cert from the service store into the temp store (this is done on the Registry)
# and then get the certificate object (this is done on the PSDrive)
$CertList = foreach ($Thumbprint in $SvcThumbs) {
$Source = "HKLM\SOFTWARE\Microsoft\Cryptography\Services\$Service\SystemCertificates\My\Certificates\$Thumbprint"
$Destin = "HKLM\Software\Microsoft\SystemCertificates\Temp\Certificates\$Thumbprint"
reg copy $Source $Destin /s /f 2>&1 | where {$_ -is [Management.Automation.ErrorRecord]} | Write-Error -EA Stop
Get-ChildItem $TempStore | where Thumbprint -eq $Thumbprint
}
# assemble the output object
$out = $CertList | foreach {
if ($_.Subject -match 'CN=') {$Name = (@($_.Subject.Split(',')) -match 'CN=')[0] -replace 'CN=',$null}
else {$Name = $_.Subject}
[pscustomobject]@{
PSTypeName = 'Service.Certificate'
ComputerName = $env:COMPUTERNAME
ServiceName = $Service # <-- [string] the service name, not the sevice display name
ServiceName2 = (Get-Service -Name $Service).DisplayName
Thumbprint = $_.Thumbprint
Name = $Name # <-- [string] the CN part from the certificate's subject name
ExpiresAt = $_.NotAfter # <-- [datetime]
Certificate = $_ # <-- [Security.Cryptography.X509Certificates.X509Certificate2]
}
}
# clean up the temp store (delete the certs within, but leave the temp store)
$CertList | Remove-Item -Verbose:$false -Force
# finally return the output
Write-Output $out
}
function Remove-ServiceCertificate {
<#
.SYNOPSIS
Delete a certificate from the cert store of a windows service
.EXAMPLE
Remove-ServiceCertificate -Name tapisrv -Thumbprint 621A5BBE9CC5F9FE4337D37D8B859DB0A0E1E293
.EXAMPLE
Remove-ServiceCertificate -Name tapisrv
It deletes all the certificates from the tapisrv service
#>
[cmdletbinding()]
[OutputType([void])]
param (
[Parameter(Mandatory, HelpMessage = 'The service which we''ll delete the certificates from.')]
[ValidateScript({(Get-Service -Name $_) -as [bool]})]
[Alias('Name')]
[string]$Service,
[ValidatePattern('^[A-F0-9]{40}$')]
[string[]]$Thumbprint
)
$ErrorActionPreference = 'Stop'
# get all the certificate thumbprints of that service (this is done on the Registry)
if (-not ($PSBoundParameters.ContainsKey('Thumbprint'))) {
$RegPath = 'HKLM:\SOFTWARE\Microsoft\Cryptography\Services\*\SystemCertificates\My\Certificates\'
$RegFilter = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Services\$Service\SystemCertificates\My\Certificates\*"
$RegKeys = Get-ChildItem -Path $RegPath -Recurse | where Name -like $RegFilter
if ($RegKeys) {$Thumbprint = $RegKeys.PSChildName}
else {return}
}
# delete the certificates from the service cert store (this is done on the Registry)
foreach ($thumb in $Thumbprint) {
$Path = "HKLM:\SOFTWARE\Microsoft\Cryptography\Services\$Service\SystemCertificates\My\Certificates\$thumb"
Remove-Item -Path $Path -Recurse -Force -Verbose:$false
}
}
## Notes about -match
# The -match operator is both a comparison operator and an array-filter operator, depending on its input object.
# If you pass a single string, it returns a boolean. But if it's an array of strings,
# it returns all the elements of the array that match the pattern.
# If you don't know whether you'll have an array of strings or a single string.
# Enclose the input item with the array syntax @() to ensure that you still pass an array to the -match operator
# Otherwise, if it's single string you'll get back $True or $False instead of the item if it matches.
# Further on, using the -match operator with an array input, it will always return an array
# even if it has a single item in it.
# So if you then need to select the 1st item only, you can use an index.
# By comparison if you use the Where-Object to filter an array of strings and get a single string
# it will be unboxed and thus the index will return a character from the string
## About the Temporary Certificate Store
# You can only create a custom store under the LocalMachine, it cannot be done in CurrentUser
# That is why you need to have admin rights to run the Get-ServiceCertificate function.
## About the reg.exe CMD command
# The reason why we're using the reg.exe command instead of the PowerShell native Copy-Item
# is because the reg.exe will also create any necessary registry folders if not there.
# While the Copy-Item will just error out. As-in the following would error out:
# Copy-Item -Path $Source -Destination $Destin -Recurse -Verbose:$false
# Which then means we would first need to check if the registry folder is there,
# create one if not there, and then copy the registry key with the Copy-Item.
# So by using the reg.exe we're saving a few lines of code, and simplify our script.
## About NTFS permissions
# These commands do not take into account any potential changes that might be needed on the
# actual certificate files. As-in if the windows service is running under a specific account
# and that account is not a local admin, then you would need to grant access of the cert files
# to that account on the file system level. These commands do not do that, to keep things simple.
## About argument completion for the service parameter
# The service parameter can take only a specific value, which is any windows service name of the
# local computer. I could've added an argument completer to create a list of those service names
# or a custom validation attribute for the same. But I've chosen to ommit that to keep the code
# simple.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment