PowerShell · Microsoft Graph · NHI Governance

Entra ID
NHI Audit

A PowerShell module that audits a Microsoft Entra ID tenant for non-human identity risk. Discovers every service principal, app registration, and managed identity; scores each on eight factors; writes an HTML + JSON report your IAM team can act on.

PowerShell 7 Microsoft Graph SDK 8 risk factors Mock + live modes
At a glance 30-second read

What it is

A focused, opinionated audit tool. Four cmdlets: Connect, Get-Inventory, Test-Risk, Export-Report. Runs offline against a 28-record mock tenant for demos; connects to a real tenant via Microsoft.Graph SDK when given a tenant GUID. Same risk-scoring logic both ways. Ships a sample report so you can preview the output without installing anything.

Maps to

Identity Engineer · NHI Governance Engineer · Cloud Identity (Microsoft) · Security Engineer (IAM)

Skills demonstrated

  • PowerShell 7 module design (manifest, public/private structure)
  • Microsoft Graph SDK integration (delegated + app-only auth patterns)
  • Entra ID NHI taxonomy: SP, app registration, managed identity
  • Eight-factor risk scoring with explainable findings
  • Self-contained HTML report + structured JSON for SIEM / ticketing
  • Mock-vs-live mode for testability
PowerShell 7 Microsoft Graph Entra ID Active Directory NHI IAM
01 — Sample Output 28 mock NHIs · auto-scored

A real audit, written for an IAM team.

What the report looks like — exec-summary counts at the top, per-identity cards sorted by risk, each finding paired with a specific remediation. The full HTML is self-contained, drop it on a fileshare or attach it to a ticket.

reports/sample-report.html [ ACTUAL OUTPUT ]
Contoso (mock) — NHI Audit
Tenant: Contoso (mock) · Mode: mock · Generated: 2026-05-24 12:00 UTC
5
Critical
4
High
2
Medium
4
Low
13
Healthy
28
Total
legacy-payroll-sync
100 / 100 · Critical
Application · 0 owners · Last sign-in: 2024-12-08
[High] NHI001 — No owner assigned
→ Assign a team owner via az ad sp owner add or Entra portal.
[Critical] NHI004 — Credential expired 251 days ago but identity still enabled
→ Rotate immediately or disable identity if no longer needed.
[High] NHI008 — Marked 'legacy' and still enabled
→ Confirm migration plan; set decommission date.
intern-poc-api-2024
85 / 100 · Critical
Application · 0 owners · Last sign-in: 2024-08-22
[High] NHI001 — No owner assigned
[Critical] NHI004 — Credential expired 343 days ago but identity still enabled
datadog-monitor
0 / 100 · Healthy
Application · 1 owner · Last sign-in: 2026-05-24
No findings — healthy
Open the full sample report on GitHub →
02 — Risk Factors eight scored signals

What the scorer looks for.

Each factor produces a finding with a severity, a one-sentence description, and a specific remediation. The factors are deliberately opinionated — the kind of things an IAM lead would call out in a quarterly review.

CodeFactorSeverity
NHI001No owner assignedHigh
NHI002Created >14 days ago, never signed inHigh
NHI003No sign-in for >90 days (180+ → High; 90–180 → Medium)High / Med
NHI004Credential expired but identity still enabledCritical
NHI005Credential expires within 30 daysMedium
NHI006Oldest credential >365 days (rotation policy breach)Medium
NHI007Account disabled but credentials still presentMedium
NHI008Tagged 'legacy' and still enabledHigh

Adding a factor is a function in Test-NhiRisk.ps1. The scoring is linear (per-factor weights add to 0–100) so individual contributions stay readable in a report — no opaque ML model.

03 — Try it mock mode · no tenant required

Clone, run, open the report.

PowerShell 7+ on macOS, Linux, or Windows. No Microsoft Graph SDK needed for the mock demo — only for live-tenant mode.

entra-id-nhi-audit · demo [ COPY/PASTE ]
$ git clone https://github.com/iambrucedavis/entra-id-nhi-audit
$ cd entra-id-nhi-audit
$ pwsh scripts/run-demo.ps1 # mock mode, no tenant
$ open reports/nhi-audit.html
# Real tenant — requires Microsoft.Graph SDK + user consent
$ Install-Module Microsoft.Graph -Scope CurrentUser -Force
$ pwsh scripts/connect-live-tenant.ps1 -TenantId <guid>

The cmdlets pipe naturally. Get-EntraNhiInventory | Test-NhiRisk | Where-Object RiskLevel -eq 'Critical' gives you just the critical findings; | Export-NhiAuditReport writes the report.

04 — Downstream JSON is shaped for ingestion

Built to plug into the rest of the stack.

The HTML report is for humans. The JSON output is for everything else.

SIEM Alerts
Pipe the JSON to Splunk / Sentinel / Datadog. Alert on any record with RiskLevel: Critical — auto-page the on-call IAM engineer with the remediation already attached.
Ticketing
One JIRA / Linear ticket per Critical or High finding. The Remediation field becomes the ticket description; the owner team gets assigned automatically.
Slack Digest
Group findings by owner team, surface the top three risks per team, post daily. The owner team sees their NHI hygiene before the security team has to ask.
Trend Tracking
Archive each run's JSON; diff against the previous run. The metric that matters isn't today's count — it's whether the count is going down month over month.