All scripts
Microsoft 365 729

Teams User Presence Report

Pulls current Microsoft Teams presence and activity status for a list of users via Microsoft Graph and exports it to CSV, handy for checking who's actually online before a broadcast or major announcement.

Get-TeamsPresenceReport.ps1
<#
.SYNOPSIS
Reports the current Microsoft Teams presence status for a list of users.

.DESCRIPTION
Uses the Microsoft Graph presence API to pull the current availability and
activity status for a set of users, then exports the results to a CSV file.
Useful for checking whether a distribution list or department is actually
online during business hours, or for auditing status before a broadcast or
major announcement. Requires the Microsoft.Graph.CloudCommunications and
Microsoft.Graph.Users modules and the Presence.Read.All permission.

.PARAMETER UserPrincipalNames
One or more user principal names to check presence for.

.PARAMETER OutputPath
Path to the CSV file the report is written to. Defaults to a file in the
current directory named TeamsPresenceReport.csv.

.EXAMPLE
.\Get-TeamsPresenceReport.ps1 -UserPrincipalNames "alice@contoso.com","bob@contoso.com"

.EXAMPLE
Get-Content .\users.txt | .\Get-TeamsPresenceReport.ps1 -OutputPath "C:\Reports\presence.csv"

.AUTHOR
Shehryar Hassan
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string[]]$UserPrincipalNames,

    [Parameter(Mandatory = $false)]
    [string]$OutputPath = ".\TeamsPresenceReport.csv"
)

begin {
    $allUsers = @()

    if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.CloudCommunications)) {
        Write-Error "Microsoft.Graph.CloudCommunications module not found. Install it with: Install-Module Microsoft.Graph.CloudCommunications"
        return
    }

    Import-Module Microsoft.Graph.CloudCommunications -ErrorAction Stop
    Import-Module Microsoft.Graph.Users -ErrorAction Stop
    Connect-MgGraph -Scopes "Presence.Read.All" -NoWelcome

    $results = New-Object System.Collections.Generic.List[object]
}

process {
    foreach ($upn in $UserPrincipalNames) {
        $allUsers += $upn
    }
}

end {
    if ($allUsers.Count -eq 0) {
        Write-Warning "No user principal names were provided."
        return
    }

    Write-Host "Checking presence for $($allUsers.Count) user(s)..."

    foreach ($upn in $allUsers) {
        try {
            $mgUser = Get-MgUser -UserId $upn -Property Id, DisplayName, UserPrincipalName -ErrorAction Stop
            $presence = Get-MgUserPresence -UserId $mgUser.Id -ErrorAction Stop

            $results.Add([PSCustomObject]@{
                DisplayName       = $mgUser.DisplayName
                UserPrincipalName = $mgUser.UserPrincipalName
                Availability      = $presence.Availability
                Activity          = $presence.Activity
                CheckedAt         = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
            })
        }
        catch {
            Write-Warning "Could not get presence for $upn : $($_.Exception.Message)"
        }
    }

    $results | Sort-Object DisplayName | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8

    Write-Host "Presence report saved to $OutputPath"
    Write-Host "Availability breakdown:"
    $results | Group-Object Availability | Sort-Object Count -Descending | ForEach-Object {
        Write-Host "  $($_.Name): $($_.Count)"
    }
}

Read it before you run it, and test in a safe tenant first.