Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Get all AAD B2B Guest Accounts which haven't signed in, in the last XX Days, or haven't accepted a B2B Guest Invitation in last XX Days. Associated Blogpost https://blog.darrenjrobinson.com/finding-stale-azure-ad-b2b-guest-accounts-based-on-lastsignindatetime
Function AuthN {
<#
.SYNOPSIS
Authenticate to Azure AD and receieve Access and Refresh Tokens.
.DESCRIPTION
Authenticate to Azure AD and receieve Access and Refresh Tokens.
.PARAMETER tenantID
(required) Azure AD TenantID.
.PARAMETER credential
(required) ClientID and ClientSecret of the Azure AD registered application with the necessary permissions.
.EXAMPLE
$myCred = Get-Credential
AuthN -credential $myCred -tenantID '74ea519d-9792-4aa9-86d9-abcdefgaaa'
.LINK
http://darrenjrobinson.com/
#>
[cmdletbinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$tenantID,
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Management.Automation.PSCredential]$credential
)
if (!(get-command Get-MsalToken)) {
Install-Module -name MSAL.PS -Force -AcceptLicense
}
try {
# Authenticate and Get Tokens
$token = Get-MsalToken -ClientId $credential.UserName -ClientSecret $credential.Password -TenantId $tenantID
return $token
}
catch {
$_
}
}
Function GetAADSignIns {
<#
.SYNOPSIS
Get AAD Account SignIn Activity.
.DESCRIPTION
Get AAD Account SignIn Activity.
.PARAMETER date
(required) date whereby users haven't signed in since to return objects for
.EXAMPLE
GetAADSignIns -date "2021-01-01T00:00:00Z"
.LINK
http://darrenjrobinson.com/
#>
[cmdletbinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$date
)
# Refresh Access Token
$global:myToken = AuthN -credential $myCred -tenantID $myTenantId
try {
# Get AAD B2B Users.
$aadSignInActivity = Invoke-RestMethod -Headers @{Authorization = "Bearer $($myToken.AccessToken)" } `
-Uri "https://graph.microsoft.com/beta/users?filter=signInActivity/lastSignInDateTime le $($date)" `
-Method Get
return $aadSignInActivity
}
catch {
$_
}
}
Function GetAADUserSignInActivity {
<#
.SYNOPSIS
Get AAD Account SignIn Activity.
.DESCRIPTION
Get AAD Account SignIn Activity.
.PARAMETER ID
(required) ObjectID of the user to get SignIn Activity for
.EXAMPLE
GetAADUserSignInActivity -ID "feeb81f9-af70-2d5a-aa8c-f035ddaabcde"
.LINK
http://darrenjrobinson.com/
#>
[cmdletbinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$ID
)
# Refresh Access Token
$global:myToken = AuthN -credential $myCred -tenantID $myTenantId
try {
# Get AAD SignIn Activity.
$aadSignInActivity = Invoke-RestMethod -Headers @{Authorization = "Bearer $($myToken.AccessToken)" } `
-Uri "https://graph.microsoft.com/beta/users/$($ID)?`$select=id,displayName,signInActivity" `
-Method Get
return $aadSignInActivity
}
catch {
$_
}
}
Function GetAADPendingGuests {
<#
.SYNOPSIS
Get AAD B2B Accounts where the inviation hasn't been accepted.
.DESCRIPTION
Get AAD B2B Accounts where the inviation hasn't been accepted.
.EXAMPLE
GetAADPendingGuests
.LINK
http://darrenjrobinson.com/
#>
[cmdletbinding()]
param()
# Refresh Access Token
$global:myToken = AuthN -credential $myCred -tenantID $myTenantId
try {
# Get AAD B2B Pending Users.
$aadPendingGuests = Invoke-RestMethod -Headers @{Authorization = "Bearer $($myToken.AccessToken)" } `
-Uri "https://graph.microsoft.com/beta/users?filter=externalUserState eq 'PendingAcceptance'&`$top=999" `
-Method Get
return $aadPendingGuests.value
}
catch {
$_
}
}
# Globals
# Tenant ID
$global:myTenantId = '<YOUR AZURE AD TENANT ID>'
# Registered AAD App ID and Secret
$global:myCred = [pscredential]::new("<YOUR AAD APP CLIENT ID>", ("<YOUR AAD APP CLIENT SECRET>" | ConvertTo-SecureString -AsPlainText -Force))
Import-Module MSAL.PS
<#
FIND State/Dormant B2B Accounts and Stale/Dormant B2B Guest Invitations
Get all AAD Accounts which haven't signed in, in the last XX Days, or haven't accepted a B2B Guest Invitation in last XX Days.
#>
# Number of days over which an Azure AD Account that hasn't signed in is considered stale
$staleAgeDays = '90'
[datetime]$today = (get-date).ToString('yyyy-MM-dd')
$staleDate = $today.AddDays( - "$($staleAgeDays)")
$strStaleDate = $staleDate.ToString('yyyy-MM-dd')
# Get InActivity Accounts
$inactiveAccounts = GetAADSignIns -date "$($strStaleDate)T00:00:00Z"
$inactiveAccounts.value.Count
# Filter Accounts down to Azure AD Guest Accounts
$guests = $inactiveAccounts.value | Select-Object | Where-Object { $_.userType -eq 'Guest' }
Write-Host -ForegroundColor Green "$($Guests.count) Guest accounts haven't signed in since $($strStaleDate)"
# Add lastSignInDateTime to the User PowerShell Object
foreach ($b2bUser in $guests) {
$signIns = $null
$signIns = GetAADUserSignInActivity -ID $b2bUser.id
$b2bUser | Add-Member -Type NoteProperty -Name "lastSignInDateTime" -Value $signIns.signInActivity.lastSignInDateTime
}
# Disabled Guest Accounts
$disabledGuests = $guests | Select-Object | Where-Object { $_.accountEnabled -eq $false }
Write-Host -ForegroundColor Yellow " $($disabledGuests.count) Guest accounts haven't signed in since $($strStaleDate) and are flagged as 'Account Disabled'."
# Guest Accounts inivited but pending acceptance
$pendingGuests = GetAADPendingGuests
Write-Host -ForegroundColor Green "$($pendingGuests.count) Guest accounts are still 'pending' B2B Guest invitation acceptance."
$stalePendingInvites = $pendingGuests | Select-Object | Where-Object {[datetime]$_.externalUserStateChangeDateTime -le [datetime]"$($strStaleDate)T00:00:00Z"}
Write-Host -ForegroundColor Yellow " $($stalePendingInvites.count) Guest accounts were invited before '$($strStaleDate)'"
# All Stale Accounts
$staleGuests = $null
$staleGuests += $guests
$staleGuests += $stalePendingInvites
Write-Host -ForegroundColor Green "$($staleGuests.count) Guest accounts are still 'pending' B2B Guest invitation acceptance or haven't signed in since '$($strStaleDate)'."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment