Resolving the missing Microsoft Teams Calendar tab for users hosted on an On-Premises Exchange Server requires establishing a secure OAuth trust and configuring Intra-Organization connectors between your local environment and Microsoft 365. When mailboxes reside on-premises, the Teams application (which operates entirely in the cloud) cannot natively pull calendar data without explicitly granted permissions to query your local Exchange Web Services (EWS).
Prerequisites for Teams Calendar Integration
- Exchange Server 2016 or newer.
- An active Azure subscription.
- An administrative account assigned the Global Admin role in your Microsoft 365 tenant.
Phase 1: Environment Preparation and Authentication
All subsequent steps must be executed directly on an Exchange Server that meets the prerequisites. You will need to interact with both the local Exchange Management Shell (EMS) and standard Windows PowerShell to bridge the on-premises and cloud environments.
First, launch a standard PowerShell session as an Administrator. You must connect to the Microsoft Graph service using your Global Admin credentials to manage the cloud-side service principals.
Connect-MgGraph -Scopes Application.ReadWrite.AllCode language: CSS (css)
Once authenticated to your tenant, install the Exchange Online Management module, which will be required for configuring the cloud-side connectors later in the process.
Install-Module ExchangeOnlineManagement
Phase 2: Generating the Automation Scripts
To establish the OAuth trust, Microsoft 365 needs a copy of your on-premises Exchange authentication certificate. You will need to create three distinct PowerShell (.ps1) scripts to handle the extraction, cloud upload, and endpoint registration. Save these three scripts to your server.
1. ExportAuthCert.ps1 This script queries your local Exchange configuration for the active OAuth certificate, extracts it, and saves it as a .cer file to a new directory on your system drive (C:\OAuthConfig).
$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
if((Test-Path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
{
New-Item -Path $env:SYSTEMDRIVE\OAuthConfig -Type Directory
}
Set-Location -Path $env:SYSTEMDRIVE\OAuthConfig
$oAuthCert = (dir Cert:\LocalMachine\My) | Where-Object {$_.Thumbprint -match $thumbprint}
$certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
$certBytes = $oAuthCert.Export($certType)
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
[System.IO.File]::WriteAllBytes($CertFile, $certBytes)Code language: PHP (php)2. RegisterEndpoint.ps1 This script informs Azure Active Directory where to route requests when Teams tries to access calendar data. The Application ID 00000002-0000-0ff1-ce00-000000000000 is the universal identifier for Exchange Online. Note: You must revise the URLs in this script (tolgakaya.tr and autodiscover.tolgakaya.tr) to match your organization’s specific domain and Autodiscover endpoints before executing it.
$ServiceName = "00000002-0000-0ff1-ce00-000000000000";
$x = Get-MgServicePrincipal -Filter "AppId eq '$ServiceName'"
$x.ServicePrincipalNames += "https://tolgakaya.tr/"
$x.ServicePrincipalNames += "https://autodiscover.tolgakaya.tr/"
Update-MgServicePrincipal -ServicePrincipalId $x.Id -ServicePrincipalNames $x.ServicePrincipalNamesCode language: PHP (php)3. UploadAuthCert.ps1 This final script takes the .cer file you exported in the first step and uploads it to the Exchange Online Service Principal in Azure. This step ensures that when your on-premises server communicates with the cloud, Microsoft 365 cryptographically verifies and trusts the connection.
Connect-MgGraph -Scopes Application.ReadWrite.All
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
$objFSO = New-Object -ComObject Scripting.FileSystemObject
$CertFile = $objFSO.GetAbsolutePathName($CertFile)
$cer = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertFile)
$binCert = $cer.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
Write-Host "[+] Trying to query the service principals for service: $ServiceName" -ForegroundColor Cyan
$p = Get-MgServicePrincipal -Filter "AppId eq '$ServiceName'"
Write-Host "[+] Trying to query the keyCredentials for service: $ServiceName" -ForegroundColor Cyan
$servicePrincipalKeyInformation = Get-MgServicePrincipal -Filter "AppId eq '$ServiceName'" -Select "keyCredentials"
$keyCredentialsLength = $servicePrincipalKeyInformation.KeyCredentials.Length
if ($keyCredentialsLength -gt 0) {
Write-Host "[+] $keyCredentialsLength existing key(s) found – we keep them if they have not expired" -ForegroundColor Cyan
$newCertAlreadyExists = $false
$servicePrincipalObj = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphServicePrincipal
$keyCredentialsArray = @()
foreach ($cred in $servicePrincipalKeyInformation.KeyCredentials) {
$thumbprint = [System.Convert]::ToBase64String($cred.CustomKeyIdentifier)
Write-Host "[+] Processing existing key: $($cred.DisplayName) thumbprint: $thumbprint" -ForegroundColor Cyan
if ($newCertAlreadyExists -ne $true) {
$newCertAlreadyExists = ($cer.Thumbprint).Equals($thumbprint, [System.StringComparison]::OrdinalIgnoreCase)
}
if ($cred.EndDateTime -lt (Get-Date)) {
Write-Host "[+] This key has expired on $($cred.EndDateTime) and will not be retained" -ForegroundColor Yellow
continue
}
$keyCredential = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential
$keyCredential.Type = "AsymmetricX509Cert"
$keyCredential.Usage = "Verify"
$keyCredential.Key = $cred.Key
$keyCredentialsArray += $keyCredential
}
if ($newCertAlreadyExists -eq $false) {
Write-Host "[+] New key: $($cer.Subject) thumbprint: $($cer.Thumbprint) will be added" -ForegroundColor Cyan
$keyCredential = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential
$keyCredential.Type = "AsymmetricX509Cert"
$keyCredential.Usage = "Verify"
$keyCredential.Key = [System.Text.Encoding]::ASCII.GetBytes($credValue)
$keyCredentialsArray += $keyCredential
$servicePrincipalObj.KeyCredentials = $keyCredentialsArray
Update-MgServicePrincipal -ServicePrincipalId $p.Id -BodyParameter $servicePrincipalObj
} else {
Write-Host "[+] New key: $($cer.Subject) thumbprint: $($cer.Thumbprint) already exists and will not be uploaded again" -ForegroundColor Yellow
}
} else {
$params = @{
type = "AsymmetricX509Cert"
usage = "Verify"
key = [System.Text.Encoding]::ASCII.GetBytes($credValue)
}
Write-Host "[+] This is the first key which will be added to this service principal" -ForegroundColor Cyan
Update-MgServicePrincipal -ServicePrincipalId $p.Id -KeyCredentials $params
}Code language: PHP (php)Phase 3: Executing the Configuration
With the scripts ready, proceed to execute them in the correct sequence.
- Open the Exchange Management Shell (EMS) and run the
ExportAuthCert.ps1script. This will output a certificate file to your local drive. - Switch back to your standard PowerShell window (where you are authenticated to MgGraph) and execute
UploadAuthCert.ps1to push the certificate to Azure. - Finally, execute your modified
RegisterEndpoint.ps1script in the same standard PowerShell window to bind your domains to the cloud service.
Phase 4: Establishing Intra-Organization Connectors
The last technical requirement is to bridge the two environments by creating reciprocal Intra-Organization Connectors. This configuration allows the Teams application backend to seamlessly query the on-premises Autodiscover service for free/busy and meeting data.
Return to the Exchange Management Shell and run the following command to create the outbound connector from your local environment to the Microsoft 365 cloud. Make sure to replace the target domain with your actual .onmicrosoft.com tenant address:
New-IntraOrganizationConnector -Name ExchangeHybridOnPremisesToOnline -DiscoveryEndpoint https://outlook.office365.com/autodiscover/autodiscover.svc -TargetAddressDomains "tolgakayatr.mail.onmicrosoft.com"Code language: PHP (php)Next, open a PowerShell session connected to Exchange Online and execute the inverse command. This connector directs cloud traffic back to your on-premises Autodiscover endpoint. Adjust the discovery endpoint URL and target domain to match your local infrastructure setup:
New-IntraOrganizationConnector -Name ExchangeHybridOnlineToOnPremises -DiscoveryEndpoint "https://mail.trt.net.tr/autodiscover/autodiscover.svc" -TargetAddressDomains "tolgakaya.tr"Code language: PHP (php)Once the Active Directory synchronization processes these changes and the OAuth trust propagates, the Teams application will successfully resolve local EWS endpoints. Users hosted on the On-Premises Exchange server will see the Calendar tab appear in their Teams client, fully populated and functional.
Official Microsoft: Microsoft Learn
Read More: How to Best Using SCCM Filter Devices in a Specific Organizational Unit (OU)






