/
/

How to Run Scheduled Graph API Checks for Expired Licenses and Inactive Users

by Mauro Mendoza, IT Technical Writer
How to Run Scheduled Graph API Checks for Expired Licenses and Inactive Users blog banner image

Unused Microsoft 365 licenses are a silent budget drain, while inactive Azure AD accounts pose a real security risk. Prevent unnecessary elements in your domain by automating checks using Microsoft Graph API to identify expired licenses and dormant users.

In this guide, we will help you schedule critical checks via PowerShell to lock down vulnerabilities and scale across clients.

Step-by-step procedure to schedule Graph API checks via PowerShell

Automate Graph API checks to reclaim wasted licenses, secure dormant accounts, meet compliance, and streamline tenant management—done in one sweep.

📌 Use case: Do this method during monthly cost reviews, security audits, or before license renewals. This is ideal for MSPs reclaiming Office 365 expired licenses or hardening security after offboarding employees. This can help spot inactive users before they become vulnerabilities to your system.

📌 Prerequisites: Before we proceed with the procedure, ensure you have the following:

  • Administrative permissions: Make sure your account has a Global Admin or Reports Reader role in Azure AD.
  • Tools: Microsoft Graph PowerShell SDK (Microsoft.Graph module) and PowerShell 7+.
  • Azure setup: Ensure your Azure AD app has User.Read.All, LicenseAssignment.ReadWrite.All, and AuditLog.Read.All permissions for automation.
  • Optional but recommended: Download and install an RMM tool like NinjaOne to schedule scripts, monitor outputs, and scale across tenants.

We recommend checking ⚠️ Things to look out for before proceeding.

Step 1: Install and authenticate Microsoft Graph SDK

Set up your toolkit to automate license and user checks. Installing the Graph SDK to query Azure AD data (which the admin usually hides) authenticates ties to your script to Microsoft’s API.

Step-by-step procedure:

  1. Install the SDK by running this script in PowerShell (Admin) or Windows Terminal (Admin):

Install-Module Microsoft.Graph -Scope CurrentUser -Force

  1. Authenticate using this script:

Connect-MgGraph -Scopes "User.Read.All", "Reports.Read.All", "Directory.Read.All"

You can also configure a registered app with a client secret and use certificate or client credentials flow for unattended execution.

After you’ve authenticated Graph SDK, you can query inactive Azure AD users and expired Office 365 licenses in the next steps.

Step 2: Query for users with expired or unassigned licenses

This step will help uncover expired licenses and inactive accounts that pose a security risk and unnecessary costs.

Step-by-step procedure:

  1. In PowerShell (Admin), run this script to check for unassigned licenses at the tenant-level:

Get-MgSubscribedSku |
Select-Object SkuPartNumber,
@{Name="TotalLicenses"; Expression={$_.PrepaidUnits.Enabled}},
@{Name="UsedLicenses"; Expression={$_.ConsumedUnits}},
@{Name="Available"; Expression={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}} |
Format-Table -AutoSize

  1. Run this script to find unlicensed users:

Get-MgUser -Filter "assignedLicenses/`$count eq 0" `
-ConsistencyLevel eventual `
-CountVariable unlicensedCount `
-All |
Select-Object DisplayName, UserPrincipalName |
Export-Csv -Path "UnlicensedUsers.csv" -NoTypeInformation

  1. Run this script to find inactive users with licenses:

$daysInactive = 90
$cutoffDate = (Get-Date).AddDays(-$daysInactive)# Get licensed inactive users
Get-MgUser -All -Property "DisplayName,UserPrincipalName,SignInActivity,AssignedLicenses" |
Where-Object {
($_.SignInActivity.LastSignInDateTime -lt $cutoffDate) -and
($_.AssignedLicenses.Count -gt 0)
} |
Select-Object DisplayName,
UserPrincipalName,
@{Name="LastSignIn"; Expression={$_.SignInActivity.LastSignInDateTime}} |
Export-Csv -Path "InactiveLicensedUsers.csv" -NoTypeInformation

⚠️ Important: Keep in mind that SignInActivity requires Azure AD Premium P1/P2. Avoid beta endpoints as well for production scripts.

After running these scripts, you’ll have two CSV files: UnlicensedUsers.csv, which contains users that need license assignment; and InactiveLicensedUsers.csv, which lists accounts that are security risks.

Step 3: Export and log audit results

After getting the list of expired and unassigned licenses, turn this data into actionable insights with automated reporting. Exporting this data creates permanent audit trails for proof of compliance, and automation ensures you catch issues before they become costly issues.

Step-by-step procedure:

  1. In PowerShell (Admin), run this script to export to Timestamped CSV:
    • Script to generate a filename with a timestamp:

$timestamp = Get-Date -Format "yyyyMMdd-HHmm"
$reportPath = "C:\Audits\LicenseReport_$timestamp.csv"

    • Export inactive users with licenses:

$inactiveUsers | Export-Csv -Path $reportPath -NoTypeInformation

    • This will create universally openable CSV files, and then the timestamping creates versioned records for compliance.
  1. (Optional): Run this script to email reports:

Send-MailMessage -From "[email protected]" -To "[email protected]" `
-Subject "ACTION REQUIRED: $($inactiveUsers.Count) Inactive Licensed Users" `
-Body "Attached: Users with licenses but no sign-ins for 90+ days" `
-Attachments $reportPath -SmtpServer "smtp.office365.com" -Port 587 `
-Credential (Get-Credential)

  1. Run this script to fully automate with the Azure AD app:

$tenantId = "YOUR_TENANT_ID"
$clientId = "YOUR_CLIENT_ID"
$clientSecret = "YOUR_SECRET" | ConvertTo-SecureString -AsPlainText -ForceConnect-MgGraph -TenantId $tenantId -ClientId $clientId -ClientSecret $clientSecret

💡 Tip: Store secrets in Azure Key Vault. Never hardcode them.

After running these scripts, you’ll have timestamped CSV reports stored locally and optional email alerts to stakeholders instantly. These files can help you make automated runs via Task Scheduler or RMM tools like NinjaOne to ensure continuous monitoring.

Step 4: Automate with Task Scheduler or RMM policy

Schedule your script once and let it run monthly, yearly, or forever, ensuring proactive monitoring without lifting a finger. Creating regular check-ups helps catch issues before they cause problems. There are two options to do this:

Option 1: Via Task Scheduler (Single Machine)

Step-by-step procedure:

  1. Open Command Prompt (Admin), then run this command:

schtasks /create /tn "M365LicenseAudit" /tr `
"powershell.exe -ExecutionPolicy Bypass -File C:\Scripts\LicenseAudit.ps1" `
/sc weekly /d MON /st 02:00 /ru "NT AUTHORITY\SYSTEM"

    • This works because it runs as a system account even when no user is logged in.

Option 2: Via RMM tools

Step-by-step procedure:

  1. Open your preferred RMM tool like NinjaOne.
  2. Create the script (from Option 1), then store it in NinjaOne’s script library.
  3. Assign the script to the Microsoft 365 Clients policy group.
  4. Configure outputs:
    • Save CSVs to \\ClientShare\Audits.
    • Upload to the RMM’s dashboard for centralized viewing.

After scheduling the script, it will run on the assigned date and time, auto-saving the reports to designated folders, and showing the trends among tenants.

⚠️ Things to look out for

This section highlights potential challenges to keep in mind while following this guide.

RisksPotential ConsequencesReversals
1. Missing data in reportsIncomplete audits, undetected risksCheck permissions: Run Get-MgContext to verify scopes like User.Read.All.

Bypass MFA blocks: Use certificate auth instead of secrets.

2. Script fails silentlyFalse sense of security, compliance gapsEnable logging: Add Start-Transcript -Path C:\Logs\Audit.log to scripts.

Monitor Event Viewer: Check under Applications and Services Logs > PowerShell.

3. Users not flaggedExpired licenses/inactive accounts missedValidate thresholds: Confirm that the $cutoffDate aligns with the policy.

Verify license fields: Use Get-MgUser -UserId [email protected] -Property AssignedLicenses.

4. Hardcoded secretsTenant compromise, credential theftRotate secrets immediately in Azure AD.

Migrate to Azure Key Vault: Use Get-AzKeyVaultSecret for secure retrieval.

5. Beta API dependencyScript breaks after Microsoft updatesReplace beta endpoints: Use Get-MgUser (v1.0) instead of Get-MgBetaUser.

Subscribe to the Microsoft Graph changelog.

Key considerations for bulletproof Graph API PowerShell audits

Tweak these settings to match your environment. Ensuring precision prevents false alarms and keeps workflows smooth.

Report exclusions

Exclude service accounts, shared mailboxes, and break-glass accounts from inactivity scans. This prevents accidental license reclamation for critical resources. You can use PowerShell filters to whitelist them, using this script:

$excludedUPNs = @("[email protected]", "[email protected]")
Get-MgUser | Where-Object { $_.UserPrincipalName -notin $excludedUPNs }

Inactivity thresholds

Set thresholds based on your risk tolerance. If an account is inactive for 90 days, they are low risk, 60 days is medium risk, while 30 days is high risk. Based on the risk, action them by sending our warning emails, revoke their licenses, or disable the account then reclaim. This ensures you are protected from issues.

Auditing frequency

Time your audits properly to avoid security risks and other issues. For most businesses, it is recommended to have monthly checks to align with billing cycles.

In high-turnover industries, doing weekly checks ensures reports can keep up with the changes in the environment. You should also trigger the audit immediately each time an employee exits the company.

Action mapping

Build SOPs that auto-trigger next steps. Run the following scripts to reclaim licenses and disable accounts:

  • To reclaim licenses:

Set-MgUserLicense -UserId "[email protected]" -RemoveLicenses @("skuId")

  • To disable accounts:

Update-MgUser -UserId "[email protected]" -AccountEnabled:$false

After running these scripts, archive the data. Forward them to the manager/SharePoint via Graph API.

How NinjaOne can streamline your license audits

Transform Graph API checks from manual tasks into fully automated, cross-tenant compliance engines.

  • Centralized script deployment: Push your Graph API PowerShell scripts to 100+ endpoints/tenants in one action.
  • Automatic output monitoring: Track CSV reports/logs across all clients. Alerts fire instantly if files don’t update.
  • Failure alerts in your tools: Get Slack, Team, or email notifications when scripts fail.
  • Unified cross-tenant reports: Merge expired Office 365 licenses and inactive users’ data into one executive dashboard.
  • Auto-trigger remediation: Auto-create tickets in your PSA tool with predefined SOPs to detect expired licenses.

Lock down costs and security with Graph API license checks

Automating license and user audits transforms reactive oversight into proactive control. By reclaiming unused licenses and disabling dormant accounts, you prevent billing leaks and shut down attack vectors. Use the procedure above to turn unused licenses into budget savings and stale accounts into compliance.

Related topics

You might also like

Ready to simplify the hardest parts of IT?