Public/Reports/Get-CameraReport.ps1

function Get-CameraReport {
    <#
    .SYNOPSIS
        Gets a detailed report containing information about all cameras on one or more Recording Servers
 
    .DESCRIPTION
        Gets a [PSCustomObject] with a wide range of properties for each camera found on one or more
        Recording Servers. When the -RecordingServer parameter is omitted, all cameras on all servers in
        the currently selected site will be included in the report. Otherwise, only the server(s) included
        in the -RecordingServer array will enumerated for cameras to include in the report.
 
        A number of switches can optionally be included to add information to the default result set. For
        example, camera passwords are only included when the -IncludePasswords switch is present.
 
    .PARAMETER RecordingServer
        Specifies one Recording Server, or an array of Recording Servers. All cameras on the specified
        servers will be included in the report. If omitted, all cameras from all Recording Servers on the
        currently selected site will be included in the report. To include only cameras from a subset of
        Recording Servers, use Get-RecordingServer to select and filter for the desired servers.
 
    .PARAMETER IncludeNetworkState
        When this switch is present, the host and port from the hardware address of all cameras will be used
        in a call to Test-NetConnection to determine if a device is reachable or unreachable. Note that if
        the camera network is unreachable from the location where this command is executed, it will always
        return 'Offline'. If camera networks are segmented and accessible only by Recording Servers, then
        you should run this cmdlet directly on the Recording Servers. You can use the RecordingServer
        parameter to ensure each Recording Server produces a report for only the cameras added to itself.
 
    .PARAMETER IncludeActualResolutions
        When this switch is present, a live and playback image will be requested from the Recording Server
        for every enabled camera in the report. The resolution in the format WidthxHeight for a single image
        will be included columns named ActualLiveResolution and ActualRecordingResolution.
 
    .PARAMETER IncludeDisabledCameras
        Disabled cameras, which are considered to be any camera where either the camera, or it's parent
        Hardware object are disabled in Milestone, are excluded from the report by default. To include
        properties from disabled cameras in addition to enabled cameras, you must supply this switch.
 
        For disabled cameras, we will not attempt to retrieve the Actual*Resolution values or the first
        and last image timestamps from the media database.
 
    .PARAMETER IncludePasswords
        Passwords are only included in the report when this switch is present. Otherwise, only the UserName
        value will be included.
 
    .PARAMETER IncludePlaybackInfo
        When present, the IncludePlaybackInfo switch will cause the Get-PlaybackInfo command to be executed
        on each enabled camera and the first and last image timestamps from the media database will be
        included in the report.
 
    .PARAMETER AdditionalHardwareProperties
        Specifies an array of setting keys which should be included as columns in the report. The available
        keys vary by hardware driver. You can discover the available columns using the Get-HardwareSetting
        command.
 
    .PARAMETER AdditionalCameraProperties
        Specifies an array of setting keys which should be included as columns in the report. The available
        keys vary by hardware driver. You can discover the available columns using the Get-CameraSetting -General
        command.
 
    .EXAMPLE
        Get-RecordingServer -Name NVR01 | Get-CameraReport -IncludePasswords -AdditionalHardwareProperties HTTPSEnabled
 
        Generates a report including all cameras on NVR01, and the report will include hardware passwords, and the
        HTTPSEnabled value for all hardware devices which have this setting available.
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [VideoOS.Platform.ConfigurationItems.RecordingServer[]]
        $RecordingServer,
        [Parameter()]
        [switch]
        $IncludeNetworkState,
        [Parameter()]
        [switch]
        $IncludeActualResolutions,
        [Parameter()]
        [switch]
        $IncludeDisabledCameras,
        [Parameter()]
        [switch]
        $IncludePasswords,
        [Parameter()]
        [switch]
        $IncludePlaybackInfo,
        [Parameter()]
        [string[]]
        $AdditionalHardwareProperties,
        # Additional camera setting values to retrieve using exact key names
        [Parameter()]
        [string[]]
        $AdditionalCameraProperties
    )

    process {
        $states = Get-ItemState -CamerasOnly
        $networkStates = @{}
        $storageRetention = @{}
        $RecordingServer = if ($null -eq $RecordingServer) { Get-RecordingServer } else { $RecordingServer }
        foreach ($rec in $RecordingServer) {
            foreach ($hw in $rec | Get-Hardware) {
                if ($hw.Enabled -or $IncludeDisabledCameras) {
                    $driver = $hw | Get-HardwareDriver
                    $hwSettings = $hw | Get-HardwareSetting
                    $pass = if ($IncludePasswords) { $hw | Get-HardwarePassword } else { $null }
                }
                foreach ($cam in $hw | Get-Camera) {
                    $enabled = $hw.Enabled -and $cam.Enabled
                    if (-not $enabled -and -not $IncludeDisabledCameras) { continue }
                    

                    $liveSize = $null
                    $playbackSize = $null
                    if ($enabled -and $IncludeActualResolutions) {
                        $live = $cam | Get-Snapshot -Live -LiveTimeoutMS 10000
                        $playback = $cam | Get-Snapshot -Behavior GetEnd
                        $liveSize = if ($null -ne $live.Content -and $live.Content.Length -gt 0) { "$($live.Width)x$($live.Height)" } else { $null }
                        $playbackSize = if ($null -ne $playback.Bytes -and $playback.Bytes.Length -gt 0) { "$($playback.Width)x$($playback.Height)" } else { $null }
                    }


                    $liveStream = $cam | Get-Stream -LiveDefault
                    $recordedStream = $cam | Get-Stream -Recorded
                    $liveStreamName = $liveStream.StreamReferenceIdValues.Keys | ForEach-Object { if ($liveStream.StreamReferenceId -eq $liveStream.StreamReferenceIdValues[$_]) { $_ } }
                    $recordedStreamName = $recordedStream.StreamReferenceIdValues.Keys | ForEach-Object { if ($recordedStream.StreamReferenceId -eq $recordedStream.StreamReferenceIdValues[$_]) { $_ } }
                    $liveStreamSettings = $cam.DeviceDriverSettingsFolder.DeviceDriverSettings[0].StreamChildItems | Where-Object DisplayName -eq $liveStreamName
                    $recordedStreamSettings = $cam.DeviceDriverSettingsFolder.DeviceDriverSettings[0].StreamChildItems | Where-Object DisplayName -eq $recordedStreamName
                    $motion = $cam.MotionDetectionFolder.MotionDetections[0]
                    $storage = $rec.StorageFolder.Storages | Where-Object Path -eq $cam.RecordingStorage
                    if (!$storageRetention.ContainsKey($cam.RecordingStorage)) {
                        if ($storage.ArchiveStorageFolder.ArchiveStorages.Count -eq 0) {
                            $storageRetention.$($cam.RecordingStorage) = $storage.RetainMinutes
                        }
                        else {
                            $storageRetention.$($cam.RecordingStorage) = $storage.ArchiveStorageFolder.ArchiveStorages | Sort-Object RetainMinutes -Descending | Select-Object -First 1 -ExpandProperty RetainMinutes
                        }
                    }
                    $retention = $storageRetention.$($cam.RecordingStorage)
                    $state = ($states | Where-Object { $_.FQID.ObjectId -eq $cam.Id }).State
                    
                    $networkState = $networkStates.($hw.Id)
                    if ($IncludeNetworkState -and $null -eq $networkState) {
                        $networkState = 'Online'
                        if ($state -ne 'Responding') {
                            $uri = [Uri]$hw.Address
                            $responding = Test-NetConnection -ComputerName $uri.Host -Port $uri.Port -InformationLevel Quiet
                            if (-not $responding) {
                                $networkState = 'Offline'
                            }
                        }
                        $networkStates.($hw.Id) = $networkState
                    }
                    
                    $obj = [PSCustomObject]@{
                        CameraName = $cam.Name
                        Channel = $cam.Channel
                        Enabled = $hw.Enabled -and $cam.Enabled
                        State = $state
                        NetworkState = $networkState
                        LastModified = $cam.LastModified
                        CameraId = $cam.Id
                        HardwareName = $hw.Name
                        HardwareId = $hw.Id
                        RecorderName = $rec.Name
                        RecorderHostName = $rec.HostName
                        RecorderId = $rec.Id
                        Model = $hw.Model
                        Driver = $driver.Name
                        Address = $hw.Address
                        UserName = $hw.UserName
                        Password = $pass
                        MAC = $hwSettings.MacAddress
                        LiveStream = $liveStreamName
                        LiveCodec = GetCodecValueFromStream $liveStreamSettings
                        LiveResolution = GetResolutionValueFromStream $liveStreamSettings
                        LiveFPS = GetFpsValueFromStream $liveStreamSettings
                        RecordingStream = $recordedStreamName
                        RecordingCodec = GetCodecValueFromStream $recordedStreamSettings
                        RecordingResolution = GetResolutionValueFromStream $recordedStreamSettings
                        RecordingFPS = GetFpsValueFromStream $recordedStreamSettings
                        ActualLiveResolution = $liveSize
                        ActualRecordingResolution = $playbackSize
                        RecordingEnabled = $cam.RecordingEnabled
                        RecordKeyframesOnly = $cam.RecordKeyframesOnly
                        PreBufferEnabled = $cam.PrebufferEnabled
                        PreBufferSeconds = $cam.PrebufferSeconds
                        PreBufferInMemory = $cam.PrebufferInMemory
                        StorageName = $storage.Name
                        StoragePath = [IO.Path]::Combine($storage.DiskPath, $storage.Id)
                        StorageRetentionMinutes = $retention
                        MotionEnabled = $motion.Enabled
                        MotionKeyframesOnly = $motion.KeyframesOnly
                        MotionDetectionMethod = $motion.DetectionMethod
                        MotionProcessTime = $motion.ProcessTime
                        MotionManualSensitivity = $motion.ManualSensitivityEnabled
                        MotionMetadataEnabled = $motion.GenerateMotionMetadata
                        MotionHardwareAcceleration = $motion.HardwareAccelerationMode
                    }
                    if ($IncludePlaybackInfo) {
                        $obj | Add-Member -MemberType NoteProperty -Name "OldestImageUtc" -Value $null
                        $obj | Add-Member -MemberType NoteProperty -Name "LatestImageUtc" -Value $null
                        $obj | Add-Member -MemberType NoteProperty -Name "MeetsRetentionPolicy" -Value $null
                        try {
                            $info = $cam | Get-PlaybackInfo -ErrorAction Stop
                            $obj.OldestImageUtc = $info.Begin
                            $obj.LatestImageUtc = $info.End
                            $expectedRetention = [datetime]::UtcNow.AddMinutes(($retention * -1))
                            $obj.MeetsRetentionPolicy = $info.Begin -le $expectedRetention
                        }
                        catch {
                            Write-Warning "Get-PlaybackInfo failed: $($_.Exception.Message)"
                        }
                    }
                    foreach ($p in $AdditionalHardwareProperties) {
                        $obj | Add-Member -MemberType NoteProperty -Name "Custom_$p" -Value $hwSettings.$p
                    }
                    foreach ($p in $AdditionalCameraProperties) {
                        try {
                            $camSettings = $cam | Get-CameraSetting -General
                            $obj | Add-Member -MemberType NoteProperty -Name "Custom_$p" -Value $camSettings.$p
                        }
                        catch {
                            Write-Error $_
                        }
                    }
                    $obj
                }
            }
        }
    }
}