There seems to be very little useful information on this topic, let alone for PowerShell 5.1 and 7.1 (.NET Core). Hopefully this helps someone.
I've adapted this from Vadims Podāns 'Retrieve CNG key container name and unique name'
Please note:
- Example use of Cmdlet:
Get-Item 'Cert:\LocalMachine\My\FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' | Get-CertificateContainerAbsolutePath
- I cheated and just searched for key file recursively under Microsoft Crypto directory hierarchy instead of searching correct specific sub-directories
- Despite use of CNG Provider, this seems to work against a test CryptoAPI provided key I created
- I tested this on Powershell 5.1 and 7.1 on Windows.
if(-not ('EmbeddedTemporary.PKITools' -as [type])) {
$PKIToolsDefinition = @"
[DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CertGetCertificateContextProperty(
IntPtr pCertContext,
uint dwPropId,
IntPtr pvData,
ref uint pcbData
);
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct CRYPT_KEY_PROV_INFO {
[MarshalAs(UnmanagedType.LPWStr)]
public string pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptOpenStorageProvider(
ref IntPtr phProvider,
[MarshalAs(UnmanagedType.LPWStr)]
string pszProviderName,
uint dwFlags
);
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptOpenKey(
IntPtr hProvider,
ref IntPtr phKey,
[MarshalAs(UnmanagedType.LPWStr)]
string pszKeyName,
uint dwLegacyKeySpec,
uint dwFlags
);
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptGetProperty(
IntPtr hObject,
[MarshalAs(UnmanagedType.LPWStr)]
string pszProperty,
byte[] pbOutput,
int cbOutput,
ref int pcbResult,
int dwFlags
);
[DllImport("ncrypt.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int NCryptFreeObject(
IntPtr hObject
);
"@
Add-Type -MemberDefinition $PKIToolsDefinition -Namespace 'EmbeddedTemporary' -Name 'PKITools'
}
Function Get-CertificateContainerAbsolutePath {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ValuefromPipeline)]
$Certificate
)
$CERT_KEY_PROV_INFO_PROP_ID = 0x2 # from Wincrypt.h header file
$pcbData = 0
[EmbeddedTemporary.PKITools]::CertGetCertificateContextProperty($Certificate.Handle, $CERT_KEY_PROV_INFO_PROP_ID, [IntPtr]::Zero, [ref]$pcbData) | Out-Null
$pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal($pcbData)
[EmbeddedTemporary.PKITools]::CertGetCertificateContextProperty($Certificate.Handle, $CERT_KEY_PROV_INFO_PROP_ID, $pvData, [ref]$pcbData) | Out-Null
$keyProv = [Runtime.InteropServices.Marshal]::PtrToStructure($pvData, [type][EmbeddedTemporary.PKITools+CRYPT_KEY_PROV_INFO])
[Runtime.InteropServices.Marshal]::FreeHGlobal($pvData) | Out-Null
$CngProvider = [System.Security.Cryptography.CngProvider]::new($keyProv.pwszProvName)
$CngKey = [System.Security.Cryptography.CngKey]::Open($keyProv.pwszContainerName, $CngProvider, [System.Security.Cryptography.CngKeyOpenOptions]::MachineKey)
$UniqueName = $CngKey.UniqueName
$CngKey.Dispose()
$Certificates = Get-ChildItem -Path (Join-Path -Path $Env:ProgramData -ChildPath 'Microsoft\Crypto') -Recurse -Filter $UniqueName
$Certificates.FullName
}