An integration stops working. The certificate expired last month. The person who set it up has moved teams, and nobody is sure who owns the application. You can start that conversation before the outage.

I built this small PowerShell tool to turn app-registration credential metadata and ownership into a review queue I could actually work through. You get an HTML report to read, CSV files to investigate and a coverage record that tells you what the script could actually check.

Verified tenant run. On 12 November 2026, the read-only inventory returned 24 app registrations and 16 stored credentials, with all 25 collection reads complete. The CSV files were reconciled and three registrations were checked in Entra. The walkthrough below shows the findings, the portal comparisons and the limits of that test.

1. Choose the right inventory

The scope is ordinary app registrations in your tenant: stored client-secret and key/certificate metadata, plus the owners of those registrations. It is a useful starting point for renewal planning and ownership conversations.

You need to answerThis tool's answer
Which stored credentials have expired or expire soon?Credential identifiers, UTC dates and separate 7-, 30- and 60-day windows.
Which app registrations have no owner or only one?A confirmed owner count only when the full owner query succeeds.
Did every query succeed?An explicit coverage table; missing evidence stays incomplete.
Is an application broken, unused or compromised?Not assessed. Expiry and ownership metadata cannot establish those conclusions.
Are all workload identities covered?No. Service-principal-only credentials, SAML signing certificates, managed identities, federation and credentials held in other tenants are outside this version.

Microsoft Graph can also return agent identity blueprints in its applications collection. This version lists explicit derived object types as excluded instead of quietly treating them as ordinary registrations. It does not inspect permissions, sign-in activity or owner account status.

2. Prepare the connection and package

Use PowerShell 7.4 or newer and the official Microsoft.Graph.Authentication module, version 2.37.0 or newer. The initial release supports the Global cloud and a delegated work or school account. It does not support unattended app-only runs.

The minimum Graph permission for these reads is Application.Read.All. Consent and the signed-in user's access are separate requirements. Use an account allowed to read the selected applications; Global Administrator is not a prerequisite. Do not add directory-wide permissions merely to fill in an owner's display name.

Download v1.0.0 from GitHub · Release notes and checksum · Source and instructions

Download the named ZIP under the release's Assets. GitHub hosts the source and versioned package; the script does not upload tenant reports to GitHub or download updates during an audit.

Extract the package and review the two PowerShell files. Open PowerShell 7 in the extracted folder containing Get-EntraAppCredentialAudit.ps1 and EntraAppCredentialAudit.psm1, or use Set-Location to move to that folder before running the commands below. They use ./ to refer to the current folder. The files have a Tiago Carvalho attribution header and an MIT licence. They are not digitally signed. Follow your organisation's script approval and signing process. The hash manifest checks file integrity; it is not a substitute for publisher authentication.

Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -MinimumVersion 2.37.0
Import-Module Microsoft.Graph.Authentication

$tenantId = '<your-tenant-guid>'
Connect-MgGraph -TenantId $tenantId -Scopes 'Application.Read.All' `
    -ContextScope Process -NoWelcome

Get-MgContext | Select-Object TenantId, Account, AuthType, Environment

Install the module only if needed. Connect and run the collector in the same PowerShell window. Review any consent prompt before accepting it. The collector itself never installs a module, signs in, grants permissions or changes the tenant.

3. Start with one application

Open Entra admin center → Entra ID → App registrations → All applications. Select an application you can independently inspect and open Overview. Copy the Object ID. The Application (client) ID is a different identifier; the enterprise application's Object ID is different again.

Real Entra app-registration overview showing two certificates, zero secrets and an expiry warning; identifiers concealed.
Figure 1. The selected app registration in Entra. Client ID, Object ID and tenant ID are concealed. The credential count and expiry warning are unchanged. Open full size.

The local registration used for this comparison is named MicrosoftGraph. This is its display name, not an assertion that it is the Microsoft Graph service or the official PowerShell client. The portal shows two certificates and zero client secrets.

$appObjectId = '<app-registration-object-guid>'

./Get-EntraAppCredentialAudit.ps1 -ExpectedTenantId $tenantId `
    -ApplicationObjectId $appObjectId -OutputPath ./PilotAudit

The expected-tenant check runs before any inventory request. Every run needs a new output folder, so an earlier report cannot be overwritten. A selected object that is not returned is recorded as incomplete coverage rather than a successful empty result.

4. Read coverage before the findings

Open PilotAudit/report.html locally. Start with Query coverage, then review applications and credentials. The report is self-contained and does not require hosting, an account or external scripts.

Actual PowerShell tenant run showing 24 applications, 16 credentials, 20 ownerless applications, 12 expired credentials, zero due within 60 days and zero incomplete sections.
Figure 2. The actual PowerShell result supplied after the tenant run. The crop retains the result fields and omits local paths and the unrelated terminal behind it. Open full size.

The real tenant run used collector 1.0.0-rc.1 with -AllApplications, PowerShell 7.7.0 and Microsoft.Graph.Authentication 2.39.0. It requested Application.Read.All using an existing administrative account and existing consent. This confirms execution in that environment; the documented minimum permissions were not separately tested with a restricted non-admin account. Version 1.0.0 adds the nested-error correction described below; its regression checks were performed offline.

Observed resultCountHow to read it
App registrations24All returned registrations were ordinary application objects; no derived types were excluded.
Stored credentials1613 client secrets and 3 certificates across 14 registrations.
Expired credentials1210 secrets and 2 certificates. These are review findings, not 12 proven outages.
Future expiry within 60 days0The 4 unexpired credentials all end beyond 60 days. This does not cancel the expired findings.
Registrations with zero owners20Complete owner queries returned no assigned owners. The other 4 registrations each had one.
No stored password/key credentials10Informational: the authentication design needs separate review.
Collection coverage25 complete; 0 incompleteOne application-list read and 24 owner-collection reads.
FileUse it for
applications.csvThe review queue: owner state, stored credential counts and reasons for follow-up.
credentials.csvIndividual Key IDs, types, validity dates and expiry windows.
owners.csvOwner IDs and types, names when readable, and whether the owner collection was complete.
coverage.csvSuccessful, incomplete and unattempted reads, including safe error categories.

COMPLETE describes the stated reads. It is not a security score. PARTIAL means the returned rows can be useful, but the inventory has gaps. An owner query that fails is Unknown, never NoOwner.

Dates use UTC. A credential whose end time has passed is Expired; future expiry windows are disjoint. The 30-day bucket contains more than 7 and up to 30 days; the 60-day bucket contains more than 30 and up to 60. A separate validity field identifies future start dates, invalid ranges and missing dates. The displayed day count is rounded up; the status uses the exact timestamp.

Neither a second valid credential nor a certificate with a future start date proves the workload is using it. Likewise, an application with no stored password or key credentials may be perfectly legitimate. Confirm the authentication design before opening a remediation ticket.

5. Compare the evidence in Entra

Stay in the same app registration and open Certificates & secrets. Select the relevant tab. Match the report's Key ID with the Certificate ID or Secret ID and compare the end date. Allow for the portal's date display and the report's UTC timestamps. Do not copy a secret value.

Real Entra Certificates tab showing two certificates expiring on 14 June 2025 and 19 June 2025.
Figure 3. The two certificate rows from the same app registration. Expiry dates are visible; thumbprints and identifiers are concealed. No certificate was uploaded or removed. Open full size.

The two certificate IDs and expiry dates matched the CSV: 14 June 2025 and 19 June 2025. Both were correctly classified as Expired. Workload use and service availability were not tested.

Next open Owners under the app registration. Compare the returned identities with owners.csv. Do not use the Owners page under Enterprise applications for this check: that is the service principal.

Real Entra app-registration Owners page with one user row; the owner name and addresses are masked.
Figure 4. One owner row is visible in the portal and matches the report. The exact identity is concealed in the publication asset. Open full size.

The one user owner shown here matched the report's SingleOwner result. Microsoft recommends at least two owners. Treat a single owner as a continuity question: who can take responsibility if that person is unavailable? Owner access is privileged, so the follow-up needs a considered assignment, not an automatic bulk fix.

The Graph relationship can contain users or service principals. Under minimum permissions, a returned owner may have an ID and object type but no readable name. The script counts that owner and labels the details as limited. An empty name does not turn it into an ownerless application.

Real Intune Graph Uploader app-registration Owners page stating that no owners have been assigned to this application.
Figure 5. Entra confirms no assigned owners for the second sampled registration, matching its NoOwner report state. Open full size.

A second registration, Intune Graph Uploader, showed no assigned owners in Entra and NoOwner in the report. Its client-secret ID and expiry date, 22 November 2025, also matched. As a control, a certificate on a third registration ended on 2 December 2029 and correctly appeared as Beyond60Days with WithinValidityDates.

These are three sampled portal comparisons. The totals for all 24 registrations come from the Graph inventory and reconciliation of its CSV files; they are not a claim that every registration was inspected manually.

6. Expand the audit and turn findings into work

Once the pilot matches the portal, collect the tenant's app registrations into a separate folder:

./Get-EntraAppCredentialAudit.ps1 -ExpectedTenantId $tenantId `
    -AllApplications -OutputPath ./AllAppsAudit

Disconnect-MgGraph

Keep the report private: it contains application identifiers and owner information. The exporter excludes secret values, password hints, certificate bytes, tokens and raw server error messages. Spreadsheet-like formula text is escaped in CSV output, and application-supplied text is encoded in HTML.

FindingUseful next action
Expires within 7 daysFind the accountable team, establish whether the credential is used, and agree a tested renewal plan.
Expired credentialCheck the workload and any replacement before deciding whether to remove the stale credential.
No ownerIdentify the business and technical owners; follow the approved ownership assignment process.
Single ownerReview continuity and select an appropriate backup owner.
Unknown or incompleteResolve the read gap and rerun before treating the result as definitive.

A useful renewal ticket records the application Object ID, credential Key ID, expiry in UTC, responsible team, change window and a workload-specific acceptance test. Where supported, consider managed identity or workload identity federation; if a stored credential is necessary, assess a certificate rather than automatically choosing another client secret.

After an approved change, rerun into a new folder and compare the identifiers and dates. A new credential in Entra is only one milestone. The workload must use it successfully before you retire the old one.

7. Handle failures without hiding them

403 / AccessDenied: check consent and the signed-in user's access. 401: reconnect and retry into a new folder. SelectedApplicationNotReturned: check the app-registration Object ID and whether the object still exists.

429 / Throttled: the SDK performs its normal retries. If it still returns throttling, this collector stops remaining requests and preserves a partial report. Wait according to the service's guidance; do not loop the script. Selecting application keyCredentials has a documented tenant limit of 150 requests per minute. Owner queries are sequential and all returned pages are followed.

Authentication window error: if Graph reports that a window handle must be configured, use a normal interactive PowerShell 7 window. That resolved the launch issue for this tenant test without changing permissions or tenant controls.

The CSV coverage record keeps safe error categories instead of raw responses. Retain the timestamp and scope for an administrator-led investigation. Avoid publishing tenant reports or diagnostic payloads in GitHub issues.

8. What has been verified

CheckEvidence in this revision
Portal credential comparisonMatching IDs and dates for two expired certificates, one expired client secret and one unexpired certificate across three registrations.
Portal owner comparisonOne registration with a user owner and another with no assigned owners matched their report states.
Offline behaviour57 checks passed for version 1.0.0, including exact date boundaries, incomplete and limited owner reads, pagination, GET-only collection, wrong-tenant checks and HTML/CSV output. Nested 401/429/503 errors now stop later requests. Direct and wrapped HTTP 429 exceptions from the installed SDK also passed a separate offline check.
Authenticated collector runCompleted at approximately 15:08 UTC on 12 November 2026: 24 registrations, 16 credentials, 20 registrations without assigned owners and no incomplete sections.
CSV reconciliationAll application credential/owner counts, coverage rows, UTC expiry classifications and summary totals agreed.
Test boundariesThe live run fit on one application-list page. Multi-page results, throttling, denied reads and limited owner details were tested offline, not deliberately induced in the tenant. Restricted non-admin access and minimum-version compatibility were not live-tested. The live inventory was not rerun after the final error-handling correction; successful-collection and report logic is unchanged.
Changes to applications or credentialsNone performed by this audit or the portal comparison.

The GitHub repository includes the offline tests, licence, changelog, security guidance and validation notes. Check the Actions tab for hosted validation results and the v1.0.0 release for the versioned download. Raw tenant reports remain private and are excluded from the package.

If you are building a repeatable administration routine, pair this inventory with the read-only Exchange Online forwarding audit. Each report answers a bounded question and records where the evidence stops.

Microsoft references

  1. List applications: permissions, paging and key credential throttling
  2. List owners: permissions and limited information
  3. Application resource: ownership and the two-owner recommendation
  4. Password credential metadata
  5. Key credential types and validity dates
  6. Microsoft Graph throttling guidance
  7. Security operations for applications
  8. Microsoft Graph PowerShell authentication
  9. Workload identity federation and supported scenarios
  10. Application objects and service principals

Documentation checked on 12 November 2026. Screenshots show the portal available in the test tenant; labels and layouts can change.