List inactive Guest\Users\Members Users and Devices in Azure AD \ Entra

# This script requires PowerShell 7 or later for the following reasons:
# 1. The Microsoft Graph PowerShell SDK (Microsoft.Graph module) is designed for PowerShell 7+ and may not work reliably in Windows PowerShell 5.1.
# 2. The SignInActivity property is not returned or accessible in PowerShell 5.1 due to .NET and serialization limitations.
# 3. Modern authentication methods used by Connect-MgGraph are only fully supported in PowerShell 7+.
# 4. Paging and the -All switch for Get-MgUser are more reliable in PowerShell 7+.
# 5. Microsoft is focusing new features and bug fixes for the Graph SDK on PowerShell 7+ only.
# Attempting to run this script in Windows PowerShell 5.1 or earlier will result in errors or missing data.

# Check for PowerShell 7+
if ($PSVersionTable.PSVersion.Major -lt 7) {
    Write-Host "ERROR: This script requires PowerShell 7 or later. Please run it in PowerShell 7+ (pwsh.exe)." -ForegroundColor Red
    exit 1
}

# Check if Microsoft.Graph module is installed
$module = Get-Module -Name Microsoft.Graph -ListAvailable
if (-not $module) {
    Write-Host "Microsoft.Graph module not found. Installing..."
    try {
        Install-Module -Name Microsoft.Graph -Scope CurrentUser -Force -ErrorAction Stop
        Write-Host "Microsoft.Graph module installed successfully."
    }
    catch {
        Write-Host "ERROR: Failed to install Microsoft.Graph module. Error: $_" -ForegroundColor Red
        exit 1
    }
}
else {
    Write-Host "Microsoft.Graph module is already installed."
}

# Import the module if not already loaded
if (-not (Get-Command -Name Connect-MgGraph -ErrorAction SilentlyContinue)) {
    Import-Module Microsoft.Graph -ErrorAction Stop
}

# Connect to Microsoft Graph with required scopes
Write-Host "Connecting to Microsoft Graph..."
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All", "Device.Read.All", "Organization.Read.All"

# Define log file
$logFile = "InactiveUsersAndOldDevices_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Dynamically retrieve the organization's primary domain
Write-Host "Retrieving organization details to determine primary domain..."
$org = Get-MgOrganization
$verifiedDomains = $org.VerifiedDomains
$initialDomain = $verifiedDomains | Where-Object { $_.IsInitial -eq $true } | Select-Object -ExpandProperty Name -First 1
$customDomains = $verifiedDomains | Where-Object { $_.IsInitial -eq $false } | Select-Object -ExpandProperty Name

if ($customDomains.Count -gt 0) {
    $orgDomain = $customDomains[0]  # Select the first custom domain as primary; adjust logic if needed for multiple domains
} else {
    $orgDomain = $initialDomain  # Fallback to initial domain if no custom domains exist
}

Write-Host "Primary domain dynamically set to: $orgDomain"

# Threshold: 6 months ago
$inactiveThreshold = (Get-Date).AddMonths(-6)

# Initialize log
Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Starting Inactive Users + Old Enabled Devices Report."
Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Primary domain: $orgDomain"

#####################################################################
# 1. INACTIVE ENABLED USERS (6+ months no sign-in)
#####################################################################
Write-Host "Retrieving inactive enabled users..." -ForegroundColor Cyan

$allUsers = Get-MgUser -All -Property Id, DisplayName, SignInActivity, AccountEnabled, UserType, UserPrincipalName -ConsistencyLevel eventual -CountVariable userCount |
    Where-Object {
        $_.AccountEnabled -eq $true -and
        $_.SignInActivity.LastSignInDateTime -and
        ([datetime]$_.SignInActivity.LastSignInDateTime) -lt $inactiveThreshold
    }

$guestUsers = $allUsers | Where-Object UserType -eq 'Guest'
$memberUsers = $allUsers | Where-Object UserType -eq 'Member'

# Log Guest Users
Add-Content -Path $logFile -Value "`n$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Inactive Guest Users (6+ months no sign-in):"
if ($guestUsers.Count -eq 0) {
    $msg = "No inactive Guest users found."
    Write-Host $msg
    Add-Content -Path $logFile -Value $msg
} else {
    foreach ($user in $guestUsers) {
        $msg = "Guest: $($user.DisplayName) | UPN: $($user.UserPrincipalName) | Last sign-in: $($user.SignInActivity.LastSignInDateTime)"
        Write-Host $msg -ForegroundColor Yellow
        Add-Content -Path $logFile -Value $msg
    }
}

# Log Member Users
Add-Content -Path $logFile -Value "`n$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Inactive Member Users (6+ months no sign-in):"
if ($memberUsers.Count -eq 0) {
    $msg = "No inactive Member users found."
    Write-Host $msg
    Add-Content -Path $logFile -Value $msg
} else {
    foreach ($user in $memberUsers) {
        $msg = "Member: $($user.DisplayName) | UPN: $($user.UserPrincipalName) | Last sign-in: $($user.SignInActivity.LastSignInDateTime)"
        Write-Host $msg -ForegroundColor Yellow
        Add-Content -Path $logFile -Value $msg
    }
}


#####################################################################
# 2. OLD ENABLED DEVICES (6+ months no sign-in)
#####################################################################
Write-Host "Retrieving old enabled devices (no sign-in for 6+ months)..." -ForegroundColor Cyan

# Get all Entra ID devices
$devices = Get-MgDevice -All -Property Id, DisplayName, ApproximateLastSignInDateTime, AccountEnabled, OperatingSystem, TrustType, ProfileType, DeviceOwnership

# Filter for enabled stale devices
$oldDevices = @()
foreach ($device in $devices) {
    if ($device.AccountEnabled -eq $true -and
        $device.ApproximateLastSignInDateTime -ne $null -and
        ([datetime]$device.ApproximateLastSignInDateTime) -lt $inactiveThreshold) {

        # Get owner if available
        $ownerUPN = "Unknown"
        $ownerDisplayName = "Unknown"
        try {
            $ownerRef = Get-MgDeviceRegisteredOwner -DeviceId $device.Id -Top 1
            if ($ownerRef) {
                $ownerId = $ownerRef.Id
                $owner = Get-MgUser -UserId $ownerId -Property DisplayName, UserPrincipalName
                $ownerDisplayName = $owner.DisplayName
                $ownerUPN = $owner.UserPrincipalName
            }
        } catch {
            Write-Warning "Failed to get owner for device $($device.DisplayName): $_"
        }

        $oldDevices += [PSCustomObject]@{
            DeviceName = $device.DisplayName
            DeviceId = $device.Id
            OperatingSystem = $device.OperatingSystem
            TrustType = $device.TrustType
            ProfileType = $device.ProfileType
            DeviceOwnership = $device.DeviceOwnership
            LastSignInDateTime = $device.ApproximateLastSignInDateTime
            OwnerDisplayName = $ownerDisplayName
            OwnerUPN = $ownerUPN
        }
    }
}

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...