Monitoring with PowerShell: Monitoring Office 365 deleted users & License usage

I’ve been getting some requests to talk more about monitoring access and license management for Office 365. Some of you have asked how to be notified when users get deleted, or to get a notification right before a user is deleted permanently. Another question was on how to check if all licenses are assigned and you’re not wasting any resources or money on unused licenses. I’ve decided to blog about both ūüôā

Monitoring deleted users

So the first one up is monitoring the deleted users – I understand monitoring this for a multitude of reasons. Imagine you’re distributing licenses and when a user gets deleted you need to update your billing systems, or imagine that you have a specific off-boarding procedure that needs to be kicked off when a user is deleted. I’ve created two cases; One to alert as soon as a deleted user has been found, another to alert when the user is about to be permanently deleted.

Monitoring all deleted users
##############################
$ApplicationId = 'XXXX-XXXX-XXXX-XXX-XXX'
$ApplicationSecret = 'YourApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID = 'YourTenantID.Onmicrosoft.com'
$RefreshToken = 'VeryLongRefreshToken'
##############################
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
$DeletedUserlist = foreach ($customer in $customers) {
    write-host "Getting Deleted users for $($Customer.name)" -ForegroundColor Green
    $DeletedUsers = Get-MsolUser -ReturnDeletedUsers -TenantId $($customer.TenantID)
    foreach($User in $DeletedUsers){ "$($user.UserPrincipalName) has been deleted on $($User.SoftDeletionTimestamp)" }
}
if(!$DeletedUserlist) {  $DeletedUserlist = "Healthy" }
Monitoring near permanent delete date.
##############################
$Daystomonitor = (Get-Date).AddDays(-28) #This means we will alert when a user has been deleted for 28 days, and is 1 day before permanent deletion.
##############################
$ApplicationId = 'XXXX-XXXX-XXXX-XXX-XXX'
$ApplicationSecret = 'YourApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID = 'YourTenantID.Onmicrosoft.com'
$RefreshToken = 'VeryLongRefreshToken'
##############################
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
$DeletedUserlist = foreach ($customer in $customers) {
    write-host "Getting Deleted users for $($Customer.name)" -ForegroundColor Green
    $DeletedUsers = Get-MsolUser -ReturnDeletedUsers -TenantId $($customer.TenantID) | Where-Object {$($User.SoftDeletionTimestamp) -lt $Daystomonitor}
    foreach ($User in $DeletedUsers) { "$($user.UserPrincipalName) has been deleted on $($User.SoftDeletionTimestamp)" }
}
if (!$DeletedUserlist) { $DeletedUserlist= "Healthy" }


 

To explain the scripts; The first script connects to each tenant in your Microsoft Partner portal, grabs all deleted users, and gives you a report of all deleted users. The second one does the same, but filters specifically on users that have been deleted for 28 days.

Monitoring unused licenses

So this is one that we are going to use internally after getting some requests from all of you – I think it’s a pretty smart idea to monitor if licenses are in use, and if not to alert on them. It helps to compare the administrative side of licensing to the actual cost of licensing.

##############################
$ApplicationId = 'XXXX-XXXX-XXXX-XXX-XXX'
$ApplicationSecret = 'YourApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID = 'YourTenantID.Onmicrosoft.com'
$RefreshToken = 'VeryLongRefreshToken'
##############################
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
$UnusedLicensesList = foreach ($customer in $customers) {
    write-host "Getting licenses $($customer.name)" -ForegroundColor Green
    $Licenses = Get-MsolAccountSku -TenantId $($customer.TenantId)
    foreach ($License in $Licenses) { 
        if ($License.ActiveUnits -lt $License.consumedUnits) { "$($customer.name) - $($License.AccountSkuId) has licenses available." }

    }
}
if (!$UnusedLicensesList) { $UnusedLicensesList = "Healthy" } 

And that’s it! a somewhat longer blog than normal, with multiple scripts. But I hope you’ve enjoyed it. As always, Happy Powershelling.

Kelvin Tegelaar
Follow me

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.