Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active July 1, 2025 23:51
Show Gist options
  • Save joshooaj/a200f7892b9338ca815bd3ac137ca899 to your computer and use it in GitHub Desktop.
Save joshooaj/a200f7892b9338ca815bd3ac137ca899 to your computer and use it in GitHub Desktop.
Export and import XProtect VMS device permissions
#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