Automating License Optimization and Audits in Microsoft 365
Welcome back to the podcast blog! If you have ever stared at your monthly Microsoft 365 software bill and wondered why you are paying for seats that nobody has touched in six months, you are certainly not alone. License management is one of those hidden operational drains that quietly bleeds IT budgets dry while creating unnecessary compliance and security headaches. Fortunately, we have powerful automation tools right at our fingertips. In this comprehensive guide, we will explore how to track, assign, and revoke Microsoft 365 licenses using PowerShell to control costs, streamline onboarding, and ensure strict compliance across your entire tenant. If you enjoy diving deep into technical automation strategies, make sure to check out our related episode on how to Run Your Full Stack Locally with .NET Aspire for more insights into developer workflows and tooling.
Introduction to Automating License Optimization and Audits
Managing a Microsoft 365 tenant manually is a recipe for burnout and human error. As organizations grow, employees churn, departments shift, and software requirements fluctuate wildly. Left unchecked, administrators end up hoarding licenses, continuing to pay for former contractors, or leaving premium E5 licenses assigned to inactive accounts. By introducing automation into your license optimization strategy, you remove the guesswork. PowerShell enables you to query tenant data, analyze usage patterns, and programmatically reclaim unused assets, turning a reactive administrative chore into a proactive financial safeguard.
Prerequisites and Environment Setup
Before writing a single line of license-reclamation code, you need to prepare your environment. Proper preparation ensures that your scripts execute smoothly without running into permission roadblocks or module assembly conflicts.
Supported Systems
You can run your Microsoft 365 management scripts on both Windows and macOS systems. Most administrators prefer Windows 10 or later, while Mac users can leverage PowerShell Core. Always ensure your operating system supports the latest modular packages. If you encounter Web Account Manager (WAM) integration errors or assembly dependency conflicts when connecting to services, utilizing temporary workarounds like the DisableWAM switch can keep your scripts moving forward.
Permissions and Roles
Security is paramount. Never run administrative scripts using a standard user account. Ensure your executing identity holds the appropriate roles, such as Global Administrator, User Administrator, or License Administrator. Sticking to the principle of least privilege ensures that your administrative credentials carry only the exact permissions needed to perform license audits and assignments.
Environment Setup Checklist
- Open Windows PowerShell as an administrator.
- Set the PowerShell Gallery as a trusted repository:
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted - Install the required NuGet provider if prompted.
- Configure your script execution policy:
Set-ExecutionPolicy RemoteSigned
Installing Essential Microsoft 365 PowerShell Modules
To effectively manage users and licenses, you need the right toolsets installed locally. The Microsoft ecosystem has transitioned heavily toward unified SDKs, but several foundational modules remain crucial for day-to-day administrative tasks.
Module Overview
- Microsoft.Graph: The modern unified module replacing legacy tools like MSOnline and AzureAD.
- ExchangeOnlineManagement: Essential for mailbox governance, shared mailbox conversions, and mail-flow tracking.
- MicrosoftTeams: Essential for managing collaboration workloads and meeting policies.
Installation Steps
You can install the core modern modules by executing the following commands in an elevated PowerShell prompt:
Install-Module -Name Microsoft.Graph -Scope CurrentUser -Force
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force
Install-Module -Name MicrosoftTeams -Scope CurrentUser -Force Always verify your installations using Get-InstalledModule to ensure that conflicting versions do not disrupt your automation runbooks.
Connecting PowerShell to Your Microsoft 365 Tenant
Establishing a secure and reliable connection to your tenant is the next critical step. Modern authentication standards require robust approaches, especially when running unattended or scheduled scripts.
Authentication Options
Interactive logins are great for quick, ad-hoc administrative tasks, but automated background scripts require secure alternatives. Leveraging certificate-based authentication or managed identities allows you to connect securely without hardcoding passwords into plain-text script files.
Connecting to the Graph and Services
To connect to the Microsoft Graph backend for user and license management, use:
Connect-MgGraph -Scopes "User.ReadWrite.All", "Organization.Read.All" For Exchange-specific workloads and mailbox configurations, establish your session using:
Connect-ExchangeOnline When your script finishes executing its batch operations, remember to clean up your session by running Disconnect-ExchangeOnline or Disconnect-MgGraph.
Auditing Unused Seats and License Inventories
License optimization starts with visibility. You cannot reclaim seats if you do not know who is utilizing them and who has abandoned them. Using PowerShell, you can pull comprehensive inventory reports detailing assigned SKUs and last logon timestamps.
Checking Subscribed SKUs
To check your tenant's total pool of purchased licenses versus available counts, run the following command:
Get-MgSubscribedSku | Select-Object SkuId, SkuPartNumber, @{Name="Total";Expression={$_.ConsolidatedStatus}}, @{Name="Assigned";Expression={$_.AssignedUnits}} Finding Inactive Users
Identifying accounts that have not authenticated in over 90 days is the fastest way to slash unnecessary cloud expenditures. You can filter user lists to locate dormant accounts:
$Threshold = (Get-Date).AddDays(-90)
Get-MgGet-MgUser -All -Property Id, DisplayName, UserPrincipalName, SignInActivity | Where-Object { $_.SignInActivity.LastSignInDateTime -lt $Threshold } Exporting this data to a CSV file allows you to present clear cost-saving metrics to IT leadership before executing bulk license removals.
Using Set-MgUserLicense to Assign and Revoke Licenses
Once you have identified underutilized seats or new hires requiring onboarding, the Set-MgUserLicense cmdlet becomes your primary mechanism for adjustment.
Assigning Licenses Programmatically
To assign a specific product SKU to a new user, you construct a license assignment object containing the target SkuId:
$Params = @{
AddLicenses = @(
@{
SkuId = "your-sku-guid-here"
}
)
RemoveLicenses = @()
}
Set-MgUserLicense -UserId "alex.johnson@contoso.com" -BodyParameter $Params Revoking Unused Licenses
When reclaiming licenses from dormant accounts or offboarded personnel, you reverse the operation by passing the SKU identifier into the RemoveLicenses array parameter, ensuring your organization stops paying for unutilized cloud capacity immediately.
Automating Onboarding and Offboarding License Workflows
Manual user provisioning and deprovisioning introduce significant latency and security risks. By scripting your onboarding and offboarding workflows, you ensure that licenses are provisioned the moment an employee joins and immediately revoked the day they depart.
An automated offboarding script can disable the user account, strip out their assigned licenses, convert their mailbox to a shared resource, and export audit trails to a secure storage location—all within a single continuous execution block. This level of synchronization eliminates license waste and preserves organizational security posture.
Security Best Practices and Least Privilege
Automation grants immense power, which means it must be governed tightly. Adhering to strict security principles protects your tenant from accidental misconfigurations and malicious script execution.
- Least Privilege Access: Never grant Global Administrator rights to automated service accounts. Scope permissions tightly to what the script actually needs.
- Credential Security: Never store plain-text passwords inside scripts. Use Azure Key Vault, managed identities, or certificate authentication.
- Comprehensive Logging: Implement robust try/catch blocks and record script execution steps to local or cloud logs for complete traceability.
Troubleshooting Common License and Connection Errors
Even seasoned administrators occasionally hit roadblocks when running Microsoft 365 automation scripts. Understanding how to handle these errors will save you hours of frustration.
- Permission Denied Errors: Verify that your executing account holds the required Azure AD directory roles and that your Graph connection requested the correct consent scopes.
- License Assignment Failures: Ensure the target user has a designated "Usage Location" configured. Microsoft 365 will reject license assignments if the user's country location is blank.
- Module Conflicts: If cmdlets fail due to conflicting module versions, completely uninstall legacy modules (like MSOnline) and standardize your environment on the modern Microsoft Graph SDK.
Conclusion
Automating license optimization and audits in Microsoft 365 bridges the gap between efficient IT administration and smart financial management. By leveraging PowerShell and cmdlets like Set-MgUserLicense, you can eradicate wasted software spend, enforce strict security compliance, and streamline user administration workflows across your enterprise. If you want to expand your technical horizons even further into modern tooling and infrastructure management, be sure to check out our related episode on how to Run Your Full Stack Locally with .NET Aspire. Stay proactive, keep your scripts secure, and transform your Microsoft 365 tenant into a well-oiled, cost-optimized engine!