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.