Public/Import-Role.ps1

function Import-Role {
    <#
    .SYNOPSIS
        Imports Milestone user roles from a JSON file
    .DESCRIPTION
        Import a JSON file generated by Export-Role, or by hand or programatically for the purpose of bulk role
        creation or update. If the members of a role are determined by a system other than simple Active Directory
        security groups, you could for example use this function to quickly set the role members for one or more
        roles.
 
        If you do not care to update specific properties like Overall Security or Smart Client Profiles, you can
        set these properties to null and the import procedure will skip that property. This can greatly improve
        the import speed if you know you don't need to update certain properties.
    .EXAMPLE
        PS C:\> Import-Role -Path .\roles.json -DoNotRemoveMembers
        Import all roles from roles.json but do not remove members from any roles if they are not present in the
        Members property of the role in the JSON file. With the DoNotRemoveMembers switch, you will only add
        new users to the role if they are not already present - no users will be removed.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param (
        # Specifies the path to a JSON file containing a list of objects with role information. Consider generating a template using Export-Role if you will be using this to bulk import role configuration
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FromFile')]
        [string]
        $Path,
        # Specifies the pscustomobject array containing the a list of roles to be imported.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromObject')]
        [pscustomobject]
        $InputObject,
        # Specifies that users should not be removed from roles if they are not present in the specified JSON file
        [Parameter()]
        [switch]
        $DoNotRemoveMembers,
        # Specifies that the Administrators role should be modified if the role is present in the specified JSON file and Viewgroups should be deleted if HasMatchingViewGroup is false.
        [Parameter()]
        [switch]
        $Force,
        # Specifies that no public view groups should be created or updated - even if HasMatchingViewGroup and ViewGroupPermissions are not null
        [Parameter()]
        [switch]
        $IgnoreViewGroups
    )

    begin {
        $config = BuildConfigMemoryMap
    }

    process {
        $stopWatch = [Diagnostics.StopWatch]::new()
        <# Import JSON file#>
        $stopWatch.Restart()
        if ($PSCmdlet.ParameterSetName -eq 'FromFile') {
            Write-Verbose "Importing $Path"
            $rows = Get-Content -Path $Path | ConvertFrom-Json
            Write-Verbose "Completed JSON import in $($stopWatch.Elapsed.TotalSeconds) seconds"
        }
        else {
            $rows = $InputObject
        }

        $total = $rows.Count
        if ($total -le 0) {
            $total = 1
        }
        $i = 0
        $progressParams = @{
            Activity = "Importing Roles"
            Status = "Working"
            CurrentOperation = "Waiting"
            PercentComplete = 0
        }
        $rowStopWatch = [Diagnostics.StopWatch]::StartNew()
        foreach ($row in $rows) {
            $progressParams.PercentComplete = $i / $total * 100
            $progressParams.Status = "Imported $i of $total roles"
            $progressParams.CurrentOperation = "Importing $($row.Name)"
            Write-Progress @progressParams
            $i += 1

            $rowStopWatch.Restart()
            Write-Verbose "Processing role '$($row.Name)'"

            <# Create the role or find it in the dictionary #>
            $stopWatch.Restart()
            Write-Verbose "Creating or retrieving role"
            if (-not $config.Roles.ContainsKey($row.Name)) {
                $roleParams = @{
                    Name = $row.Name
                }
                if (-not [string]::IsNullOrWhiteSpace($row.DualAuthorizationRequired)) {
                    $roleParams.RequireDualAuth = $row.DualAuthorizationRequired
                }
                if (-not [string]::IsNullOrWhiteSpace($row.MakeUsersAnonymousDuringPTZSession)) {
                    $roleParams.AnonymousPtz = $row.MakeUsersAnonymousDuringPTZSession
                }
                if (-not [string]::IsNullOrWhiteSpace($row.AllowMobileClientLogOn)) {
                    $roleParams.AllowMobileClient = $row.AllowMobileClientLogOn
                }
                if (-not [string]::IsNullOrWhiteSpace($row.AllowSmartClientLogOn)) {
                    $roleParams.AllowSmartClient = $row.AllowSmartClientLogOn
                }
                if (-not [string]::IsNullOrWhiteSpace($row.AllowWebClientLogOn)) {
                    $roleParams.AllowWebClient = $row.AllowWebClientLogOn
                }
                if (-not [string]::IsNullOrWhiteSpace($row.Description)) {
                    $roleParams.Description = $row.Description
                }
                if ($PSCmdlet.ShouldProcess($row.Name, "Create role")) {
                    $role = Add-Role @roleParams
                    if ($null -eq $role) {
                        Write-Warning "Skipping $($row.Name) because the role does not exist and was unable to be created"
                        continue
                    }
                    else {
                        $config.Roles.$($row.Name) = $role
                    }
                }
                else {
                    continue
                }

            }
            else {
                $role = $config.Roles.$($row.Name)
            }
            Write-Verbose "Completed role creation/retrieval in $($stopWatch.Elapsed.TotalSeconds) seconds"

            <# Check that the properties all match and update them if they don't #>
            $stopWatch.Restart()
            Write-Verbose "Updating direct properties of role if necessary"
            $dirty = $false
            if ($null -ne $row.DualAuthorizationRequired -and $row.DualAuthorizationRequired -ne $role.DualAuthorizationRequired) {
                $role.DualAuthorizationRequired = $row.DualAuthorizationRequired
                $dirty = $true
            }
            if ($null -ne $row.MakeUsersAnonymousDuringPTZSession -and $role.MakeUsersAnonymousDuringPTZSession -ne $row.MakeUsersAnonymousDuringPTZSession) {
                $role.MakeUsersAnonymousDuringPTZSession = $row.MakeUsersAnonymousDuringPTZSession
                $dirty = $true
            }
            if ($null -ne $row.AllowMobileClientLogOn -and $role.AllowMobileClientLogOn -ne $row.AllowMobileClientLogOn) {
                $role.AllowMobileClientLogOn = $row.AllowMobileClientLogOn
                $dirty = $true
            }
            if ($null -ne $row.AllowSmartClientLogOn -and $role.AllowSmartClientLogOn -ne $row.AllowSmartClientLogOn) {
                $role.AllowSmartClientLogOn = $row.AllowSmartClientLogOn
                $dirty = $true
            }
            if ($null -ne $row.AllowWebClientLogOn -and $role.AllowWebClientLogOn -ne $row.AllowWebClientLogOn) {
                $role.AllowWebClientLogOn = $row.AllowWebClientLogOn
                $dirty = $true
            }
            if ($null -ne $row.Description -and $role.Description -ne $row.Description) {
                $role.Description = $row.Description
                $dirty = $true
            }
            if ($dirty) {
                if ($PSCmdlet.ShouldProcess($row.Name, "Save changes to role properties")) {
                    $role.Save()
                }
            }
            Write-Verbose "Completed checking and updating properties in $($stopWatch.Elapsed.TotalSeconds) seconds"

            <# Update the ClientTimeProfile if necessary #>
            if ($null -ne $row.ClientTimeProfile -and $config.VmoClient.ManagementServer.IsFeatureLicensed('TimeControlledAccessRights')) {
                $stopWatch.Restart()
                Write-Verbose "Updating ClientTimeProfile if necessary"
                $timeProfileInfo = $role.SetClientTimeProfile()
                if ($timeProfileInfo.ItemSelection -ne $row.ClientTimeProfile) {
                    if (-not ($timeProfileInfo.ItemSelectionValues.ContainsKey($row.ClientTimeProfile))) {
                        Write-Error "No Time Profile found matching '$($row.ClientTimeProfile)'. Valid options include $([string]::Join(', ', $timeProfileInfo.ItemSelectionValues.Keys))"
                    }
                    else {
                        if ($PSCmdlet.ShouldProcess($row.Name, "Set ClientTimeProfile to $($row.ClientTimeProfile)")) {
                            $timeProfileInfo.ItemSelection = $timeProfileInfo.ItemSelectionValues.$($row.ClientTimeProfile)
                            $invokeResult = $timeProfileInfo.ExecuteDefault()
                            if ($invokeResult.State -ne 'Success') {
                                Write-Error "Failed to update ClientTimeProfile on $($role.Name): $($invokeResult.ErrorText)"
                            }
                        }
                    }
                }
                Write-Verbose "Completed ClientTimeProfile update in $($stopWatch.Elapsed.TotalSeconds) seconds"
            }

            <# Update role members #>
            if ($null -ne $row.Members) {
                $stopWatch.Restart()
                Write-Verbose "Setting members for role"
                $role | Set-RoleMembership -Members $row.Members -DoNotRemoveMembers:$DoNotRemoveMembers -Force:$Force
                Write-Verbose "Completed Set-RoleMembers in $($stopWatch.Elapsed.TotalSeconds) seconds"
            }

            <# Update Overall Security #>
            if ($null -ne $row.OverallSecurity) {
                $overallSecurityKeys = $row.OverallSecurity | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name
                $invokeInfo = $role.ChangeOverallSecurityPermissions()
                foreach ($key in $overallSecurityKeys) {
                    if (-not $invokeInfo.SecurityNamespaceValues.ContainsValue($row.OverallSecurity.$key.SecurityNamespace)) {
                        Write-Warning "The Overall Security Namespace '$key' with NamespaceId '$($row.OverallSecurity.$key.SecurityNamespace)' does not exist."
                        continue
                    }
                    $invokeInfo.SecurityNamespace = $row.OverallSecurity.$key.SecurityNamespace
                    $namespaceInvokeInfo = $invokeInfo.ExecuteDefault()
                    $securityAttributes = $row.OverallSecurity.$key | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name
                    $dirty = $false
                    foreach ($attribute in $securityAttributes) {
                        if ($null -ne $namespaceInvokeInfo.GetProperty($attribute) -and $namespaceInvokeInfo.GetProperty($attribute) -ne $row.OverallSecurity.$key.$attribute) {
                            $namespaceInvokeInfo.SetProperty($attribute, $row.OverallSecurity.$key.$attribute)
                            $dirty = $true
                        }
                    }
                    if ($dirty) {
                        if ($PSCmdlet.ShouldProcess($row.Name, "Update Overall Security / $($key)")) {
                            $invokeResult = $namespaceInvokeInfo.ExecuteDefault()
                            if ($invokeResult.State -ne 'Success') {
                                Write-Error "Failed to change Overall Security for $($row.Name) in namespace '$($key)'"
                            }
                        }
                    }
                }
            }

            <# Update Smart Client Profile #>
            if ($null -ne $row.SmartClientProfile) {
                $stopWatch.Restart()
                Write-Verbose "Updating Smart Client Profile if necessary"
                if ($config.RoleToSmartClientProfile.$($row.Name) -ne $row.SmartClientProfile) {
                    if ($config.SmartClientProfiles.ContainsKey($row.SmartClientProfile)) {
                        if ($PSCmdlet.ShouldProcess($row.Name, "Attach Smart Client Profile '$($row.SmartClientProfile)'")) {
                            try {
                                $config.SmartClientProfiles.$($row.SmartClientProfile).AttachToRole($role.Id)
                            }
                            catch {
                                Write-Error $_
                            }
                        }
                    }
                    else {
                        Write-Error "Smart Client Profile '$($row.SmartClientProfile)' does not exist"
                    }
                }
                Write-Verbose "Completed Smart Client Profile update in $($stopWatch.Elapsed.TotalSeconds) seconds"
            }

            <# Update Management Client Profile #>
            if ($null -ne $row.ManagementClientProfile) {
                $stopWatch.Restart()
                Write-Verbose "Updating Management Client Profile if necessary"
                if ($config.RoleToManagementClientProfile.$($row.Name) -ne $row.ManagementClientProfile) {
                    if ($config.ManagementClientProfiles.ContainsKey($row.ManagementClientProfile)) {
                        if ($PSCmdlet.ShouldProcess($row.Name, "Attach Management Client Profile '$($row.ManagementClientProfile)'")) {
                            try {
                                $config.ManagementClientProfiles.$($row.ManagementClientProfile).AttachToRole($role.Id)
                            }
                            catch {
                                Write-Error $_
                            }
                        }
                    }
                    else {
                        Write-Error "Management Client Profile '$($row.ManagementClientProfile)' does not exist"
                    }
                }
                Write-Verbose "Completed Management Client Profile update in $($stopWatch.Elapsed.TotalSeconds) seconds"
            }

            <# Add or remove the public viewgroup if necessary #>
            if (!$IgnoreViewGroups -and $null -ne $row.HasMatchingViewGroup) {
                $stopWatch.Restart()
                Write-Verbose "Updating Public View Group if necessary"
                if ($row.HasMatchingViewGroup -eq $true -and -not $config.ViewGroups.ContainsKey($row.Name)) {
                    if ($PSCmdlet.ShouldProcess($row.Name, "Create View Group for role")) {
                        Write-Verbose "Adding Public View Group to match role"
                        $vg = New-PublicViewGroup -Name $row.Name
                        if ($null -ne $vg) {
                            $config.ViewGroups.Add($row.Name, $vg)
                        }
                    }
                }
                elseif ($row.HasMatchingViewGroup -eq $false -and $config.ViewGroups.ContainsKey($row.Name)) {
                    if ($PSCmdlet.ShouldProcess($row.Name, "Delete View Group for role")) {
                        $config.ViewGroups.$($row.Name) | Remove-ViewGroup
                        $config.ViewGroups.Remove($row.Name)
                    }
                }

                if ($config.ViewGroups.ContainsKey($row.Name) -and $null -ne $row.ViewGroupPermissions) {
                    $permissionSet = $row.ViewGroupPermissions | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name | Where-Object { $row.ViewGroupPermissions.$_ }
                    $config.ViewGroups.$($row.Name) | Set-ViewGroupPermission -RoleName $row.Name -PermissionSet $permissionSet
                }
                Write-Verbose "Completed Public View Group update in $($stopWatch.Elapsed.TotalSeconds) seconds"
            }

            Write-Verbose "Processed row in $($rowStopWatch.Elapsed.TotalSeconds) seconds"
        }
    }

    end {
        if ($null -ne $vmo) {
            $vmo.Dispose()
        }
    }
}