Last active
July 1, 2025 23:51
-
-
Save joshooaj/a200f7892b9338ca815bd3ac137ca899 to your computer and use it in GitHub Desktop.
Export and import XProtect VMS device permissions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -Module MilestonePSTools | |
function Export-DevicePermissions { | |
<# | |
.SYNOPSIS | |
Export all device permissions for a given role to a JSON file. | |
.DESCRIPTION | |
Export all device permissions for a given role to a JSON file. Note that | |
this does not include the Overall Security permissions or other settings. | |
.PARAMETER Role | |
Specifies the role for which to export device permissions. The role must be | |
user-defined - you cannot export permissions for the Administrator role. | |
.PARAMETER DeviceType | |
Specifies the type of devices to include in the export. The default is | |
'Camera'. You may specify multiple types, such as 'Camera', 'Microphone', | |
'Speaker', 'Metadata', 'Input', and 'Output'. | |
.PARAMETER Path | |
Specifies the file path where the JSON data will be saved. The file may not | |
already exist. | |
.EXAMPLE | |
Get-VmsRole -RoleType UserDefined | ForEach-Object { | |
Export-DevicePermissions -Role $_ -Path "$($_.Name)_Permissions.json" | |
} | |
Exports all camera permissions for all user-defined roles to one JSON file | |
per role using the role name for the file name. | |
.EXAMPLE | |
Get-VmsRole -RoleType UserDefined | ForEach-Object { | |
$params = @{ | |
Role = $_ | |
Path = "$($_.Name)_Permissions.json" | |
DeviceType = 'Camera', 'Microphone', 'Speaker', 'Metadata' | |
} | |
Export-DevicePermissions @params | |
} | |
Exports all camera, microphone, speaker, and metadata permissions for all | |
user-defined roles to one JSON file per role using the role name for the | |
file name. | |
.EXAMPLE | |
Export-DevicePermissions -Role Operators -DeviceType Camera, Microphone -Path Operators.json | |
Exports all camera, and microphone permissiosn for the Operators role. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[ArgumentCompleter([MilestonePSTools.Utility.MipItemNameCompleter[VideoOS.Platform.ConfigurationItems.Role]])] | |
[MilestonePSTools.Utility.MipItemTransformation([VideoOS.Platform.ConfigurationItems.Role])] | |
[VideoOS.Platform.ConfigurationItems.Role] | |
$Role, | |
[Parameter()] | |
[ValidateSet('Camera', 'Microphone', 'Speaker', 'Metadata', 'Input', 'Output')] | |
[string[]] | |
$DeviceType = 'Camera', | |
[Parameter(Mandatory)] | |
[string] | |
$Path | |
) | |
process { | |
if (Test-Path -Path $Path) { | |
Write-Error "The file at $Path already exists. Please use a different path or remove the existing file." | |
return | |
} | |
if (!($Role.RoleType -eq 'UserDefined')) { | |
Write-Warning "This function only applies to UserDefined roles. The role type is: $($Role.RoleType)" | |
return | |
} | |
$data = [pscustomobject]@{ | |
RoleName = $Role.Name | |
RolePath = $Role.Path | |
Devices = [system.collections.generic.list[pscustomobject]]::new() | |
} | |
foreach ($hardware in Get-VmsHardware) { | |
foreach ($device in Get-VmsDevice -Type $DeviceType -Hardware $hardware) { | |
$permissions = [ordered]@{} | |
$taskInfo = $device.ChangeSecurityPermissions($Role.Path) | |
foreach ($key in $taskInfo.GetPropertyKeys() | Sort-Object) { | |
$permissions[$key] = $taskInfo.GetProperty($key) | |
} | |
$data.Devices.Add( | |
[pscustomobject]@{ | |
DeviceName = $device.Name | |
DevicePath = $device.Path | |
Permissions = $permissions | |
} | |
) | |
} | |
} | |
$data | ConvertTo-Json -Depth 3 -Compress | Out-File -FilePath $Path | |
Get-Item -Path $Path | |
} | |
} | |
function Import-DevicePermissions { | |
<# | |
.SYNOPSIS | |
Import device permissions from a JSON file created by | |
Export-DevicePermissions. | |
.DESCRIPTION | |
Import devivce permissions from a JSON file created by | |
Export-DevicePermissions. Each JSON file represents all device permissions | |
for a single role. The role must already exist in the VMS either by ID, or by name. | |
.PARAMETER Path | |
Specifies the path to the JSON file to be imported. | |
.PARAMETER MatchByName | |
Specifies that the role and devices should be matched by name instead of by | |
ID. You would use this option if you have created a copy of an existing VMS | |
where the devices and roles have the same names, but different IDs. | |
.EXAMPLE | |
Import-DevicePermissions -Path Operators.json | |
Import device permissions from the file Operators.json. The role and device | |
IDs must be present in the connected VMS or the import will fail. | |
.EXAMPLE | |
Import-DevicePermissions -Path Operators.json -MatchByName | |
Import device permissions from the file Operators.json. The role and device | |
names must be present in the connected VMS or the import will fail. | |
#> | |
[CmdletBinding()] | |
param( | |
# Path to a file created by Export-DevicePermissions. | |
[Parameter(Mandatory)] | |
[string] | |
$Path, | |
# Match roles and devices by name instead of ID. | |
[Parameter()] | |
[switch] | |
$MatchByName | |
) | |
process { | |
$data = Get-Content -Path $Path | ConvertFrom-Json | |
if ([string]::IsNullOrWhiteSpace($data.RoleName)) { | |
Write-Error "The JSON data is invalid. Expected a 'RoleName' property with a non-empty value." | |
return | |
} | |
if ([string]::IsNullOrWhiteSpace($data.RolePath)) { | |
Write-Error "The JSON data is invalid. Expected a 'RolePath' property with a non-empty value." | |
return | |
} | |
if ($data.Devices.Count -eq 0) { | |
Write-Error "The JSON data is invalid. Expected a 'Devices' array property with at least one record." | |
return | |
} | |
# Find the matching role using either the name or path | |
if ($MatchByName) { | |
try { | |
$role = Get-VmsRole -Name $data.RoleName -ErrorAction Stop | |
} catch { | |
Write-Error -Message "Could not find role matching the name '$($data.RoleName)'. Check if the role exists, and create it if necessary." | |
return | |
} | |
if ($role.Count -gt 1) { | |
Write-Error "More than one role found matching the name '$($data.RoleName)'." | |
return | |
} | |
} else { | |
try { | |
$role = [videoos.platform.configurationitems.role]::new((Get-VmsManagementServer).ServerId, $data.RolePath) | |
} catch { | |
Write-Error -Message "Could not find role using the configuration item path '$($data.RolePath)'. Consider using the -MatchByName parameter to match roles by name instead." | |
return | |
} | |
} | |
Write-Verbose -Message "Importing device permissions for role '$($role.Name)'" | |
$svc = Get-IConfigurationService | |
foreach ($record in $data.Devices) { | |
if ($MatchByName) { | |
$type = [VideoOS.Platform.Proxy.ConfigApi.ConfigurationItemPath]::new($record.DevicePath).ItemType | |
$type = $type -replace 'Event$', '' # Remove 'Event' suffix on InputEvent | |
# If there is a 'Camera 1' and a 'Camera 11', then a search for 'Camera 1' will return both. | |
# So we filter the results of Get-VmsDevice for an exact (case-insensitive) name match. | |
$device = Get-VmsDevice -Name $record.DeviceName -Type $type -EnableFilter All | Where-Object Name -EQ $record.DeviceName | |
} else { | |
$device = Get-VmsDevice -Path $record.DevicePath -ErrorAction SilentlyContinue | |
} | |
if ($null -eq $device) { | |
Write-Warning -Message "Could not find a matching device for '$($record.DeviceName) ($($record.DevicePath))'." | |
continue | |
} | |
if ($device.Count -gt 1) { | |
Write-Warning -Message "More than one device found matching the name '$($record.DeviceName)'." | |
continue | |
} | |
Write-Verbose -Message "Applying permissions: Role = $($role.Name); Device = $($device.Name)" | |
$invokeInfo = [VideoOS.ConfigurationApi.ClientService.ConfigurationItem]@{ | |
ItemCategory = 'Item' | |
ItemType = 'InvokeInfo' | |
MethodIds = @(, 'ChangeSecurityPermissions') | |
Path = $device.Path | |
ParentPath = $device.ParentPath | |
} | |
$properties = [system.collections.generic.list[VideoOS.ConfigurationApi.ClientService.Property]]::new() | |
$properties.Add( | |
[VideoOS.ConfigurationApi.ClientService.Property]@{ | |
Key = 'UserPath' | |
Value = $role.Path | |
} | |
) | |
foreach ($key in ($record.Permissions | Get-Member -MemberType NoteProperty).Name) { | |
if ($key -eq 'UserPath') { continue } | |
$properties.Add( | |
[VideoOS.ConfigurationApi.ClientService.Property]@{ | |
Key = $key | |
Value = $record.Permissions.$key | |
} | |
) | |
} | |
$invokeInfo.Properties = $properties | |
try { | |
$null = $svc.InvokeMethod($invokeInfo, 'ChangeSecurityPermissions') | |
} catch { | |
Write-Warning -Message "Failed to apply permissions for device '$($device.Name)' on role '$($role.Name)'." | |
Write-Error -ErrorRecord $_ | |
} | |
} | |
} | |
} | |
Export-ModuleMember -Function Export-DevicePermissions, Import-DevicePermissions |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment