Monitoring with PowerShell: Monitoring Azure AD Devices and users age.

So we’re managing more and more cloud only clients. This is fantastic because you don’t have to worry about all the old worries like keeping a server online and updated. Another cool thing is that it becomes a lot easier to manage devices and endpoints.

The thing is, even with Azure AD you still have maintenance tasks that never seem to disappear. This time, we’re picking up the age old issue of keeping your Active Directory cleaned up. In this case; The Azure Active Directory.

With the following script we detect a couple of things; any user that has not logged in for 90 days, but also any device that has not logged into the Azure AD for 90 days. Finding these older devices gives you the ability to see if your off-boarding procedures are running well and you’re not having a total mess.

A good real life example came to me recently; one of our employees had a device stolen and I logged into the intune portal to start a remote wipe. The problem was that this user had around 10 devices in the portal and I could not be sure which was the current one. If I had maintained the portal and ran this script more often, finding the device would’ve been much easier.

Lets get to the script! As always I’ll publish two versions

Single tenant script

########################## Secure App Model Settings ############################
$ApplicationId = 'YourApplicationID'
$ApplicationSecret = 'YourApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID = 'YourTenantID'
$RefreshToken = 'YourRefreshToken'
$UPN = "YourUPN"
$CustomerTenant = "Customer.onmicrosoft.com"
########################## Script Settings  ############################
$Date = (get-date).AddDays(-90)
$Baseuri = "https://graph.microsoft.com/beta"
write-host "Generating token to log into Azure AD. Grabbing all tenants" -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $CustomerTenant
write-host "$($Tenant.Displayname): Starting process." -ForegroundColor Green
$Header = @{
    Authorization = "Bearer $($CustGraphToken.AccessToken)"
}
write-host " $($Tenant.Displayname): Grabbing all Users that have not logged in for 90 days." -ForegroundColor Green
$UserList = (Invoke-RestMethod -Uri "$baseuri/users/?`$select=displayName,UserPrincipalName,signInActivity" -Headers $Header -Method get -ContentType "application/json").value | select-object DisplayName, UserPrincipalName, @{Name = 'LastLogon'; Expression = { [datetime]::Parse($_.SignInActivity.lastSignInDateTime) } } | Where-Object { $_.LastLogon -lt $Date }
$devicesList = (Invoke-RestMethod -Uri "$baseuri/devices" -Headers $Header -Method get -ContentType "application/json").value | select-object Displayname, @{Name = 'LastLogon'; Expression = { [datetime]::Parse($_.approximateLastSignInDateTime) } }

$OldObjects = [PSCustomObject]@{
Users = $UserList | where-object { $_.LastLogon -ne $null }
Devices = $devicesList | Where-Object { $_.LastLogon -lt $Date }
}

if (!$OldObjects) { write-host "No old objects found in any tenant" } else { write-host "Old objects found."; $Oldobjects }

All tenants script

########################## Secure App Model Settings ############################
$ApplicationId = 'YourApplicationID'
$ApplicationSecret = 'YourApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID = 'YourTenantID'
$RefreshToken = 'YourRefreshToken'
$UPN = "YourUPN"
########################## Script Settings  ############################
$Date = (get-date).AddDays((-90))
$Baseuri = "https://graph.microsoft.com/beta"
write-host "Generating token to log into Azure AD. Grabbing all tenants" -ForegroundColor Green
$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-AzureAD -AadAccessToken $aadGraphToken.AccessToken -AccountId $upn -MsAccessToken $graphToken.AccessToken -TenantId $tenantID | Out-Null
$tenants = Get-AzureAdContract -All:$true
Disconnect-AzureAD
$OldObjects = foreach ($Tenant in $Tenants) {

    $CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $tenant.CustomerContextId
    write-host "$($Tenant.Displayname): Starting process." -ForegroundColor Green
    $Header = @{
        Authorization = "Bearer $($CustGraphToken.AccessToken)"
    }
    write-host " $($Tenant.Displayname): Grabbing all Users that have not logged in for 90 days." -ForegroundColor Green
    $UserList = (Invoke-RestMethod -Uri "$baseuri/users/?`$select=displayName,UserPrincipalName,signInActivity" -Headers $Header -Method get -ContentType "application/json").value | select-object DisplayName,UserPrincipalName,@{Name='LastLogon';Expression={[datetime]::Parse($_.SignInActivity.lastSignInDateTime)}} | Where-Object { $_.LastLogon -lt $Date }
    $devicesList = (Invoke-RestMethod -Uri "$baseuri/devices" -Headers $Header -Method get -ContentType "application/json").value | select-object Displayname,@{Name='LastLogon';Expression={[datetime]::Parse($_.approximateLastSignInDateTime)}}


    [PSCustomObject]@{
        Users = $UserList | where-object {$_.LastLogon -ne $null}
        Devices = $devicesList | Where-Object {$_.LastLogon -lt $Date}
    }
}

if(!$OldObjects) { write-host "No old objects found in any tenant"} else { write-host "Old objects found."; $Oldobjects}

And that’s it! as always, Happy PowerShelling!

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.