Unattended Graph PowerShell scripts with certificate authentication
Scheduled scripts can't answer an MFA prompt. App-only authentication with a certificate is the right way to run Microsoft Graph PowerShell unattended.
Part 2 of 5 in Automating Entra and Azure safely
On this page
Interactive Connect-MgGraph is fine at your desk. A scheduled task or server-side script needs its own identity. If the script runs in Azure, use a managed identity. Otherwise, use an app registration with a certificate, never a client secret pasted into the script.
Delegated (interactive)
- Someone signs in
- Acts as that user
- Limited to what the user can access
- MFA prompts apply
App-only
- No user involved
- Acts as the app
- Limited to the app permissions granted
- Needs a certificate or managed identity
1. Create a certificate
For a script on a Windows server, a self-signed certificate in the machine store works well:
$cert = New-SelfSignedCertificate -Subject "CN=Graph-UserReport" `
-CertStoreLocation "Cert:\LocalMachine\My" `
-KeyExportPolicy NonExportable -KeySpec Signature `
-NotAfter (Get-Date).AddYears(1)
Export-Certificate -Cert $cert -FilePath C:\Temp\Graph-UserReport.cerOnly the public key (.cer) leaves the server. The private key stays put and can't be exported.
2. Register the app
- Create an app registration, single tenant.
- Under Certificates & secrets, upload the
.cerfile. - Under API permissions, add the Application permissions the script needs, such as
User.Read.All, and grant admin consent. Keep it to the minimum. - Add at least two owners.
3. Connect in the script
Connect-MgGraph -ClientId "<app-id>" -TenantId "<tenant-id>" `
-CertificateThumbprint "<thumbprint>" -NoWelcome
Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled |
Export-Csv C:\Reports\users.csv -NoTypeInformationKeep it safe
- Run the scheduled task as an account that can read the certificate's private key, and no more.
- Track the certificate's expiry date and renew it before it lapses.
- Watch the app's sign-ins in the service principal sign-in logs.
- Consider limiting the app with a Conditional Access policy for workload identities if you have the licensing.
Renewing the certificate
- About a month before expiry, create a new certificate on the server, as before.
- Upload the new
.certo the app registration. Both certificates now work. - Update the thumbprint in the script, or the scheduled task's parameter, and test a run.
- Remove the old certificate from the app registration and the server.
Troubleshooting
- AADSTS700027 (client assertion failed signature validation): the certificate on the server doesn't match one uploaded to the app, or it has expired.
- Certificate not found: the account running the task can't see it. Check the store (LocalMachine or CurrentUser) and the private key permissions.
- 403 Forbidden from Graph: the app is missing an application permission, or admin consent wasn't granted.