#Requires -Version 7.0
class CucmConnector {
[string] $BaseUrl
[PSCredential] $Credential
CucmConnector(
[string] $BaseUrl,
[PSCredential] $Credential
) {
$this.BaseUrl = $BaseUrl
$this.Credential = $Credential
}
[System.Xml.XmlDocument] ListPhone([int]$Skip, [int]$First, [string]$ApiVersion) {
$uri = "{0}/axl/" -f $this.BaseUrl
$headers = @{
'Content-Type' = 'text/xml'
'SOAPAction' = "CUCM:DB ver=${ApiVersion} listPhone"
}
$requestBody = @"
SEP%
${Skip}
${First}
"@
return Invoke-RestMethod -Method 'Post' -Authentication Basic -Credential $this.Credential -Headers $headers -Uri $uri -Body $requestBody -SkipCertificateCheck
}
[System.Xml.XmlDocument] SelectCmDevice([string[]]$DeviceNames) {
$uri = "{0}/realtimeservice2/services/RISService70" -f $this.BaseUrl
$headers = @{
'Content-Type' = 'text/xml; charset=UTF-8'
}
$bodySelectItemsList = $DeviceNames | ForEach-Object { @"
${PSItem}
"@}
$requestBody = @"
2000
Phone
255
Any
Name
${bodySelectItemsList}
Any
Any
"@
$maxNumAttemptsForRateLimitError = 7
for ($attempt = 1; $attempt -le $maxNumAttemptsForRateLimitError; $attempt++) {
try {
return Invoke-RestMethod -Method 'Post' -Authentication Basic -Credential $this.Credential -Headers $headers -Uri $uri -Body $requestBody -SkipCertificateCheck
}
catch {
# Handle rate limit errors
if ($_.ToString().Contains('Exceeded allowed rate for Rea')) {
if ($attempt -lt $maxNumAttemptsForRateLimitError) {
Write-Host "Encountered rate limit error when getting device registration statuses [attempt=${attempt}]. Retrying in 10 sec..."
Start-Sleep -Seconds 10
continue
}
else {
Write-Host -ForegroundColor Red "Reached max. attempts ${maxNumAttemptsForRateLimitError} for rate limit error when getting device registration statuses"
throw
}
}
throw
}
}
throw "An unexpected error occurred when getting device registration statuses"
}
}
function Get-Devices {
param (
[CucmConnector]$CucmConnector,
[string]$ApiVersion
)
$allDevices = New-Object System.Collections.Generic.List[object]
$skip = 0
$first = 2000
$response = $CucmConnector.ListPhone($skip, $first, $ApiVersion)
while (!($response.GetElementsByTagName('return').IsEmpty)) {
$pageDevices = [object[]]($response.GetElementsByTagName('return').phone)
$allDevices.AddRange($pageDevices)
$skip += $first
$response = $CucmConnector.ListPhone($skip, $first, $ApiVersion)
}
return $allDevices
}
function Get-DeviceRegistrationStatuses {
param (
[string[]]$DeviceNames,
[CucmConnector]$CucmConnector
)
$registrationStatuses = @{}
$maxReturnedDevices = 2000 # The endpoint allows querying a max of 2000 devices at a time
$windowStartIndex = 0
while ($windowStartIndex -lt $DeviceNames.Length) {
$progressParameters = @{
Activity = 'Fetching device registrations statuses...'
Status = "Fetched: $windowStartIndex of $($DeviceNames.Length)"
PercentComplete = ($windowStartIndex / $DeviceNames.Length) * 100
}
Write-Progress @progressParameters
$windowEndIndex = $windowStartIndex + $maxReturnedDevices - 1
# powershell doesn't throw an out-of-bounds error when accessing an index that is past the end of array
$devicesToQuery = $DeviceNames[$windowStartIndex..$windowEndIndex] # upper bound is inclusive
$response = $CucmConnector.SelectCmDevice($devicesToQuery)
$totalDevicesFound = $response.Envelope.Body.selectCmDeviceResponse.selectCmDeviceReturn.SelectCmDeviceResult.TotalDevicesFound
if ($totalDevicesFound -gt 0) {
$devicesPerNode = $response.Envelope.Body.selectCmDeviceResponse.selectCmDeviceReturn.SelectCmDeviceResult.CmNodes.item.CmDevices
| Where-Object { $_.HasChildNodes }
foreach ($nodeDevices in $devicesPerNode) {
$nodeDevices.item | Select-Object -Property Name, Status, StatusReason, TimeStamp
| ForEach-Object {
if (!$registrationStatuses.Contains($_.Name)) {
$registrationStatuses.Add($_.Name, $_)
}
}
}
}
$windowStartIndex = $windowStartIndex + $maxReturnedDevices
Start-Sleep -Seconds 1 # a pinch of throttling to help with rate limits
}
return $registrationStatuses
}
$serverUrl = Read-Host 'CUCM server URL (https://mycucm.company.com)'
$credential = Get-Credential -Message 'Enter username and password'
$cucmConnector = [CucmConnector]::new($serverUrl, $credential)
try {
Write-Host "Fetching devices from CUCM server..."
try {
$devices = Get-Devices -CucmConnector $cucmConnector -ApiVersion '11.5'
} catch {
if ($_.ToString().Contains('Incorrect axl version. Supported axl versions are 12.x, 14.0 and 15.0')) {
$devices = Get-Devices -CucmConnector $cucmConnector -ApiVersion '15.0'
} else {
throw
}
}
Write-Host "Found [$($devices.Length)] devices"
Write-Host "Getting device registration statuses..."
$registrationStatuses = Get-DeviceRegistrationStatuses -DeviceNames $devices.name -CucmConnector $cucmConnector
$exportResults = New-Object System.Collections.Generic.List[Hashtable]
foreach ($device in $devices) {
if ($registrationStatuses.ContainsKey($device.name)) {
$matchingStatus = $registrationStatuses[$device.name]
$exportResults.Add(@{
Name = $device.name
Status = $matchingStatus.Status
StatusReason = $matchingStatus.StatusReason
TimeStamp = $matchingStatus.TimeStamp
Model = $device.model
})
}
else {
$exportResults.Add(@{
Name = $device.name
Status = "Unknown"
StatusReason = ""
TimeStamp = ""
Model = $device.model
})
}
}
# Export to JSON file
$serverHost = ([System.Uri]$serverUrl).Host
$date = (Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').ToString()
$outputFileName = "${date}_${serverHost}.json"
$outputFile = $exportResults | ConvertTo-Json -depth 100 -AsArray | New-Item -Path . -Name $outputFileName -ItemType File
Write-Host "RISport information export was successful: $outputFile"
exit 0
}
catch {
$responseCode = $_.Exception.Response.StatusCode.value__
if ($responseCode -eq 401) {
Write-Error "Invalid credentials (401 Unauthorized)"
}
elseif ($responseCode -eq 403) {
Write-Error "Insufficient permissions (403 Forbidden)"
}
else {
Write-Error "Ran into errors when exporting RISport information: $_"
}
Write-Error "RISport information export failed"
exit 1
}