All scripts
Microsoft 365 604

Microsoft Bookings Appointment Report

Pulls appointments across every Microsoft Bookings calendar in the tenant using Microsoft Graph, and exports them to a CSV so you can see booking volume without opening each calendar by hand.

Get-BookingsAppointmentReport.ps1
<#
.SYNOPSIS
Exports appointments from every Microsoft Bookings calendar in the tenant to a CSV file.

.DESCRIPTION
Connects to Microsoft Graph, lists every Bookings business (calendar) in the tenant, then
pulls appointments for each one within a given date range using the calendar view endpoint.
Useful for seeing total booking volume, which staff are actually getting used, or just
answering "how many appointments did we take last month" without opening every Bookings
calendar one at a time.

.AUTHOR
Shehryar Hassan

.EXAMPLE
.\Get-BookingsAppointmentReport.ps1 -StartDate (Get-Date) -EndDate (Get-Date).AddDays(30) -OutputPath C:\Reports\bookings-report.csv
#>

param(
    [datetime]$StartDate = (Get-Date),
    [datetime]$EndDate = (Get-Date).AddDays(30),
    [string]$OutputPath = ".\BookingsAppointmentReport.csv"
)

if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) {
    Write-Host "Installing Microsoft.Graph.Authentication module..."
    Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force
}

Import-Module Microsoft.Graph.Authentication
Connect-MgGraph -Scopes "Bookings.Read.All"

$startIso = $StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
$endIso = $EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")

$businesses = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/solutions/bookingBusinesses").value

if (-not $businesses -or $businesses.Count -eq 0) {
    Write-Host "No Bookings calendars found in this tenant."
    return
}

$report = @()

foreach ($business in $businesses) {
    $businessId = $business.id
    $businessName = $business.displayName
    Write-Host "Checking $businessName..."

    $uri = "https://graph.microsoft.com/v1.0/solutions/bookingBusinesses/$businessId/calendarView?start=$startIso&end=$endIso"

    try {
        $appointments = (Invoke-MgGraphRequest -Method GET -Uri $uri).value
    }
    catch {
        Write-Warning "Could not read appointments for $businessName, skipping. Error: $($_.Exception.Message)"
        continue
    }

    foreach ($appt in $appointments) {
        $staffNames = ($appt.staffMemberIds -join ", ")
        $report += [PSCustomObject]@{
            BusinessName  = $businessName
            ServiceName   = $appt.serviceName
            CustomerName  = $appt.customerName
            CustomerEmail = $appt.customerEmailAddress
            Start         = $appt.start.dateTime
            End           = $appt.end.dateTime
            StaffIds      = $staffNames
            IsOnline      = $appt.isLocationOnline
            Duration      = $appt.duration
        }
    }
}

if ($report.Count -eq 0) {
    Write-Host "No appointments found in the given date range."
    return
}

$report | Sort-Object BusinessName, Start | Export-Csv -Path $OutputPath -NoTypeInformation

Write-Host "Exported $($report.Count) appointments to $OutputPath"

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