Update
04/09/2025
Microsoft have finally updated their documentation and now provide a script to remove the required key.
Script can be found here https://microsoft.github.io/CSS-Exchange/Hybrid/ConfigureExchangeHybridApplication/ its always recommended to download the latest version.
The command you will want to run is as follows.
.\ConfigureExchangeHybridApplication.ps1 -ResetFirstPartyServicePrincipalKeyCredentials The problem
Picture the scene, Microsoft finally allow you to remove your last exchange server from your on premise environment and keep Extra Connect synchronising your user identities, you are most of the way through the steps and you get to the “Permanently shutting down your last Exchange Server” section, specifically step 5 where you have to remove the remove the service principal credentials created for OAuth and the commands just wont work.
The guide I’m referring too can be found here and the powershell commands in question are these…
$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
$oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
$certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
$certBytes = $oAuthCert.Export($certType)
$credValue = [System.Convert]::ToBase64String($certBytes)
Import-Module Microsoft.Graph.Applications
Connect-MgGraph -Scopes "Application.Read.All"
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MgServicePrincipalByAppId -AppId $ServiceName
$keyId = (Get-MgServicePrincipal -ServicePrincipalId $p.Id).KeyCredentials $true | Where-Object {$_.Value -eq $credValue}).KeyId The errors will start to be thrown when line 11 is executed. This line has a mix of the older MSOnline and the newer Graph cmdlets. The Graph cmdlet doesn’t have a property called ‘Value’, nor did it ever have property named that.
It seems someone or maybe event AI has tried to convert the commands over to the Graph Module and done it without testing.
For the curious the previous code was as follows…
$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
$oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
$certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
$certBytes = $oAuthCert.Export($certType)
$credValue = [System.Convert]::ToBase64String($certBytes)
Import-Module -Name MSOnline
Connect-MsolService
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
$keyId = (Get-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -ReturnKeyValues $true | ?{$_.Value -eq $credValue}).KeyId Pretty similar hey? So you can see where this error has come from.
So what should the code be now?
For that we have to find another property to match against. In this case we have the ‘customKeyIdentifier’ property. Now this property can be encoded to base64 which gives us the certificate thumbprint. This simplifies things slightly as we can get the thumbprint in one line of code.
We then need to get the apps service principal though the Graph Module and loop through all the keys to see which matches the thumbprint.
You might have multiple keys matching the thumbprint and should ensure that nothing is relying on them in your production environment before removing them, you have been warned!
Also the Remove-MgServicePrincipalKey requires a JWT signed by the private key we are retrying to remove. Firstly, you might not have that private key, secondly I have no worked out how to create that JWT yet (If you know, help me out please by letting me know in the comments), so we need to work around that by using the Update-MqServicePrincipal cmdlet instead.
Please remember the below code provided as is, use at own risk.
// 5a
$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
// 5b / 5c
Import-Module Microsoft.Graph.Applications
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MgServicePrincipalByAppId -AppId $ServiceName
$keycredentials = @()
Write-Host "Current Key Credentials"
$p.KeyCredentials
foreach ($key in $p.KeyCredentials) {
if ($key.customKeyIdentifier -and ([System.Convert]::ToBase64String($key.customKeyIdentifier) -eq $thumbprint)) {
Write-Host "Match - Removing key with KeyId: $($key.keyId)"
}
else
{
Write-Host "Keeping key with KeyId: $($key.keyId)"
$keycredentials += $key
}
}
Update-MgServicePrincipal -ServicePrincipalId $p.Id -KeyCredentials $keycredentials If you know you want to remove all keys we can use the following code instead
// 5b / 5c
Import-Module Microsoft.Graph.Applications
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MgServicePrincipalByAppId -AppId $ServiceName
$keycredentials = @()
Update-MgServicePrincipal -ServicePrincipalId $p.Id -KeyCredentials $keycredentials Conclusion
Hopefully this blog post will be rendered useless when Microsoft finally update the documentation correctly. Until then I hope this allows you to continue with your last exchange server decommission.
Let me know in the comments if this has helped your organisation.