Import hardware from Excel¶
Introduction¶
The Export-VmsHardware
and Import-VmsHardware
cmdlets in MilestonePSTools
offer a way to export your camera/device configuration to a generic CSV file
format, and import cameras into your VMS from CSV. However, there are a lot
of configuration options available on any given device, and it's difficult to
represent all the settings available in a single CSV file structure.
This guide introduces an alternative using Doug Finke's
ImportExcel
module which makes exporting and importing data using the .XLSX file format as
easy as piping your data to Export-Excel
and Import-Excel
. Even if you
don't have Excel installed on the machine from which you're running PowerShell!
Download VmsImportHardwareExcel.psm1
script module from the link below and
read on for more information about this import/export script and how to use it.
Sample worksheet¶
Usage¶
Download the script module¶
To use this script module, download it from the link above and save it to
C:\Users\<user>\Documents\WindowsPowerShell\Modules\VmsImportHardwareExcel\VmsImportHardwareExcel.psm1
for the current user, or C:\Program Files\WindowsPowerShell\Modules\VmsImportHardwareExcel\VmsImportHardwareExcel.psm1
to make it available to all users.
Technically you can save it anywhere you like, but if you place it in one of
the paths PowerShell uses to automatically import modules, it'll save you from
having to manually call Import-Module
with the full path to the file later.
Install ImportExcel¶
Since this script module is not published to, and installed from PowerShell Gallery, the ImportExcel module will not be installed automatically. To install it, use the command below and I encourage you to check out the ImportExcel GitHub repository for more information about this Excel-lent module!
Get-Help¶
The two cmdlets exported by this script module include comment-based help. Use
Get-Help Export-VmsHardwareExcel -Full
and Get-Help Export-VmsHardwareExcel -Full
to see descriptions of the parameters, and examples.
Export existing hardware¶
To export all enabled hardware and devices, you can run the following in
PowerShell and you should end up with a file named something like
hardware_2023-02-18_10-30-22.xlsx
on your desktop.
Connect-ManagementServer -ShowDialog -AcceptEula -Force
$timestamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'
Export-VmsHardwareExcel -Path "~\Desktop\hardware_$timestamp.xlsx" -IncludedDevices Cameras, Microphones, Speakers, Inputs, Outputs, Metadata -Verbose
Import changes¶
Note
You should perform a configuration (SQL) backup before using any tools like this in a production environment. When possible, you should also test this in a test environment.
Open the exported .XLSX file from the previous step and make a change to a setting. For example, change the frame rate of a camera, add coordinates, or adjust the prebuffer time.
If you change the names of any devices, it's important that you find and replace all references to the old name. The reason is that the parent device names are included in each worksheet to help the import script figure out with which recording server and hardware the camera row is associated. The CameraStreams worksheet references the parent camera, hardware, and recording server for instance.
Next, you can try importing those changes.
Since the -Path
parameter was not provided, an open-file dialog will be
displayed allowing you to locate the file you exported in the previous step.
Adding new hardware¶
New hardware can be added using an Excel file with a single worksheet named "Hardware", if all you care about is getting the hardware added to a recording server so that you can configure it later. In fact, if you do it this way you can do an export afterward, modify the settings in the export, and then import the changes. This would save you from trying to construct all of the worksheets with their columns and values yourself.
The columns can be in any order, and are case-insensitive. The minimum required columns on the Hardware worksheet are:
- Name
- The display name to give the hardware after it is added.
- Address
- The hardware address in URL form. For example, http://10.10.10.100/
- UserName
- Password
- RecordingServer
- This should be the display name of the recording server on which to add the hardware.
Additional, optional columns include:
- Enabled
- Values: True | False
- Description
- DriverNumber
- Adding hardware goes a lot faster if you know the driver number. This number is shown in the supported hardware list on milestonesys.com
- DriverGroup
- This is the name of a group of cameras seen in Management Client when adding cameras. Some examples of known group names include "AXIS", "Bosch", "Hanwha", "Milestone", and "ONVIF". If you don't know the DriverNumber, you can provide DriverGroup instead, and the recording server will attempt to discover the correct driver from the list of drivers under that group.
With your Excel file prepared, you can import new hardware by calling the
Import-VmsHardwareExcel
command with or without any parameters. If you're
not logged in the the VMS yet, you will be prompted with a login dialog. And
if you do not provide a value for -Path
, an open-file dialog will be
displayed.
Notes¶
The standalone VmsImportHardwareExcel.psm1
script module provided in this
guide is not a part of the MilestonePSTools module, but it does depend on it,
along with the ImportExcel module. The script module is provided separately
for a couple important reasons.
Limited testing¶
These import/export functions have undergone some limited internal testing, and while they have proven useful in a few professional services projects, we don't consider them "complete", or ready for the broader community to rely on.
Opinionated¶
This script takes an opinionated approach to getting hardware configuration in, and out of the XProtect VMS. Our goal for MilestonePSTools is to provide a PowerShell-friendly interface to the MIP SDK, making it possible for you to build tools, applications, and services around your needs rather than imposing our opinions and making assumptions about what you want.
Building out tools like this script requires a lot of time and testing, and once features are added to the MilestonePSTools module there is an implicit expectation that the feature always works, and will be supported the same as any other feature of the module. We're not ready to commit to that, but we do want to make the tool available for those who can benefit from it and accept the risk of using a "beta" tool.
Script¶
#Requires -Module @{ ModuleName="ImportExcel"; ModuleVersion="7.8" }, @{ ModuleName="MilestonePSTools"; ModuleVersion="23.1.3" }
#region private
function Show-FileDialog {
[CmdletBinding(DefaultParameterSetName = 'OpenFile')]
param (
[Parameter(ParameterSetName = 'OpenFile')]
[switch]
$OpenFile,
[Parameter(Mandatory, ParameterSetName = 'SaveFile')]
[switch]
$SaveFile
)
process {
$params = @{
Title = 'ImportVmsHardwareExcel'
Filter = 'Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*'
DefaultExt = '.xlsx'
RestoreDirectory = $true
AddExtension = $true
}
switch ($PSCmdlet.ParameterSetName) {
'OpenFile' {
$dialog = [System.Windows.Forms.OpenFileDialog]$params
}
'SaveFile' {
$params.FileName = 'Hardware_{0}.xlsx' -f (Get-Date -Format 'yyyy-MM-dd_HH-mm-ss')
$dialog = [System.Windows.Forms.SaveFileDialog]$params
}
Default {
throw "ParameterSetName '$_' not implemented."
}
}
try {
$form = [system.windows.forms.form]@{
TopMost = $true
}
if ($dialog.ShowDialog($form) -eq 'OK') {
$dialog.FileName
} else {
throw "$($PSCmdlet.ParameterSetName) aborted."
}
} finally {
if ($dialog) {
$dialog.Dispose()
}
if ($form) {
$form.Dispose()
}
}
}
}
function Resolve-Path2 {
<#
.SYNOPSIS
Resolves paths like the PowerShell-native `Resolve-Path` cmdlet, even for
paths that don't exist yet.
.DESCRIPTION
Long description
.PARAMETER Path
Parameter description
.PARAMETER LiteralPath
Parameter description
.PARAMETER Relative
Parameter description
.PARAMETER NoValidation
If the path does not exist, return the resolved path anyway.
.PARAMETER ExpandEnvironmentVariables
If a path contains CMD-style variables like "%appdata%\Roaming"
.EXAMPLE
An example
.NOTES
Inspired by a [blog post](http://devhawk.net/blog/2010/1/22/fixing-powershells-busted-resolve-path-cmdlet)
by DevHawk, aka Harry Pierson, linked to by joshuapoehls on [stackoverflow.com](https://stackoverflow.com/a/12605755/3736007).
#>
[CmdletBinding(DefaultParameterSetName = 'Path')]
[OutputType([string])]
param (
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Path')]
[SupportsWildcards()]
[string[]]
$Path,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')]
[string[]]
$LiteralPath,
[Parameter()]
[switch]
$Relative,
[Parameter()]
[switch]
$NoValidation,
[Parameter(ParameterSetName = 'Path')]
[switch]
$ExpandEnvironmentVariables
)
process {
foreach ($unresolvedPath in $MyInvocation.BoundParameters[$PSCmdlet.ParameterSetName]) {
if ($ExpandEnvironmentVariables) {
$unresolvedPath = [environment]::ExpandEnvironmentVariables($unresolvedPath)
}
$params = @{
$($PSCmdlet.ParameterSetName) = $unresolvedPath
ErrorAction = 'SilentlyContinue'
ErrorVariable = 'resolvePathError'
}
$resolvedPath = Resolve-Path @params
if ($null -eq $resolvedPath) {
if ($NoValidation) {
$resolvedPath = $resolvePathError[0].TargetObject
} elseif ($resolvePathError) {
Write-Error -ErrorRecord $resolvePathError[0]
Remove-Variable -Name resolvePathError
continue
}
}
foreach ($pathInfo in $resolvedPath) {
if ($Relative) {
$separator = [io.path]::DirectorySeparatorChar
$currentPathUri = [uri]::new($pwd.Path, [System.UriKind]::Absolute)
#$currentPathUri = [uri]::new(($pwd.Path -replace "([^$([regex]::Escape($separator))])`$", "`$1$([regex]::Escape($separator))"), [System.UriKind]::Absolute)
#$resolvedPathUri = [uri]::new($pathInfo.Path, [System.UriKind]::Absolute)
$resolvedPathUri = [uri]::new(($pathInfo.Path -replace "([^$([regex]::Escape($separator))])`$", "`$1$([regex]::Escape($separator))"), [System.UriKind]::Absolute)
$relativePath = $currentPathUri.MakeRelativeUri($resolvedPathUri).ToString() -replace '/', [io.path]::DirectorySeparatorChar
if ($relativePath -notmatch "^\.+\$([io.path]::DirectorySeparatorChar)") {
$relativePath = '.{0}{1}' -f [io.path]::DirectorySeparatorChar, $relativePath
}
$relativePath
} else {
$pathInfo
}
}
}
}
}
function Get-DeviceEvents {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device
)
begin {
$validDeviceTypes = @('Hardware', 'Camera', 'Microphone', 'Speaker', 'InputEvent')
}
process {
$devicePath = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($Device.Path)
$itemType = $devicePath.ItemType
if ($itemType -notin $validDeviceTypes) {
Write-Error "Invalid device type for this cmdlet."
return
}
$deviceEvents = (Get-ConfigurationItem -Path ('HardwareDeviceEvent[{0}]' -f $Device.Id)).Children
foreach ($deviceEvent in $deviceEvents) {
[pscustomobject]@{
Event = $deviceEvent.DisplayName
Used = ($deviceEvent.Properties | Where-Object Key -eq 'EventUsed').Value -eq 'True'
Enabled = $deviceEvent.EnableProperty.Enabled
EventIndex = ($deviceEvent.Properties | Where-Object Key -eq 'EventIndex').Value
IndexName = ($deviceEvent.Properties | Where-Object Key -eq 'EventIndex').DisplayName
}
}
}
}
function Get-DeviceProperties {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device
)
begin {
$excludedProperties = 'Icon', 'ItemCategory', 'Methods', 'ServerId', 'CreatedDate', 'DisplayName', 'ParentItemPath', 'StreamDefinitions', 'StreamUsages'
$orderPriority = 'Name', 'ShortName', 'HostName', 'WebServerUri', 'Address', 'UserName', 'Password', 'Enabled', 'Channel', 'GisPoint', 'ActiveWebServerUri', 'PublicAccessEnabled', 'PublicWebserverHostName', 'PublicWebserverPort'
$rearOrderPriority = 'LastModified', 'Id'
$pathNameMap = @{}
$childToParentMap = @{}
$recordingStorage = @{}
Get-VmsRecordingServer -PipelineVariable rec | Get-VmsStorage | Foreach-Object {
$recordingStorage[$_.Path] = $_
$pathNameMap[$_.Path] = $_.Name
$pathNameMap[$rec.Path] = $rec.Name
$childToParentMap[$_.Path] = $rec.Path
}
# Use translations to take an existing device property/value, and modify the column name and value in some way.
# For example, the GisPoint property has a name unfamiliar to most users, and the "POINT(X Y)" value is even more unfamiliar.
# Also useful for translating a config API path like "Storage[guid]" to the name of that storage.
$translations = @{
'GisPoint' = {
@{
Name = 'Coordinates'
Value = $_.GisPoint | ConvertFrom-GisPoint
}
}
'RecordingStorage' = {
@{
Name = 'Storage'
Value = $recordingStorage[$_.RecordingStorage].Name
}
}
}
# Properties to be added. Keys represent the name of a property after which these new properties will be added. Each scriptblock can return one or more Name/Value pairs
$additionalProperties = @{
'UserName' = {
[pscustomobject]@{
Name = 'Password'
Value = try { $_ | Get-VmsHardwarePassword -ErrorAction SilentlyContinue } catch {}
}
}
'RecordOnRelatedDevices' = {
$motion = $_.MotionDetectionFolder.MotionDetections[0]
[pscustomobject]@{ Name = 'MotionEnabled'; Value = $motion.Enabled }
[pscustomobject]@{ Name = 'MotionManualSensitivityEnabled'; Value = $motion.ManualSensitivityEnabled }
[pscustomobject]@{ Name = 'MotionManualSensitivity'; Value = $motion.ManualSensitivity }
[pscustomobject]@{ Name = 'MotionThreshold'; Value = $motion.Threshold }
[pscustomobject]@{ Name = 'MotionKeyframesOnly'; Value = $motion.KeyframesOnly }
[pscustomobject]@{ Name = 'MotionProcessTime'; Value = $motion.ProcessTime }
[pscustomobject]@{ Name = 'MotionDetectionMethod'; Value = $motion.DetectionMethod }
[pscustomobject]@{ Name = 'MotionGenerateMotionMetadata'; Value = $motion.GenerateMotionMetadata }
[pscustomobject]@{ Name = 'MotionUseExcludeRegions'; Value = $motion.UseExcludeRegions }
[pscustomobject]@{ Name = 'MotionGridSize'; Value = $motion.GridSize }
[pscustomobject]@{ Name = 'MotionExcludeRegions'; Value = $motion.ExcludeRegions }
[pscustomobject]@{ Name = 'MotionHardwareAccelerationMode'; Value = $motion.HardwareAccelerationMode }
}
'ManualRecordingTimeoutMinutes' = {
$ptzTimeout = $_.DeviceDriverSettingsFolder.DeviceDriverSettings[0].PTZSessionTimeoutChildItem
[pscustomobject]@{ Name = 'ManualPTZTimeout'; Value = $ptzTimeout.ManualPTZTimeout }
[pscustomobject]@{ Name = 'PausePatrollingTimeout'; Value = $ptzTimeout.PausePatrollingTimeout }
[pscustomobject]@{ Name = 'ReservedPTZTimeout'; Value = $ptzTimeout.ReservedPTZTimeout }
}
'RecordingFramerate' = {
$privacyMask = $_.PrivacyProtectionFolder.PrivacyProtections[0]
[pscustomobject]@{ Name = 'PrivacyMaskEnabled'; Value = $privacyMask.Enabled }
[pscustomobject]@{ Name = 'PrivacyMaskXml'; Value = $privacyMask.PrivacyMaskXml }
}
'Channel' = {
$hwId = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.ParentItemPath).Id
$hw = [VideoOS.Platform.Configuration]::Instance.GetItem($hwId , [VideoOS.Platform.Kind]::Hardware)
# If the hardware is disabled, the above command returns $null so we need to fallback to a reliable, but slower, method.
if ([string]::IsNullOrEmpty($hw)) {
$hw = Get-VmsHardware -Id $hwId
$recId = [regex]::Matches($hw.ParentItemPath, '(?<=\[)[^]]+(?=\])').Value
[pscustomobject]@{
Name = 'Address'
Value = $hw.Address
}
[pscustomobject]@{
Name = 'Hardware'
Value = $hw.Name
}
[pscustomobject]@{
Name = 'RecordingServer'
Value = $pathNameMap["RecordingServer[$($recId)]"]
}
} else {
[pscustomobject]@{
Name = 'Address'
Value = $hw.Properties.Address
}
[pscustomobject]@{
Name = 'Hardware'
Value = $hw.Name
}
[pscustomobject]@{
Name = 'RecordingServer'
Value = $pathNameMap["RecordingServer[$($hw.FQID.ServerId.Id)]"]
}
}
}
'EdgeStoragePlaybackEnabled' = {
$clientSettings = $_.ClientSettingsFolder.ClientSettings[0]
if ($clientSettings.Shortcut -eq 0 -or [string]::IsNullOrEmpty($clientSettings.Shortcut))
{
[pscustomobject]@{ Name = 'Shortcut'; Value = $null }
} else {
[pscustomobject]@{ Name = 'Shortcut'; Value = $clientSettings.Shortcut }
}
[pscustomobject]@{ Name = 'MulticastEnabled'; Value = $clientSettings.MulticastEnabled }
}
# Add driver and recording server info after model column for hardware objects
'Model' = {
if ($hwSettings = ($_ | Get-HardwareSetting -ErrorAction SilentlyContinue)) {
[pscustomobject]@{
Name = 'MACAddress'
Value = $hwSettings.MacAddress
}
[pscustomobject]@{
Name = 'SerialNumber'
Value = $hwSettings.SerialNumber
}
[pscustomobject]@{
Name = 'FirmwareVersion'
Value = $hwSettings.FirmwareVersion
}
}
if ($driver = ($_ | Get-VmsHardwareDriver -ErrorAction SilentlyContinue)) {
[pscustomobject]@{
Name = 'DriverNumber'
Value = $driver.Number
}
[pscustomobject]@{
Name = 'DriverGroup'
Value = $driver.GroupName
}
[pscustomobject]@{
Name = 'DriverDriverType'
Value = $driver.DriverType
}
[pscustomobject]@{
Name = 'DriverVersion'
Value = $driver.DriverVersion
}
[pscustomobject]@{
Name = 'DriverRevision'
Value = $driver.DriverRevision
}
}
[pscustomobject]@{
Name = 'RecordingServer'
Value = $pathNameMap[$_.ParentItemPath]
}
}
}
}
process {
$properties = ($Device | Get-Member -MemberType Property | Where-Object { $_.Name -notlike '*Folder' -and $_.Name -notlike '*Path' -and $_.Name -notin $excludedProperties }).Name
$obj = [ordered]@{}
foreach ($property in $orderPriority) {
if ($null -ne $Device.$property) {
if ($translations.ContainsKey($property)) {
$translations[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
} else {
$obj.Add($property, $Device.$property)
}
if ($additionalProperties.ContainsKey($property)) {
$additionalProperties[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
}
}
}
foreach ($property in $properties | Where-Object { $_ -notin $orderPriority -and $_ -notin $rearOrderPriority }) {
if ($translations.ContainsKey($property)) {
$translations[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
} else {
$obj.Add($property, $Device.$property)
}
if ($additionalProperties.ContainsKey($property)) {
$additionalProperties[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
}
}
foreach ($property in $rearOrderPriority) {
if ($null -ne $Device.$property) {
if ($translations.ContainsKey($property)) {
$translations[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
} else {
$obj.Add($property, $Device.$property)
}
if ($additionalProperties.ContainsKey($property)) {
$additionalProperties[$property].Invoke($Device) | Foreach-Object {
$obj.Add($_.Name, $_.Value)
}
}
}
}
[pscustomobject]$obj
}
}
function Get-GeneralSettings {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device
)
begin {
$validDeviceTypes = @('Hardware', 'Camera', 'Microphone', 'Speaker', 'InputEvent', 'Output', 'Metadata')
}
process {
$commonProperties = [ordered]@{}
$devicePath = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($Device.Path)
$parentPath = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($Device.ParentItemPath)
if ($devicePath.ItemType -notin $validDeviceTypes) {
Write-Error "Invalid device type for this cmdlet."
return
}
switch ($parentPath.ItemType) {
'Hardware' {
$hwItem = [videoos.platform.configuration]::Instance.GetItem($parentPath.Id, [videoos.platform.kind]::Hardware)
# If the hardware is disabled, the above command returns $null so we need to fallback to a reliable, but slower, method.
if ([string]::IsNullOrEmpty($hwItem)) {
$hwItem = Get-VmsHardware -Id $parentPath.Id
$recId = [regex]::Matches($hwItem.ParentItemPath, '(?<=\[)[^]]+(?=\])').Value
$recorderItem = [videoos.platform.configuration]::Instance.GetItem($recId, [videoos.platform.kind]::Server)
} else {
$recorderItem = [videoos.platform.configuration]::Instance.GetItem($hwItem.FQID.ServerId.Id, [videoos.platform.kind]::Server)
}
$commonProperties['RecordingServer'] = $recorderItem.Name
$commonProperties['Hardware'] = $hwItem.Name
}
'RecordingServer' {
$recorderItem = [videoos.platform.configuration]::Instance.GetItem($parentPath.Id, [videoos.platform.kind]::Server)
$commonProperties['RecordingServer'] = $recorderItem.Name
}
Default {}
}
$commonProperties[$devicePath.ItemType] = $Device.Name
if ($null -ne $Device.Channel) {
$commonProperties['Channel'] = $Device.Channel
}
$itemType = if ($devicePath.ItemType -eq 'Hardware') { 'Hardware' } else { 'Device' }
Get-ConfigurationItem -Path "$($itemType)DriverSettings[$($Device.Id)]" | Select-Object -ExpandProperty Children | Where-Object ItemType -eq "$($itemType)DriverSettings" | Select-Object -ExpandProperty Properties | Foreach-Object {
$property = $_
$displayValue = ($property.ValueTypeInfos | Where-Object Value -eq $property.Value).Name
$key = $property.Key
if ($key -match '^([^/]+/)(?<key>[^/]+)(/[^/]+)?$') {
$key = $Matches.key
}
$row = [ordered]@{}
$commonProperties.Keys | Foreach-Object { $row[$_] = $commonProperties[$_] }
$row.Setting = $key
$row.Value = $property.Value
$row.DisplayValue = if ($property.ValueType -eq 'Enum' -and $displayValue -ne $property.Value) { $displayValue } else { $null }
$row.ReadOnly = !$property.IsSettable
[pscustomobject]$row
}
}
}
function Set-DeviceEvents {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device,
[Parameter(Mandatory)]
[pscustomobject[]]
$Settings
)
begin {
$validDeviceTypes = @('Hardware', 'Camera', 'Microphone', 'Speaker', 'InputEvent')
}
process {
$devicePath = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($Device.Path)
$itemType = $devicePath.ItemType
if ($itemType -notin $validDeviceTypes) {
Write-Error "Invalid device type for this cmdlet."
return
}
$item = Get-ConfigurationItem -Path ('HardwareDeviceEvent[{0}]' -f $Device.Id)
foreach ($eventRow in $Settings) {
$deviceEvent = $item.Children | Where-Object DisplayName -eq $eventRow.EventName | Select-Object -First 1
if ($deviceEvent) {
$deviceEvent.EnableProperty.Enabled = $eventRow.Enabled.ToString() -eq 'True' # In case the column is treated like a string, we'll make sure to do a string comparison.
$deviceEvent.Properties | Where-Object Key -eq 'EventUsed' | ForEach-Object { $_.Value = $eventRow.Used.ToString() -eq 'True' }
$deviceEvent.Properties | Where-Object Key -eq 'EventIndex' | ForEach-Object { $_.Value = $eventRow.EventIndex }
} else {
Write-Warning "Device '$($Hardware.Name)' does not have a device event setting with the key '$($eventRow.EventName)'."
}
}
$result = $item | Set-ConfigurationItem
foreach ($entry in $result.ErrorResults) {
Write-Error -Message "Validation error: $($entry.ErrorText) on device '$($device.Name)'."
}
}
}
function Set-DeviceProperties {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device,
[Parameter(Mandatory)]
[pscustomobject]
$Settings
)
begin {
$ignoredColumns = 'RecordingServer', 'Hardware', 'Address', 'LastModified', 'Id', 'MotionDetectionMethod', 'MotionGenerateMotionMetadata', 'MotionGridSize', 'MotionExcludeRegions', 'MotionHardwareAccelerationMode', 'MotionKeyframesOnly', 'MotionManualSensitivity', 'MotionManualSensitivityEnabled', 'MotionProcessTime', 'MotionThreshold', 'MotionUseExcludeRegions', 'PrivacyMaskXml', 'PausePatrollingTimeout', 'ReservedPTZTimeout', 'MulticastEnabled'
$recordingStorage = @{}
Get-VmsRecordingServer -Name $Settings.RecordingServer | Get-VmsStorage | Foreach-Object {
$recordingStorage[$_.Name] = $_
}
$translations = @{
'Coordinates' = {
param($item, $settings)
try {
@{
Name = 'GisPoint'
Value = if ($settings.Coordinates -eq 'Unknown' -or [string]::IsNullOrWhiteSpace($settings.Coordinates)) { 'POINT EMPTY' } else { ConvertTo-GisPoint -Coordinates $settings.Coordinates -ErrorAction Stop }
}
} catch {
Write-Warning "Failed to convert value '$($settings.Coordinates)' to a GisPoint value compatible with Milestone."
}
}
'Storage' = {
param($item, $settings)
if ($recordingStorage.ContainsKey($settings.Storage)) {
@{
Name = 'RecordingStorage'
Value = $recordingStorage[$settings.Storage].Path
}
} else {
Write-Warning "Storage configuration '$($settings.Storage)' not found on recording server $($settings.RecordingServer)"
}
}
}
$customHandlers = @{
'Enabled' = {
param($item, $settings)
$enabled = $false
if (-not [string]::IsNullOrWhiteSpace($settings.Enabled) -and [bool]::TryParse($settings.Enabled, [ref]$enabled) -and $item.EnableProperty.Enabled -ne $enabled) {
Write-Verbose "Changing 'Enabled' to $enabled on $($item.DisplayName)"
$item.EnableProperty.Enabled = $enabled
return $true
}
return $false
}
'RecordingStorage' = {
param($item, $settings)
try {
$storagePath = $recordingStorage[$settings.Storage].Path
if ($null -eq $storagePath) {
throw "Storage configuration named '$($settings.Storage)' not found."
}
if ($storagePath -eq ($item.Properties | Where-Object Key -eq 'RecordingStorage').Value) {
return $true
}
$invokeInfo = $item | Invoke-Method -MethodId 'ChangeDeviceRecordingStorage'
foreach ($p in $invokeInfo.Properties) {
switch ($p.Key) {
'ItemSelection' { $p.Value = $storagePath }
'moveData' { $p.Value = $false }
}
}
$invokeResult = $invokeInfo | Invoke-Method -MethodId 'ChangeDeviceRecordingStorage'
$taskPath = ($invokeResult.Properties | Where-Object Key -eq 'Path').Value
if ($taskPath) {
$null = Wait-VmsTask -Path $taskPath -Cleanup
}
return $true
} catch {
Write-Warning $_.Exception.Message
}
return $false
}
'MotionEnabled' = {
param($item, $settings)
$motion = Get-ConfigurationItem -Path "MotionDetection[$(($item.Properties | Where-Object Key -eq Id).Value)]"
$dirty = $false
foreach ($column in $settings | Get-Member -MemberType NoteProperty -Name Motion* | Select-Object -ExpandProperty Name) {
if ([string]::IsNullOrWhiteSpace($settings.$column)) {
continue
}
$key = $column.Substring(6)
if ($key -eq 'Enabled') {
$newValue = 'True' -eq $settings.$column
if ($motion.EnableProperty.Enabled -ne $newValue) {
$motion.EnableProperty.Enabled = $newValue
$dirty = $true
}
} else {
$property = $motion.Properties | Where-Object Key -eq $key
if ($property.Value -ne $settings.$column) {
$property.Value = $settings.$column
$dirty = $true
}
}
}
if ($dirty) {
$result = $motion | Set-ConfigurationItem
if (-not $result.ValidatedOk) {
foreach ($errorResult in $result.ErrorResults) {
Write-Warning "Failed to update motion detection settings for $($item.DisplayName). $($errorResult.ErrorText)."
}
}
}
}
'ManualPTZTimeout' = {
param($item, $settings)
$deviceDriverSettings = Get-ConfigurationItem -Path "DeviceDriverSettings[$(($item.Properties | Where-Object Key -eq Id).Value)]"
$dirty = $false
foreach ($column in $settings | Get-Member -MemberType NoteProperty -Name ManualPTZTimeout, PausePatrollingTimeout, ReservedPTZTimeout | Select-Object -ExpandProperty Name) {
if ([string]::IsNullOrWhiteSpace($settings.$column)) {
continue
}
$key = $column
$property = ($deviceDriverSettings.Children | Where-Object {$_.ItemType -eq 'PTZSessionTimeout'}).Properties | Where-Object Key -eq $key
if ($property.Value -ne $settings.$column) {
$property.Value = $settings.$column
$dirty = $true
}
}
if ($dirty) {
$result = $deviceDriverSettings | Set-ConfigurationItem
if (-not $result.ValidatedOk) {
foreach ($errorResult in $result.ErrorResults) {
Write-Warning "Failed to update PTZ session timeout settings for $($item.DisplayName). $($errorResult.ErrorText)."
}
}
}
}
'PrivacyMaskEnabled' = {
param($item, $settings)
$privacyMask = Get-ConfigurationItem -Path "PrivacyProtection[$(($item.Properties | Where-Object Key -eq Id).Value)]"
$dirty = $false
foreach ($column in $settings | Get-Member -MemberType NoteProperty -Name PrivacyMask* | Select-Object -ExpandProperty Name) {
if ([string]::IsNullOrWhiteSpace($settings.$column)) {
continue
}
$key = $column
if ($key -eq 'PrivacyMaskEnabled') {
$newValue = 'True' -eq $settings.$column
if ($privacyMask.EnableProperty.Enabled -ne $newValue) {
$privacyMask.EnableProperty.Enabled = $newValue
$dirty = $true
}
} elseif ($key -eq 'PrivacyMaskXml') {
$property = $privacyMask.Properties | Where-Object Key -eq $key
if ($property.Value -ne $settings.$column) {
$property.Value = $settings.$column
$dirty = $true
}
}
}
if ($dirty) {
$result = $privacyMask | Set-ConfigurationItem
if (-not $result.ValidatedOk) {
foreach ($errorResult in $result.ErrorResults) {
Write-Warning "Failed to update privacy mask settings for $($item.DisplayName). $($errorResult.ErrorText)."
}
}
}
}
'Shortcut' = {
param($item, $settings)
$clientSettings = Get-ConfigurationItem -Path "ClientSettings[$(($item.Properties | Where-Object Key -eq Id).Value)]"
$dirty = $false
foreach ($column in $settings | Get-Member -MemberType NoteProperty -Name Shortcut, MulticastEnabled | Select-Object -ExpandProperty Name) {
if ([string]::IsNullOrWhiteSpace($settings.$column)) {
continue
}
$key = $column
if ($key -eq 'MulticastEnabled') {
$newValue = 'True' -eq $settings.$column
$property = $clientSettings.Properties | Where-Object Key -eq $key
if ($property.Value -ne $newValue) {
$property.Value = $newValue
$dirty = $true
}
} elseif ($key -eq 'Shortcut') {
$property = $clientSettings.Properties | Where-Object Key -eq $key
if ($property.Value -ne $settings.$column -and $settings.$column -ge 1) {
$property.Value = $settings.$column
$dirty = $true
}
}
}
if ($dirty) {
$result = $clientSettings | Set-ConfigurationItem
if (-not $result.ValidatedOk) {
foreach ($errorResult in $result.ErrorResults) {
Write-Warning "Failed to update privacy mask settings for $($item.DisplayName). $($errorResult.ErrorText)."
}
}
}
}
}
}
process {
$dirty = $false
$properties = @{}
$item = $Device | Get-ConfigurationItem
$item.Properties | Foreach-Object { $properties[$_.Key] = $_ }
foreach ($columnName in $Settings | Get-Member -MemberType NoteProperty | Where-Object Name -notin $ignoredColumns | Select-Object -ExpandProperty Name) {
$newValue = $Settings.$columnName
if ($translations.ContainsKey($columnName)) {
$columnName, $newValue = $translations[$columnName].Invoke($item, $Settings) | Foreach-Object {
Write-Verbose "Translating column name '$($columnName)' to '$($_.Name)', and value '$($newValue)' to '$($_.Value)'"
@($_.Name, $_.Value)
}
if ($null -eq $columnName -or $null -eq $newValue) {
Write-Verbose "Failed to translate column/value. No change will be made for this property."
continue
}
}
if ($customHandlers.ContainsKey($columnName)) {
if ($customHandlers[$columnName].Invoke($item, $Settings)) {
$dirty = $true
}
} else {
$property = $properties[$columnName]
if ($property) {
if ($property.Value -ne $newValue) {
Write-Verbose "Setting $columnName to $newValue on $($Device.Name)"
$property.Value = $newValue
$dirty = $true
} else {
Write-Verbose "Setting $columnName already has value $newValue on $($Device.Name)"
}
} else {
Write-Warning "Property '$($columnName)' not found on $($Device.Name)"
}
}
}
# Update the name for the in-memory copy of $Device so that the verbose logging doesn't mention the old name anymore.
$Device.Name = ($item.Properties | Where-Object Key -eq 'Name').Value
if ($dirty) {
Write-Verbose "Saving changes to $($Device.Name)"
$result = $item | Set-ConfigurationItem
foreach ($entry in $result.ErrorResults) {
Write-Error -Message "Validation error: $($entry.ErrorText) on '$($Device.Name)'."
}
} else {
Write-Verbose "No changes made to $($Device.Name)"
}
}
}
function Set-GeneralSettings {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.IConfigurationItem]
$Device,
[Parameter(Mandatory)]
[pscustomobject[]]
$Settings
)
begin {
$validDeviceTypes = @('Hardware', 'Camera', 'Microphone', 'Speaker', 'InputEvent', 'Output', 'Metadata')
}
process {
$devicePath = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($Device.Path)
if ($devicePath.ItemType -notin $validDeviceTypes) {
Write-Error "Invalid device type for this cmdlet."
return
}
$itemType = if ($devicePath.ItemType -eq 'Hardware') { 'Hardware' } else { 'Device' }
Write-Verbose "$($devicePath.ItemType)GeneralSettings: Checking general settings for '$($Device.Name)'"
$item = Get-ConfigurationItem -Path "$($itemType)DriverSettings[$($Device.Id)]"
$general = $item.Children | Where-Object ItemType -eq "$($itemType)DriverSettings"
$dirty = $false
foreach ($setting in $Settings) {
$property = $general.Properties | Where-Object Key -match "^([^/]+/)?(?<key>$([regex]::Escape($setting.Setting)))(/[^/]+)?$" | Select-Object -First 1
$key = $setting.Setting
if ($property) {
if ($property.IsSettable) {
if ($property.Value -cne $setting.Value) {
Write-Verbose "$($devicePath.ItemType)GeneralSettings: Changing $($property.DisplayName) ($key) to '$($setting.Value)'"
$property.Value = $setting.Value
$dirty = $true
} else {
Write-Verbose "$($devicePath.ItemType)GeneralSettings: Keeping $($property.DisplayName) ($key) value '$($setting.Value)'"
}
} else {
Write-Verbose "$($devicePath.ItemType)GeneralSettings: Skipping read-only property $($property.DisplayName) ($key)"
}
} else {
Write-Warning "$($devicePath.ItemType)GeneralSettings: Device '$($Device.Name)' does not have a general setting with the key '$($setting.Setting)'."
}
}
if (-not $dirty) {
Write-Verbose "$($devicePath.ItemType)GeneralSettings: No changes to general settings were required for '$($Device.Name)'"
return
}
Write-Verbose "$($devicePath.ItemType)GeneralSettings: Saving changes to general settings for '$($Device.Name)'"
$result = $item | Set-ConfigurationItem
foreach ($entry in $result.ErrorResults) {
Write-Error -Message "$($devicePath.ItemType)GeneralSettings: Validation error: $($entry.ErrorText) on '$($Device.Name)'."
}
<## Todo: See if its possible for the validation errorresults list to include all validation errors instead of one at a time.
if (-not $result.ValidatedOk) {
Write-verbose "Retrying without the invalid values"
$general.Properties = $general.Properties | Where-Object Key -notin $result.ErrorResults.ErrorProperty
$result = $item | Set-ConfigurationItem
foreach ($entry in $result.ErrorResults) {
Write-Error -Message "Validation error: $($entry.ErrorText) on '$($Device.Name)'."
}
}
#>
}
}
#endregion
#region public
function Export-VmsHardwareExcel {
<#
.SYNOPSIS
Exports hardware configuration in Microsoft Excel XLSX format.
.DESCRIPTION
The `Export-VmsHardwareExcel` cmdlet accepts one or more Hardware objects
from `Get-VmsHardware` and exports detailed configuration to an Excel XLSX
document.
The document will contain multiple worksheets, depending on which device
types are specified in the `IncludedDevices` parameter. Each area of the
hardware configuration is represented in it's own worksheet which makes it
possible to represent many different types of objects and settings in the
same document while keeping it human-readable and easy to modify.
.PARAMETER Hardware
Specifies one or more Hardware objects returned by `Get-VmsHardware`. If no
hardware is provided, then all hardware found in the VMS matching the
desired `EnableState` will be exported.
.PARAMETER Path
The absolute, or relative path, including filename, where the .XLSX file
should be saved. If no path is provided, a save-file dialog will be shown.
.PARAMETER IncludedDevices
Defaults to "Cameras". Specifies the types of child devices to include in the export. It can be
very time consuming to export configuration for thousands of devices, and
if you only need camera and metadata settings, you can specify this and
avoid retrieving detailed configuration on microphones, speakers, inputs,
and outputs.
.PARAMETER EnableFilter
Defaults to "Enabled". Filters the exported hardware and devices to only
those matching the specified EnableFilter.
.PARAMETER Force
Overwrite an existing file if the file specified in `Path` already exists.
.EXAMPLE
Export-VmsHardwareExcel -Path ~\Documents\hardware.xlsx -Verbose
Exports configuration for all enabled hardware, and cameras to the current
user's Documents directory.
.EXAMPLE
Export-VmsHardwareExcel -Path ~\Documents\hardware.xlsx -IncludedDevices Cameras, Microphones -Verbose
Exports configuration for all enabled hardware, cameras, and microphones to
the current user's Documents directory.
.EXAMPLE
$hardware = Get-VmsRecordingServer -Name Recorder1 | Get-VmsHardware
Export-VmsHardwareExcel -Hardware $hardware -Path ~\Desktop\hardware.xlsx -Verbose
Exports configuration for all enabled hardware, and cameras on the
recording server named "Recorder1" tp the current user's Desktop.
#>
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline)]
[VideoOS.Platform.ConfigurationItems.Hardware[]]
$Hardware,
[Parameter()]
[string]
$Path,
[Parameter()]
[ValidateSet('Cameras', 'Microphones', 'Speakers', 'Metadata', 'Inputs', 'Outputs')]
[string[]]
$IncludedDevices = @('Cameras'),
[Parameter()]
[ValidateSet('All', 'Disabled', 'Enabled')]
[string]
$EnableFilter = 'Enabled',
[Parameter()]
[switch]
$Force
)
begin {
if ($null -eq (Get-VmsManagementServer -ErrorAction 'SilentlyContinue')) {
Connect-ManagementServer -ShowDialog -AcceptEula -Force -ErrorAction Stop
}
if ([string]::IsNullOrWhiteSpace($Path)) {
$Path = Show-FileDialog -SaveFile
}
if (Test-Path $Path) {
if ($Force) {
Remove-Item -Path $Path -ErrorAction Stop
} else {
Write-Error "File $Path already exists." -ErrorAction Stop
}
} else {
$fileInfo = [io.fileinfo](Resolve-Path2 -Path $Path -NoValidation)
if (-not (Test-Path $fileInfo.DirectoryName)) {
Write-Verbose "Directory $($fileInfo.DirectoryName) does not exist. This folder will be created."
$null = New-Item -Path $fileInfo.DirectoryName -ItemType Directory
}
}
$excelPackage = Open-ExcelPackage -Path $Path -Create
$worksheets = @(
'Hardware',
'HardwareGeneralSettings',
'HardwareEvents',
'Cameras',
'CameraGeneralSettings',
'CameraStreams',
'CameraStreamSettings',
'CameraPtzPresets',
'CameraPtzPatrols',
'CameraPtzPatrolPresets',
'CameraRelatedDevices',
'CameraEvents',
'Microphones',
'MicrophoneGeneralSettings',
'MicrophoneStreamSettings',
'MicrophoneEvents',
'Speakers',
'SpeakerGeneralSettings',
'SpeakerEvents',
'Metadata',
'MetadataGeneralSettings',
'Inputs',
'InputGeneralSettings',
'InputEvents',
'Outputs',
'OutputGeneralSettings'
)
$null = $worksheets | Foreach-Object { $excelPackage.Workbook.Worksheets.Add($_) }
Clear-VmsCache
}
process {
$progress = @{
Activity = 'Exporting hardware configuration to {0}' -f $Path
Id = 11
PercentComplete = 0
CurrentOperation = 'Preparing'
}
Write-Progress @progress
if ($IncludedDevices) {
$IncludedDevices = $IncludedDevices | Group-Object | Select-Object -ExpandProperty Name
}
$progress.CurrentOperation = "Retrieving list of recording servers"
Write-Progress @progress
Write-Verbose "Retrieving recording server list"
$recorderMap = @{}
Get-VmsRecordingServer | Foreach-Object {
$recorderMap[$_.Path] = $_
}
if ($null -eq $Hardware) {
$progress.CurrentOperation = "Retrieving list of hardware to be exported"
Write-Progress @progress
$Hardware = Get-VmsHardware
}
$excelParams = @{
ExcelPackage = $excelPackage
TableStyle = 'Medium9'
AutoSize = $true
Append = $true
NoNumberConversion = 'Value', 'DisplayValue', 'MotionExcludeRegions', 'MACAddress', 'SerialNumber', 'FirmwareVersion', 'Password'
PassThru = $true
}
$totalHardwareCount = $Hardware.Count
$processedHardwareCount = 0
$Hardware | ForEach-Object {
$hw = $_
$progress.PercentComplete = [math]::Round(($processedHardwareCount++) / $totalHardwareCount * 100)
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
if (($EnableFilter -eq 'Enabled' -and -not $hw.Enabled) -or ($EnableFilter -eq 'Disabled' -and $hw.Enabled)) {
Write-Verbose "Skipping hardware $($hw.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving hardware properties for $($hw.Name)"
$null = $hw | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Hardware -TableName HardwareList }
Write-Verbose "Retrieving general setting properties for $($hw.Name)"
$null = $hw | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName HardwareGeneralSettings -TableName HardwareGeneralSettingsList }
Write-Verbose "Retrieving event properties for $($hw.Name)"
$obj = [ordered]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
}
$null = $hw | Get-DeviceEvents | ForEach-Object {
$eventInfo = $_
$obj.EventName = $eventInfo.Event
$obj.Used = $eventInfo.Used
$obj.Enabled = $eventInfo.Enabled
$obj.EventIndex = $eventInfo.EventIndex
$obj.IndexName = $eventInfo.IndexName
[pscustomobject]$obj | Export-Excel @excelParams -WorksheetName HardwareEvents -TableName HardwareEventsList
}
if ('Cameras' -in $IncludedDevices) {
$hw | Get-VmsCamera -EnableFilter $EnableFilter | Foreach-Object {
Write-Verbose "Retrieving camera properties for $($_.Name)"
$cam = $_
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$null = $cam | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Cameras -TableName CamerasList }
Write-Verbose "Retrieving general setting properties for $($cam.Name)"
$null = $cam | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName CameraGeneralSettings -TableName CameraGeneralSettingsList }
Write-Verbose "Retrieving stream properties for $($cam.Name)"
$cam | Get-VmsCameraStream -Enabled -RawValues | Foreach-Object {
$stream = $_
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
Name = $stream.Name
DisplayName = $stream.DisplayName
LiveMode = $stream.LiveMode
LiveDefault = $stream.LiveDefault
Recorded = $stream.Recorded
}
$null = $obj | Export-Excel @excelParams -WorksheetName CameraStreams -TableName CameraStreamsList
$null = $stream.Settings.Keys | Foreach-Object {
$key = $_
$displayValue = ($stream.ValueTypeInfo[$key] | Where-Object { $_.Value -eq $property.Value -and $_.Name -notlike '*Value' }).Name
[pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
Stream = $stream.Name
Setting = $key
Value = $stream.Settings[$key]
DisplayValue = if ($stream.Settings[$key] -ne $displayValue) { $displayValue } else { $null }
} | Export-Excel @excelParams -WorksheetName CameraStreamSettings -TableName CameraStreamSettingsList
}
}
Write-Verbose "Retrieving system PTZ presets for $($cam.Name)"
$cam.PtzPresetFolder.PtzPresets | Foreach-Object {
$ptzPreset = $_
$usingDeviceBasedPresets = $false
if ($ptzPreset.DevicePreset -eq $false)
{
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
DefaultPreset = $ptzPreset.DefaultPreset
Pan = $ptzPreset.Pan
Tilt = $ptzPreset.Tilt
Zoom = $ptzPreset.Zoom
Name = $ptzPreset.Name
Description = $ptzPreset.Description
}
$null = $obj | Export-Excel @excelParams -WorksheetName CameraPtzPresets -TableName CameraPtzPresetsList
} else
{
$usingDeviceBasedPresets = $true
}
}
Write-Verbose "Retrieving system PTZ patrols for $($cam.Name)"
$ptzPresets = $cam.PtzPresetFolder.PtzPresets
$cam.PatrollingProfileFolder.PatrollingProfiles | Foreach-Object {
$ptzPatrol = $_
if ($usingDeviceBasedPresets -eq $false)
{
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
Name = $ptzPatrol.Name
Description = $ptzPatrol.Description
CustomizeTransitions = $ptzPatrol.CustomizeTransitions
InitSpeed = $ptzPatrol.InitSpeed
InitTransitionTime = $ptzPatrol.InitTransitionTime
EndPresetId = $ptzPatrol.EndPresetId
EndPresetName = ($ptzPresets | Where-Object { $_.Id -eq $ptzPatrol.EndPresetId}).Name
EndSpeed = $ptzPatrol.EndSpeed
EndTransitionTime = $ptzPatrol.EndTransitionTime
}
$null = $obj | Export-Excel @excelParams -WorksheetName CameraPtzPatrols -TableName CameraPtzPatrolsList
Write-Verbose "Retrieving PTZ patrols presets for $($ptzPatrol.Name) on $($cam.Name)"
$patrolChildren = (Get-ConfigurationItem -Path "PatrollingProfile[$($ptzPatrol.Id)]").Children
for ($i=0;$i -lt $patrolChildren.Count;$i++) {
$patrolChild = $patrolChildren | Where-Object {$_.Path -eq "PatrollingEntry[$($i)]"}
$presetId = ($patrolChild.Properties | Where-Object Key -eq PresetId).Value
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
Patrol = $ptzPatrol.Name
Order = ($patrolChild.Properties | Where-Object Key -eq Order).Value
WaitTime = ($patrolChild.Properties | Where-Object Key -eq WaitTime).Value
Speed = ($patrolChild.Properties | Where-Object Key -eq Speed).Value
TransitionTime = ($patrolChild.Properties | Where-Object Key -eq TransitionTime).Value
PresetName = ($ptzPresets | Where-Object { $_.Id -eq $presetId }).Name
}
$null = $obj | Export-Excel @excelParams -WorksheetName CameraPtzPatrolPresets -TableName CameraPtzPatrolPresetsList
}
}
}
Write-Verbose "Retrieving related devices, shortcut number, and multicast setting for $($cam.Name)"
$clientSettings = $cam.ClientSettingsFolder.ClientSettings[0]
if(-not [string]::IsNullOrEmpty($clientSettings.Related))
{
$relatedDevices = New-Object System.Collections.Generic.List[PSCustomObject]
$clientSettings.Related.Split(",") | ForEach-Object {
$deviceType = $_.Split("[") | Select-Object -First 1
$deviceCI = Get-ConfigurationItem -Path $_
$deviceProperties = $deviceCI.Properties
$hardwarePath = $deviceCI.ParentPath.Split("/") | Select-Object -First 1
$hardwareCI = Get-ConfigurationItem -Path $hardwarePath
$hardwareProperties = $hardwareCI.Properties
$recordingServerPath = $hardwareCI.ParentPath.Split("/") | Select-Object -First 1
$recordingServerCI = Get-ConfigurationItem -Path $recordingServerPath
$recordingServerProperties = $recordingServerCI.Properties
$row = [PSCustomObject]@{
RelatedDeviceType = $deviceType
RelatedRecordingServerName = ($recordingServerProperties | Where-Object Key -eq Name).Value
RelatedRecordingServerHostName = ($recordingServerProperties | Where-Object Key -eq HostName).Value
RelatedHardwareName = ($hardwareProperties | Where-Object Key -eq Name).Value
RelatedHardwareAddress = ($hardwareProperties | Where-Object Key -eq Address).Value
RelatedDeviceName = ($deviceProperties | Where-Object Key -eq Name).Value
RelatedDeviceChannel = ($deviceProperties | Where-Object Key -eq Channel).Value
}
$relatedDevices.Add($row)
}
} else
{
$relatedDevices = $null
}
foreach ($relatedDevice in $relatedDevices)
{
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
Channel = $cam.Channel
RelatedDeviceType = $relatedDevice.RelatedDeviceType
RelatedRecordingServerName = $relatedDevice.RelatedRecordingServerName
RelatedRecordingServerHostName = $relatedDevice.RelatedRecordingServerHostName
RelatedHardwareName = $relatedDevice.RelatedHardwareName
RelatedHardwareAddress = $relatedDevice.RelatedHardwareAddress
RelatedDeviceName = $relatedDevice.RelatedDeviceName
RelatedDeviceChannel = $relatedDevice.RelatedDeviceChannel
Shortcut = $clientSettings.Shortcut
MulticastEnabled = $clientSettings.MulticastEnabled
}
$null = $obj | Export-Excel @excelParams -WorksheetName CameraRelatedDevices -TableName CameraRelatedDevicesList
}
Write-Verbose "Retrieving event properties for $($cam.Name)"
$obj = [ordered]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Camera = $cam.Name
}
$null = $cam | Get-DeviceEvents | ForEach-Object {
$eventInfo = $_
$obj.EventName = $eventInfo.Event
$obj.Used = $eventInfo.Used
$obj.Enabled = $eventInfo.Enabled
$obj.EventIndex = $eventInfo.EventIndex
$obj.IndexName = $eventInfo.IndexName
[pscustomobject]$obj | Export-Excel @excelParams -WorksheetName CameraEvents -TableName CameraEventsList
}
}
}
if ('Microphones' -in $IncludedDevices) {
$hw | Get-Microphone | Foreach-Object {
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$mic = $_
if (($EnableFilter -eq 'Enabled' -and -not $mic.Enabled) -or ($EnableFilter -eq 'Disabled' -and $mic.Enabled)) {
Write-Verbose "Skipping microphone $($mic.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving microphone properties for $($mic.Name)"
$null = $mic | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Microphones -TableName MicrophonesList }
Write-Verbose "Retrieving general setting properties for $($mic.Name)"
$null = $mic | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName MicrophoneGeneralSettings -TableName MicrophoneGeneralSettingsList }
Write-Verbose "Retrieving stream properties for $($mic.Name)"
$deviceDriverSettings | Select-Object -ExpandProperty Children | Where-Object ItemType -eq Stream | Select-Object -ExpandProperty Properties | Where-Object IsSettable | Foreach-Object {
if ($null -eq $_) {
return
}
$property = $_
$key = $property.Key
$displayValue = ($property.ValueTypeInfos | Where-Object Value -eq $property.Value).Name
if ($key -match '^([^/]+/)(?<key>[^/]+)(/[^/]+)?$') {
$key = $Matches.key
}
$obj = [pscustomobject]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Microphone = $mic.Name
Channel = $mic.Channel
Setting = $key
Value = $property.Value
DisplayValue = if ($property.ValueType -eq 'Enum' -and $displayValue -ne $property.Value) { $displayValue } else { $null }
}
$null = $obj | Export-Excel @excelParams -WorksheetName MicrophoneStreamSettings -TableName MicrophoneStreamSettingsList
}
Write-Verbose "Retrieving event properties for $($mic.Name)"
$obj = [ordered]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Microphone = $mic.Name
}
$null = $mic | Get-DeviceEvents | ForEach-Object {
$eventInfo = $_
$obj.EventName = $eventInfo.Event
$obj.Used = $eventInfo.Used
$obj.Enabled = $eventInfo.Enabled
$obj.EventIndex = $eventInfo.EventIndex
$obj.IndexName = $eventInfo.IndexName
[pscustomobject]$obj | Export-Excel @excelParams -WorksheetName MicrophoneEvents -TableName MicrophoneEventsList
}
}
}
if ('Speakers' -in $IncludedDevices) {
$hw | Get-Speaker | Foreach-Object {
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$speaker = $_
if (($EnableFilter -eq 'Enabled' -and -not $speaker.Enabled) -or ($EnableFilter -eq 'Disabled' -and $speaker.Enabled)) {
Write-Verbose "Skipping speaker $($speaker.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving speaker properties for $($speaker.Name)"
$null = $speaker | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Speakers -TableName SpeakersList }
Write-Verbose "Retrieving general setting properties for $($speaker.Name)"
$null = $speaker | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName SpeakerGeneralSettings -TableName SpeakerGeneralSettingsList }
Write-Verbose "Retrieving event properties for $($speaker.Name)"
$obj = [ordered]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Speaker = $speaker.Name
}
$null = $speaker | Get-DeviceEvents | ForEach-Object {
$eventInfo = $_
$obj.EventName = $eventInfo.Event
$obj.Used = $eventInfo.Used
$obj.Enabled = $eventInfo.Enabled
$obj.EventIndex = $eventInfo.EventIndex
$obj.IndexName = $eventInfo.IndexName
[pscustomobject]$obj | Export-Excel @excelParams -WorksheetName SpeakerEvents -TableName SpeakerEventsList
}
}
}
if ('Metadata' -in $IncludedDevices) {
$hw | Get-Metadata | Foreach-Object {
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$metadata = $_
if (($EnableFilter -eq 'Enabled' -and -not $metadata.Enabled) -or ($EnableFilter -eq 'Disabled' -and $metadata.Enabled)) {
Write-Verbose "Skipping metadata $($metadata.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving metadata properties for $($metadata.Name)"
$null = $metadata | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Metadata -TableName MetadataList }
Write-Verbose "Retrieving metadata general settings for $($metadata.Name)"
$null = $metadata | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName MetadataGeneralSettings -TableName MetadataGeneralSettingsList }
}
}
if ('Inputs' -in $IncludedDevices) {
$hw | Get-Input | Foreach-Object {
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$inputEvent = $_
if (($EnableFilter -eq 'Enabled' -and -not $inputEvent.Enabled) -or ($EnableFilter -eq 'Disabled' -and $inputEvent.Enabled)) {
Write-Verbose "Skipping input $($inputEvent.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving input properties for $($inputEvent.Name)"
$null = $inputEvent | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Inputs -TableName InputsList }
Write-Verbose "Retrieving input general settings for $($inputEvent.Name)"
$null = $inputEvent | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName InputGeneralSettings -TableName InputGeneralSettingsList }
Write-Verbose "Retrieving event properties for $($inputEvent.Name)"
$obj = [ordered]@{
RecordingServer = $recorderMap[$hw.ParentItemPath].Name
Hardware = $hw.Name
Input = $inputEvent.Name
}
$null = $inputEvent | Get-DeviceEvents | ForEach-Object {
$eventInfo = $_
$obj.EventName = $eventInfo.Event
$obj.Used = $eventInfo.Used
$obj.Enabled = $eventInfo.Enabled
$obj.EventIndex = $eventInfo.EventIndex
$obj.IndexName = $eventInfo.IndexName
[pscustomobject]$obj | Export-Excel @excelParams -WorksheetName InputEvents -TableName InputEventsList
}
}
}
if ('Outputs' -in $IncludedDevices) {
$hw | Get-Output | Foreach-Object {
$progress.CurrentOperation = '{0} "{1}"' -f [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($_.Path).ItemType, $_.Name
Write-Progress @progress
$output = $_
if (($EnableFilter -eq 'Enabled' -and -not $output.Enabled) -or ($EnableFilter -eq 'Disabled' -and $output.Enabled)) {
Write-Verbose "Skipping output $($output.Name) due to the EnableFilter value of $EnableFilter"
return
}
Write-Verbose "Retrieving output properties for $($output.Name)"
$null = $output | Get-DeviceProperties | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName Outputs -TableName OutputsList }
Write-Verbose "Retrieving output general settings for $($output.Name)"
$null = $output | Get-GeneralSettings | Foreach-Object { $_ | Export-Excel @excelParams -WorksheetName OutputGeneralSettings -TableName OutputGeneralSettingsList }
}
}
}
$progress.PercentComplete = 100
$progress.Completed = $true
Write-Progress @progress
}
end {
$excelPackage.Workbook.Worksheets.Name | Foreach-Object {
if ($null -eq $excelPackage.Workbook.Worksheets[$_].GetValue(1, 1)) {
$excelPackage.Workbook.Worksheets.Delete($_)
}
}
$excelPackage | Close-ExcelPackage
}
}
function Import-VmsHardwareExcel {
<#
.SYNOPSIS
Imports hardware configuration from an Excel .XLSX document and adds and
optionally updates hardware based.
.DESCRIPTION
The `Import-VmsHardwareExcel` cmdlet accepts a path to an existing Excel
.XLSX document, and imports the hardware configuration. The cmdlet can add
new devices and update the settings of existing devices if the values in
the Excel document differ from the live values.
Depending on the content of the Excel document, the settings imported can
include hardware, general settings, cameras, microphones, speakers, inputs,
outputs, metadata, and the corresponding general settings, settings for
streams, recording, events, motion, and more.
The format of the Excel document, and the valid values for various settings
is challenging to document. The best way to perform a successful import is
to add and configure a representative sample of devices, and then use
`Export-VmsHardwareExcel` to generate a configuration export. You can then
use the export as a reference to build a document to import.
.PARAMETER Path
Specifies a path to an existing Excel document in .XLSX format. While the
`ImportExcel` module supports reading from password protected files, this
has not been extended to this cmdlet. If no path is provided, an open-file
dialog will be shown.
.PARAMETER UpdateExisting
If hardware defined in the Excel document is already added, it will not be
modified by default. If you wish to update the settings for existing
hardware during an import, this switch can be used.
.EXAMPLE
Update-VmsHardwareExcel -Path ~\Desktop\hardware.xlsx -Verbose
Imports the hardware.xlsx file on the current user's desktop. If any cameras
in the Excel document are already added, they will be ignored and their
settings will not be modified if they have drifted from the configuration
defined in the document.
.EXAMPLE
Update-VmsHardwareExcel -Path ~\Desktop\hardware.xlsx -UpdateExisting -Verbose
Imports the hardware.xlsx file on the current user's desktop. If any cameras
in the Excel document are already added, they will be updated to reflect the
configuration defined in the document.
#>
[CmdletBinding()]
param (
[Parameter()]
[string]
$Path,
[Parameter()]
[switch]
$UpdateExisting
)
begin {
if ($null -eq (Get-VmsManagementServer -ErrorAction 'SilentlyContinue')) {
Connect-ManagementServer -ShowDialog -AcceptEula -Force -ErrorAction Stop
}
if ([string]::IsNullOrWhiteSpace($Path)) {
$Path = Show-FileDialog -OpenFile
}
try {
$excelPackage = Open-ExcelPackage -Path $Path
$worksheets = $excelPackage.Workbook.Worksheets.Name
$data = @{
Hardware = [system.collections.generic.list[pscustomobject]]::new()
HardwareGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
HardwareEvents = [system.collections.generic.list[pscustomobject]]::new()
Cameras = [system.collections.generic.list[pscustomobject]]::new()
CameraGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
CameraStreams = [system.collections.generic.list[pscustomobject]]::new()
CameraStreamSettings = [system.collections.generic.list[pscustomobject]]::new()
CameraPtzPresets = [system.collections.generic.list[pscustomobject]]::new()
CameraPtzPatrols = [system.collections.generic.list[pscustomobject]]::new()
CameraPtzPatrolPresets = [system.collections.generic.list[pscustomobject]]::new()
CameraRelatedDevices = [system.collections.generic.list[pscustomobject]]::new()
CameraEvents = [system.collections.generic.list[pscustomobject]]::new()
Microphones = [system.collections.generic.list[pscustomobject]]::new()
MicrophoneGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
MicrophoneStreamSettings = [system.collections.generic.list[pscustomobject]]::new()
MicrophoneEvents = [system.collections.generic.list[pscustomobject]]::new()
Speakers = [system.collections.generic.list[pscustomobject]]::new()
SpeakerGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
SpeakerEvents = [system.collections.generic.list[pscustomobject]]::new()
Metadata = [system.collections.generic.list[pscustomobject]]::new()
MetadataGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
Inputs = [system.collections.generic.list[pscustomobject]]::new()
InputGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
InputEvents = [system.collections.generic.list[pscustomobject]]::new()
Outputs = [system.collections.generic.list[pscustomobject]]::new()
OutputGeneralSettings = [system.collections.generic.list[pscustomobject]]::new()
}
foreach ($key in $data.Keys) {
if ($key -in $worksheets) {
if ($excelPackage.Workbook.Worksheets[$key].GetValue(1, 1)) {
Import-Excel -ExcelPackage $excelPackage -WorksheetName $key | ForEach-Object {
$data[$key].Add($_)
}
} else {
Write-Verbose "Ignoring worksheet '$key' because the value at 1,1 is null."
}
}
}
} finally {
if ($excelPackage) {
$excelPackage | Close-ExcelPackage -NoSave
}
}
}
process {
if ($data.Hardware.Count -eq 0) {
Write-Error "No hardware entries found in the Hardware worksheet."
return
}
$totalRows = $data.Hardware.Count
$processedRows = 0
$progressParams = @{
Activity = 'Importing hardware configuration from {0}' -f $Path
Id = 42
PercentComplete = 0
CurrentOperation = 'Preparing'
}
Write-Progress @progressParams
$recorders = @{}
$existingHardware = @{}
Get-VmsRecordingServer -PipelineVariable rec | Foreach-Object {
$recorders[$rec.Name] = $rec
$existingHardware[$rec.Name] = @{}
$rec | Get-VmsHardware -PipelineVariable hw | Foreach-Object { if ($uri = $hw.Address -as [uri]) { $existingHardware[$rec.Name][('{0}:{1}' -f $uri.Host, $uri.Port)] = $hw } }
}
foreach ($row in $data.Hardware | Sort-Object RecordingServer) {
$progressParams.PercentComplete = [math]::Round(($processedRows++) / $totalRows * 100)
$progressParams.CurrentOperation = '{0} ({1})' -f $row.Name, $row.Address
Write-Progress @progressParams
try {
$recorder = if ($row.RecordingServer) { $recorders[$row.RecordingServer] } else { $null }
if (-not $recorder) {
Write-Warning "Recording server '$($row.RecordingServer)' not found. Skipping hardware '$($row.Name)' ($($row.Address))."
continue
}
$params = @{
Name = $row.Name
HardwareAddress = $row.Address -as [uri]
Credential = try { [pscredential]::new($row.UserName, ($row.Password | ConvertTo-SecureString -AsPlainText -Force)) } catch { $null };
DriverNumber = $row.DriverNumber -as [int]
RecordingServer = $recorder
ErrorAction = 'Stop'
}
if ([string]::IsNullOrWhiteSpace($params.Name)) {
$params.Remove('Name')
}
if (-not $params.HardwareAddress -or -not $params.HardwareAddress.IsAbsoluteUri) {
Write-Warning "Hardware '$($row.Name)' must have a valid address in the Address column. The value '$($row.Address)' is not a valid absolute URI. Example: http://192.168.1.101"
continue
}
$key = '{0}:{1}' -f $params.HardwareAddress.Host, $params.HardwareAddress.Port
if (($hardware = $existingHardware[$row.RecordingServer][$key])) {
if (-not $UpdateExisting) {
Write-Verbose "Skipping the hardware at $($params.HardwareAddress) because it is already added to $($recorder.Name). To Update existing hardware/devices, use the 'UpdateExisting' switch."
continue
}
} else {
if (-not $params.DriverNumber) {
$scanParams = @{
RecordingServer = $recorder
Address = $params.HardwareAddress
}
if ($row.DriverGroup) {
$scanParams.DriverFamily = $row.DriverGroup
}
if ($params.Credential) {
$scanParams.Credential = $params.Credential
} else {
$scanParams.UseDefaultCredentials
}
Write-Verbose "Scanning hardware at $($row.Address) for driver discovery"
$scan = Start-VmsHardwareScan @scanParams
if ($scan.HardwareScanValidated) {
$params.Remove('DriverNumber')
$params.HardwareDriverPath = $scan.HardwareDriverPath
if ($null -eq $params.Credential) {
$params.Credential = [pscredential]::new($scan.UserName, ($scan.Password | ConvertTo-SecureString -AsPlainText -Force))
}
} else {
Write-Error -Message "Hardware scan failed for '$($params.Name)' ($($params.HardwareAddress)). Result: $($scan.ErrorText)"
continue
}
}
$hardware = Add-VmsHardware @params
}
$hardware.Name = if ($row.Name) { $row.Name } else { $hardware.Name }
$hardware.Enabled = 'False' -ne $row.Enabled
$hardware.Description = $row.Description
$hardware.Save()
$settings = $data.HardwareGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($settings) {
Set-GeneralSettings -Device $hardware -Settings $settings
}
$settings = $data.HardwareEvents | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($settings) {
Set-DeviceEvents -Device $hardware -Settings $settings
}
$cameraRows = $data.Cameras | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($cameraRows) {
Write-Verbose "Updating camera properties for $($hardware.Name)"
$hardware | Get-VmsCamera -EnableFilter All | Where-Object Channel -in $cameraRows.Channel | Foreach-Object {
$camera = $_
Set-DeviceProperties -Device $_ -Settings ($cameraRows | Where-Object Channel -eq $_.Channel | Select-Object -First 1)
$generalSettings = $data.CameraGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $_ -Settings $generalSettings
}
$eventSettings = $data.CameraEvents | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name }
if ($eventSettings) {
Set-DeviceEvents -Device $camera -Settings $eventSettings
}
$data.CameraStreams | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name } | ForEach-Object {
$streamRow = $_
$stream = $camera | Get-VmsCameraStream -Name $streamRow.Name -ErrorAction SilentlyContinue
if ($stream) {
$streamParams = @{
Verbose = $MyInvocation.BoundParameters['Verbose'] -eq $true
}
if ($streamRow.DisplayName) { $streamParams.DisplayName = $streamRow.DisplayName }
if ('True' -eq $streamRow.Recorded) { $streamParams.Recorded = $true }
if ('True' -eq $streamRow.LiveDefault) { $streamParams.LiveDefault = $true }
if ($streamRow.LiveMode) { $streamParams.LiveMode = $streamRow.LiveMode }
if ($streamParams.Count -gt 0) {
$stream | Set-VmsCameraStream @streamParams
}
} else {
Write-Warning "No stream found on $($camera.Name) with the name '$($streamRow.Name)'"
}
}
$data.CameraStreamSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name -and $_.Setting -and $_.Value } | Group-Object Stream | Foreach-Object {
$streamName = $_.Name
$streamSettings = @{}
$_.Group | Foreach-Object { $streamSettings[$_.Setting] = $_.Value }
$stream = $camera | Get-VmsCameraStream -Name $streamName -ErrorAction Ignore
if ($stream) {
$stream | Set-VmsCameraStream -Settings $streamSettings -Verbose:($VerbosePreference -eq 'Continue' )
} else {
Write-Warning "No stream found on $($camera.Name) with the name '$($streamRow.Name)'"
}
}
$data.CameraPtzPresets | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name } | ForEach-Object {
$ptzPresetRow = $_
if ($ptzPresetRow.Name -notin $camera.PtzPresetFolder.PtzPresets.Name)
{
$newPtzPreset = $camera.PtzPresetFolder.AddPtzPreset($ptzPresetRow.Name,$ptzPresetRow.Description,$ptzPresetRow.Pan,$ptzPresetRow.Tilt,$ptzPresetRow.Zoom)
if ($ptzPresetRow.DefaultPreset -eq $true)
{
$null = $camera.PtzPresetFolder.DefaultPtzPreset($newPtzPreset.Path)
}
}
}
$data.CameraPtzPatrols | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name } | ForEach-Object {
$ptzPatrolRow = $_
if ($ptzPatrolRow.Name -notin $camera.PatrollingProfileFolder.PatrollingProfiles.Name)
{
$endPresetId = ($camera.PtzPresetFolder.PtzPresets | Where-Object { $_.Name -eq $ptzPatrolRow.EndPresetName }).Id
$newPtzPatrol = $camera.PatrollingProfileFolder.AddPatrollingProfile($ptzPatrolRow.Name, $ptzPatrolRow.Description, $ptzPatrolRow.CustomizeTransitions, $ptzPatrolRow.InitSpeed, $ptzPatrolRow.InitTransitionTime, $endPresetId, $ptzPatrolRow.EndSpeed, $ptzPatrolRow.EndTransitionTime)
$newPtzPatrol = $camera.PatrollingProfileFolder.PatrollingProfiles | Where-Object { $_.Path -eq $newPtzPatrol.Path }
$index = 0
$data.CameraPtzPatrolPresets | Where-Object { $_.Patrol -eq $newPtzPatrol.Name } | ForEach-Object {
$ptzPatrolPresetRow = $_
$patrolPresetId = ($camera.PtzPresetFolder.PtzPresets | Where-Object { $_.Name -eq $ptzPatrolPresetRow.PresetName }).Id
$null = $newPtzPatrol.AddPatrollingEntry($ptzPatrolPresetRow.Order, $patrolPresetId, $ptzPatrolPresetRow.WaitTime)
$patrol = Get-ConfigurationItem -Path "PatrollingProfile[$($newPtzPatrol.Id)]"
if ($newPtzPatrol.CustomizeTransitions)
{
($patrol.Children[$index].Properties | Where-Object { $_.Key -eq 'Speed' }).Value = $ptzPatrolPresetRow.Speed
($patrol.Children[$index].Properties | Where-Object { $_.Key -eq 'TransitionTime' }).Value = $ptzPatrolPresetRow.TransitionTime
}
### TODO: Refactor this section so Set-ConfigurationItem only needs to be called after the entire Patrol object has been updated
$null = Set-ConfigurationItem -ConfigurationItem $patrol
$index++
}
}
}
}
} else {
Write-Verbose "No cameras to configure for $($hardware.Name)"
}
$rows = $data.Microphones | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($rows) {
Write-Verbose "Updating microphone properties for $($hardware.Name)"
$hardware | Get-Microphone | Where-Object Channel -in $rows.Channel | Foreach-Object {
$device = $_
Set-DeviceProperties -Device $device -Settings ($rows | Where-Object Channel -eq $device.Channel | Select-Object -First 1)
$generalSettings = $data.MicrophoneGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Microphone -eq $device.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $device -Settings $generalSettings
}
$eventSettings = $data.MicrophoneEvents | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Microphone -eq $device.Name }
if ($eventSettings) {
Set-DeviceEvents -Device $device -Settings $eventSettings
}
}
} else {
Write-Verbose "No microphones to configure for $($hardware.Name)"
}
$rows = $data.Speakers | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($rows) {
Write-Verbose "Updating speaker properties for $($hardware.Name)"
$hardware | Get-Speaker | Where-Object Channel -in $rows.Channel | Foreach-Object {
$device = $_
Set-DeviceProperties -Device $device -Settings ($rows | Where-Object Channel -eq $device.Channel | Select-Object -First 1)
$generalSettings = $data.SpeakerGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Speaker -eq $device.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $device -Settings $generalSettings
}
}
} else {
Write-Verbose "No speakers to configure for $($hardware.Name)"
}
$rows = $data.Metadata | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($rows) {
Write-Verbose "Updating metadata properties for $($hardware.Name)"
$hardware | Get-Metadata | Where-Object Channel -in $rows.Channel | Foreach-Object {
$device = $_
Set-DeviceProperties -Device $device -Settings ($rows | Where-Object Channel -eq $device.Channel | Select-Object -First 1)
$generalSettings = $data.MetadataGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Metadata -eq $device.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $device -Settings $generalSettings
}
}
} else {
Write-Verbose "No metadata to configure for $($hardware.Name)"
}
$rows = $data.Inputs | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($rows) {
Write-Verbose "Updating IO input properties for $($hardware.Name)"
$hardware | Get-Input | Where-Object Channel -in $rows.Channel | Foreach-Object {
$device = $_
Set-DeviceProperties -Device $device -Settings ($rows | Where-Object Channel -eq $device.Channel | Select-Object -First 1)
$generalSettings = $data.InputGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.InputEvent -eq $device.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $device -Settings $generalSettings
}
}
} else {
Write-Verbose "No inputs to configure for $($hardware.Name)"
}
$rows = $data.Outputs | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($rows) {
Write-Verbose "Updating IO output properties for $($hardware.Name)"
$hardware | Get-Output | Where-Object Channel -in $rows.Channel | Foreach-Object {
$device = $_
Set-DeviceProperties -Device $device -Settings ($rows | Where-Object Channel -eq $device.Channel | Select-Object -First 1)
$generalSettings = $data.OutputGeneralSettings | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Output -eq $device.Name }
if ($generalSettings) {
Set-GeneralSettings -Device $device -Settings $generalSettings
}
}
} else {
Write-Verbose "No outputs to configure for $($hardware.Name)"
}
} catch {
Write-Error -ErrorRecord $_
}
}
$progressParams.PercentComplete = 100
$progressParams.Completed = $true
Write-Progress @progressParams
Clear-VmsCache
$totalRows = $data.Hardware.Count
$processedRows = 0
$progressParams = @{
Activity = 'Configuring related devices'
Id = 43
PercentComplete = 0
}
Write-Progress @progressParams
foreach ($row in $data.Hardware | Sort-Object RecordingServer) {
$progressParams.PercentComplete = [math]::Round(($processedRows++) / $totalRows * 100)
$progressParams.CurrentOperation = '{0} ({1})' -f $row.Name, $row.Address
Write-Progress @progressParams
$recorder = if ($row.RecordingServer) { $recorders[$row.RecordingServer] } else { $null }
$params = @{
Name = $row.Name
HardwareAddress = $row.Address -as [uri]
RecordingServer = $recorder
ErrorAction = 'Stop'
}
if ([string]::IsNullOrWhiteSpace($params.Name)) {
$params.Remove('Name')
}
$key = '{0}:{1}' -f $params.HardwareAddress.Host, $params.HardwareAddress.Port
if (($hardware = $existingHardware[$row.RecordingServer][$key])) {
if (-not $UpdateExisting) {
Write-Verbose "Skipping the hardware at $($params.HardwareAddress) because it is already added to $($recorder.Name). To Update existing hardware/devices, use the 'UpdateExisting' switch."
continue
}
}
$hardware = Get-VmsHardware -Name $row.Name
$cameraRows = $data.Cameras | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name }
if ($cameraRows) {
foreach ($camera in $hardware | Get-VmsCamera -EnableFilter All)
{
$relatedDevicesString = $null
$data.CameraRelatedDevices | Where-Object { $_.RecordingServer -eq $recorder.Name -and $_.Hardware -eq $hardware.Name -and $_.Camera -eq $camera.Name -and $_.Channel -eq $camera.Channel } | ForEach-Object {
$relatedDevicesRow = $_
if ([string]::IsNullOrEmpty($relatedDevicesString))
{
$relatedRec = Get-VmsRecordingServer -HostName $relatedDevicesRow.RelatedRecordingServerHostName
$relatedHW = Get-VmsHardware -RecordingServer $relatedRec | Where-Object Address -eq $relatedDevicesRow.RelatedHardwareAddress
switch ($relatedDevicesRow.RelatedDeviceType)
{
Metadata {$relatedDeviceItem = Get-Metadata -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
Microphone {$relatedDeviceItem = Get-Microphone -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
Speaker {$relatedDeviceItem = Get-Speaker -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
}
[string]$relatedDevicesString = $relatedDeviceItem.Path
} else {
$relatedRec = Get-VmsRecordingServer -HostName $relatedDevicesRow.RelatedRecordingServerHostName
$relatedHW = Get-VmsHardware -RecordingServer $relatedRec | Where-Object Address -eq $relatedDevicesRow.RelatedHardwareAddress
switch ($relatedDevicesRow.RelatedDeviceType)
{
Metadata {$relatedDeviceItem = Get-Metadata -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
Microphone {$relatedDeviceItem = Get-Microphone -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
Speaker {$relatedDeviceItem = Get-Speaker -Hardware $relatedHW -Channel $relatedDevicesRow.Channel}
}
[string]$relatedDevicesString += ",$($relatedDeviceItem.Path)"
}
}
$clientSettingsItem = $camera.ClientSettingsFolder.ClientSettings[0]
$clientSettingsItem.Related = $relatedDevicesString
$clientSettingsItem.Save()
}
}
}
$progressParams.PercentComplete = 100
$progressParams.Completed = $true
Write-Progress @progressParams
}
}
#endregion
Export-ModuleMember -Function Export-VmsHardwareExcel, Import-VmsHardwareExcel