PowerShell Manage Microsoft 365 Users with MSOnline

PowerShell Manage Microsoft 365 Users with MSOnline

PowerShell Tips Editor 4 min read
PowerShell Manage Microsoft 365 Users with MSOnline

Bulk license assignment for 50 new hires, finding unlicensed accounts before the monthly billing cycle, or disabling a departed employee’s Microsoft 365 access in seconds — these are the everyday tasks that justify automating PowerShell Office 365 users management. This post focuses on practical M365 user administration using the MSOnline module alongside notes on its Microsoft.Graph successor, covering user queries, license assignment, account management, and reporting.

Connect to MSOnline

The MSOnline module connects to the Azure AD/M365 tenant. It requires modern authentication and will prompt for credentials and MFA if enabled on the account.

# Install MSOnline module if not already present
Install-Module -Name MSOnline -Scope CurrentUser -Force

# Connect to your tenant
Connect-MsolService

# Verify connection — returns your tenant domain
Get-MsolDomain | Where-Object IsDefault -eq $true

Note: Microsoft is retiring MSOnline. For new scripts, prefer the Microsoft.Graph module (see the Graph API post). MSOnline continues to function in existing environments but will eventually require migration.

Get All M365 Users

Get-MsolUser returns user accounts from your Microsoft 365 tenant. By default it returns up to 500 users — use -All to retrieve the full list. Results include cloud-only and synced-from-AD accounts.

# All users
Get-MsolUser -All | Select-Object DisplayName, UserPrincipalName, IsLicensed, BlockCredential

# Only enabled accounts
Get-MsolUser -All | Where-Object BlockCredential -eq $false |
    Select-Object DisplayName, UserPrincipalName, IsLicensed

Get Licensed vs Unlicensed Users

Filter on the IsLicensed property to identify users who need a license assigned or who are holding a license unnecessarily.

# Unlicensed users (enabled accounts with no license)
Get-MsolUser -All -UnlicensedUsersOnly |
    Select-Object DisplayName, UserPrincipalName, Country |
    Export-Csv -Path "C:\Logs\unlicensed-users.csv" -NoTypeInformation

# Licensed users
Get-MsolUser -All | Where-Object IsLicensed -eq $true |
    Select-Object DisplayName, UserPrincipalName,
        @{ N="Licenses"; E={ ($_.Licenses.AccountSkuId) -join "; " } }

Assign a License

License assignment requires the AccountSkuId identifier, which is in the format TenantName:SKU_NAME. Retrieve available SKUs with Get-MsolAccountSku before assigning.

# See available licenses and remaining quantities
Get-MsolAccountSku | Select-Object AccountSkuId, ActiveUnits, ConsumedUnits,
    @{ N="Available"; E={ $_.ActiveUnits - $_.ConsumedUnits } }
AccountSkuId                 ActiveUnits  ConsumedUnits  Available
------------                 -----------  -------------  ---------
contoso:ENTERPRISEPACK       500          487            13
contoso:EXCHANGESTANDARD     50           22             28
# Assign a license to a user
Set-MsolUserLicense `
    -UserPrincipalName "[email protected]" `
    -AddLicenses "contoso:ENTERPRISEPACK"

# Assign licenses to multiple users from a list
$upns = @("[email protected]", "[email protected]", "[email protected]")
foreach ($upn in $upns) {
    Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses "contoso:ENTERPRISEPACK"
    Write-Output "Licensed: $upn"
}

Disable and Delete M365 User

Blocking a user prevents sign-in without deleting the account (useful during offboarding review). Deletion removes the account but it is recoverable from the recycle bin for 30 days.

# Block sign-in (disable account)
Set-MsolUser -UserPrincipalName "[email protected]" -BlockCredential $true

# Remove license first (optional but good practice — frees the license)
Set-MsolUserLicense -UserPrincipalName "[email protected]" -RemoveLicenses "contoso:ENTERPRISEPACK"

# Delete the user (soft-delete — recoverable for 30 days)
Remove-MsolUser -UserPrincipalName "[email protected]" -Confirm:$false

Export User Report to CSV

A complete user export with license and account status is the most common M365 admin report request.

$reportPath = "C:\Logs\m365-users-$(Get-Date -Format yyyyMMdd).csv"

Get-MsolUser -All |
    Select-Object DisplayName, UserPrincipalName, IsLicensed, BlockCredential,
        @{ N="Licenses"; E={ ($_.Licenses.AccountSkuId) -join "; " } },
        @{ N="CreatedDate"; E={ $_.WhenCreated } } |
    Export-Csv -Path $reportPath -NoTypeInformation

Write-Output "Report saved to $reportPath"

Common Errors and Fixes

  • MSOnline being replaced by Microsoft.Graph: Microsoft deprecated the MSOnline module and its retirement date has been announced for late 2025. For any new scripting, use the Microsoft.Graph module with equivalent cmdlets like Get-MgUser, New-MgUser, and license assignment via Set-MgUserLicense. Existing MSOnline scripts will need migration — the command structures are similar but not identical.
  • License SKU identifier format is non-obvious: The AccountSkuId format (TenantName:PRODUCT_CODE) is not user-friendly. The PRODUCT_CODE part (like ENTERPRISEPACK for E3, SPB for Microsoft 365 Business Premium) must match exactly. Run Get-MsolAccountSku on your tenant to see your exact SKU identifiers — they include your tenant name prefix which differs per organization.

Related Cmdlets / See Also

Wrapping Up

MSOnline covers the immediate licensing and user management needs in M365 — but start planning your migration to Microsoft.Graph for any scripts you expect to run beyond 2025. As a next step, build a monthly license review script that identifies users who haven’t signed in for 60 days and are consuming a paid license, giving you the data to reclaim unused licenses before the next billing cycle.

Send-Item -To