Public/Policies/Set-JCPolicy.ps1

function Set-JCPolicy {
    [CmdletBinding(DefaultParameterSetName = 'ByID')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'ByID',
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'The ID of the existing JumpCloud Policy to modify')]
        [Alias("id")]
        [System.String]
        $PolicyID,
        [Parameter(Mandatory = $true,
            ParameterSetName = 'ByName',
            ValueFromPipelineByPropertyName = $false,
            HelpMessage = 'The name of the existing JumpCloud Policy template to modify')]
        [Alias("name")]
        [System.String]
        $PolicyName,
        [Parameter(Mandatory = $false,
            HelpMessage = 'The new name to set on the existing JumpCloud Policy. If left unspecified, the cmdlet will not rename the existing policy.')]
        [System.String]
        $NewName,
        [Parameter(ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'The values object either built manually or passed in through Get-JCPolicy')]
        [System.object[]]
        $Values,
        [Parameter(Mandatory = $false,
            HelpMessage = 'The notes to set on the existing JumpCloud Policy.')]
        [System.String]
        $Notes
    )
    DynamicParam {

        if ($PSBoundParameters["PolicyID"]) {
            $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
            $ParameterAttribute.Mandatory = $false
            $ParameterAttribute.ParameterSetName = "ByID"
            # Get the policy by ID
            $foundPolicy = Get-JCPolicy -PolicyID $PolicyID
            if ([string]::IsNullOrEmpty($foundPolicy.ID)) {
                throw "Could not find policy by ID"
            }
            if (($foundPolicy.ID).count -gt 1) {
                throw "multiple policies with the same name were found, please specify a policy by ID"
            }

        } elseif ($PSBoundParameters["PolicyName"]) {
            $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
            $ParameterAttribute.Mandatory = $false
            $ParameterAttribute.ParameterSetName = "ByName"
            # Get the policy by Name
            $foundPolicy = Get-JCPolicy -Name $PolicyName
            if ([string]::IsNullOrEmpty($foundPolicy.ID)) {
                throw "Could not find policy by specified Name"
            }
            if (($foundPolicy.ID).count -gt 1) {
                throw "multiple policies with the same name were found, please specify a policy by ID"
            }
        }
        # If policy is identified, get the dynamic policy set
        if ($foundPolicy.id -And ($PSBoundParameters["PolicyName"] -OR $PSBoundParameters["PolicyID"])) {
            # Set the policy template object based on policy
            $templateObject = Get-JCPolicyTemplateConfigField -templateID $foundPolicy.Template.Id
            $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            # Foreach key in the supplied config file:
            foreach ($key in $templateObject.objectMap) {
                # Set the dynamic parameters' name
                $ParamName_Filter = "$($key.configFieldName)"
                # Create the collection of attributes
                $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
                # If ValidateSet is specified in the config file, set the value here:
                # If the type of value is a bool, create a custom validateSet attribute here:
                $paramType = $($key.type)
                switch ($paramType) {
                    'boolean' {
                        $arrSet = @("true", "false")
                        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
                        $AttributeCollection.Add($ValidateSetAttribute)
                    }
                    'multi' {
                        $paramType = 'string'
                        $arrSet = $key.validation.values
                        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
                        $AttributeCollection.Add($ValidateSetAttribute)
                    }
                    'file' {
                        $paramType = 'string'
                    }
                    'listbox' {
                        $paramType = [system.string[]]
                    }
                    'table' {
                        $paramType = [system.object[]]
                    }
                    'exclude' {
                        Continue
                    }
                    Default {
                        $paramType = 'string'
                    }
                }
                # Set the help message
                if ([String]::IsNullOrEmpty($($key.help))) {
                    $ParameterAttribute.HelpMessage = "sets the value for the $($key.name) field"
                } else {
                    $ParameterAttribute.HelpMessage = "$($key.help)"
                }
                # Add the attributes to the attributes collection
                $AttributeCollection.Add($ParameterAttribute)
                # Add the param
                $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_Filter, $paramType, $AttributeCollection)
                $RuntimeParameterDictionary.Add($ParamName_Filter, $RuntimeParameter)
                if ($ParamName_Filter -eq "customRegTable") {
                    $RegImport_RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                    $RegImport_AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
                    $RegImport_ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
                    $RegImport_paramType = [System.IO.FileInfo]
                    $RegImport_ParameterAttribute.HelpMessage = 'A .reg file path that will be uploaded into the "Advanced: Custom Registry Keys" Windows Policy template.'
                    $RegImport_AttributeCollection.Add($RegImport_ParameterAttribute)
                    $RegImport_RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter('RegistryFile', $RegImport_paramType, $RegImport_AttributeCollection)
                    $RuntimeParameterDictionary.Add('RegistryFile', $RegImport_RuntimeParameter)
                }
                if ($ParamName_Filter -eq "customRegTable") {
                    $RegImport_RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                    $RegImport_AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
                    $RegImport_ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
                    $RegImport_paramType = [Switch]
                    $RegImport_ParameterAttribute.HelpMessage = 'When specified along with the RegistryFile parameter, existing registry values will be overwritten'
                    $RegImport_AttributeCollection.Add($RegImport_ParameterAttribute)
                    $RegImport_RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter('RegistryOverwrite', $RegImport_paramType, $RegImport_AttributeCollection)
                    $RuntimeParameterDictionary.Add('RegistryOverwrite', $RegImport_RuntimeParameter)
                }
            }
            # Returns the dictionary
            return $RuntimeParameterDictionary
        }
    }
    begin {
        Write-Debug 'Verifying JCAPI Key'
        if ([System.String]::IsNullOrEmpty($JCAPIKEY)) {
            Connect-JCOnline
        }
    }
    process {
        # Get Existing Policy Data if not set through dynamic param
        if (([string]::IsNullOrEmpty($foundPolicy.ID)) -And ($policyID)) {
            # Get the policy by ID
            $foundPolicy = Get-JCPolicy -PolicyID $PolicyID
            if ([string]::IsNullOrEmpty($foundPolicy.ID)) {
                throw "Could not find policy by ID"
            }
            if (($foundPolicy.ID).count -gt 1) {
                throw "multiple policies with the same name were found, please specify a policy by ID"
            }
        }
        $templateObject = Get-JCPolicyTemplateConfigField -templateID $foundPolicy.Template.Id
        # First set the name from PSParamSet if set; else set from policy
        $policyNameFromProcess = if ($PSBoundParameters["NewName"]) {
            $NewName
        } else {
            $foundPolicy.name
        }
        $params = $PSBoundParameters
        $paramterSet = $params.keys
        $DynamicParamSet = $false
        $requiredSet = @('PolicyID', 'PolicyName', 'NewName' , 'Values')
        foreach ($parameter in $paramterSet) {
            $parameterComparison = Compare-Object -ReferenceObject $requiredSet -DifferenceObject $parameter
            if ($parameterComparison | Where-Object { ( $_.sideindicator -eq "=>") -And ($_.InputObject -ne "Notes") }) {
                $DynamicParamSet = $true
                break
            }
        }

        # only update newName or Notes:
        if ((("NewName" -in $params.keys) -AND ("Values" -notin $params.Keys)) -OR
            (("Notes" -in $params.keys) -AND ("Values" -notin $params.Keys))) {
            $Values = $foundPolicy.values
        }
        # get the notes if it's not in the param set
        if (-not $PSBoundParameters["Notes"]) {
            $Notes = $foundPolicy.Notes
        }

        if ($DynamicParamSet) {
            # begin dynamic param set
            $newObject = New-Object System.Collections.ArrayList
            for ($i = 0; $i -lt $templateObject.objectMap.count; $i++) {
                # If one of the dynamicParam config fields are passed in and is found in the policy template, set the new value:
                if ($templateObject.objectMap[$i].configFieldName -in $params.keys) {
                    $keyName = $params.keys | Where-Object { $_ -eq $templateObject.objectMap[$i].configFieldName }
                    # write-host "Setting value from $($keyName)"
                    $keyValue = $params.$KeyName
                    switch ($templateObject.objectMap[$i].type) {
                        'multi' {
                            $templateObject.objectMap[$i].value = $(($templateObject.objectMap[$i].validation | Where-Object { $_.Values -eq $keyValue }).keys)
                        }
                        'file' {
                            $path = Test-Path -Path $keyValue
                            if ($path) {
                                # convert file path to base64 string
                                $templateObject.objectMap[$i].value = [convert]::ToBase64String((Get-Content -Path $keyValue -AsByteStream))
                            }
                            #TODO: else we should throw an error here that the file path was not valid
                        }
                        'listbox' {
                            if ($($keyValue).getType().name -eq 'String') {
                                # Given case for single string passed in as dynamic input, convert to a list
                                $listRows = New-Object System.Collections.ArrayList
                                foreach ($regItem in $keyValue) {
                                    $listRows.Add($regItem)
                                }
                                $templateObject.objectMap[$i].value = $listRows
                            } elseif ($($keyValue).getType().name -eq 'Object[]') {
                                # else if the object passed in is a list already, pass in list
                                $templateObject.objectMap[$i].value = $($keyValue)
                            }
                        }
                        'table' {
                            # For custom registry table, validate the object
                            if ($templateObject.objectMap[$i].configFieldName -eq "customRegTable") {
                                # get default value properties
                                $RegProperties = $templateObject.objectMap[$i].defaultValue | Get-Member -MemberType NoteProperty
                                # get passed in object properties
                                $ObjectProperties = if ($keyValue | Get-Member -MemberType NoteProperty) {
                                    # for lists get note properties
                                    ($keyValue | Get-Member -MemberType NoteProperty).Name
                                } else {
                                    # for single objects, get keys
                                    $keyValue.keys
                                }
                                $RegProperties | ForEach-Object {
                                    if ($_.Name -notin $ObjectProperties) {
                                        Throw "Custom Registry Tables require a `"$($_.Name)`" data string. The following data types were found: $($ObjectProperties)"
                                    }
                                }
                                # reg type validation
                                $validRegTypes = @('DWORD', 'expandString', 'multiString', 'QWORD', 'String')
                                $($keyValue).customRegType | ForEach-Object {
                                    if ($_ -notin $validRegTypes) {
                                        throw "Custom Registry Tables require the `"customRegType`" data string to be one of: $validRegTypes, found: $_"
                                    }
                                }
                            }
                            $regRows = New-Object System.Collections.ArrayList
                            foreach ($regItem in $keyValue) {
                                $regRows.Add($regItem) | Out-Null
                            }
                            $templateObject.objectMap[$i].value = $regRows
                        }
                        Default {
                            $templateObject.objectMap[$i].value = $($keyValue)
                        }
                    }
                    $newObject.Add($templateObject.objectMap[$i]) | Out-Null
                } elseif ('RegistryFile' -in $params.Keys) {
                    try {
                        $regKeys = Convert-RegToPSObject -regFilePath $($params.'RegistryFile')
                    } catch {
                        throw $_
                    }
                    # Set the registryFile as the customRegTable
                    if ('RegistryOverwrite' -in $params.Keys) {
                        $templateObject.objectMap[0].value = $regKeys
                        $newObject.Add($templateObject.objectMap[0]) | Out-Null
                    } else {
                        $registryList = New-Object System.Collections.ArrayList
                        $registryList += $foundPolicy.values.value
                        $registryList += $regKeys
                        $templateObject.objectMap[0].value += $registryList
                        $newObject.Add($templateObject.objectMap[0]) | Out-Null
                    }
                } else {
                    # Else if the dynamicParam for a config field is not specified, set the value from the defaultValue
                    $templateObject.objectMap[$i].value = ($foundPolicy.values | Where-Object { $_.configFieldID -eq $templateObject.objectMap[$i].configFieldID }).value
                    $newObject.Add($templateObject.objectMap[$i]) | Out-Null
                }
            }
            $updatedPolicyObject = $newObject | Select-Object configFieldID, configFieldName, value
            if ($registryFile) {
                try {
                    $regKeys = Convert-RegToPSObject -regFilePath $registryFile
                } catch {
                    throw $_
                }
                $updatedPolicyObject.value += $regKeys
                Write-Debug $updatedPolicyObject
            }
        } elseif ($values) {
            # begin value param set
            $updatedPolicyObject = New-Object System.Collections.ArrayList
            # Add each value into the object to set the policy
            foreach ($value in $values) {
                $updatedPolicyObject.add($value) | Out-Null
            }
            # If only one or a few of the config fields were set, add the remaining items from $foundPolicy.values
            $foundPolicy.values | ForEach-Object {
                if ($_.configFieldID -notin $updatedPolicyObject.configFieldID) {
                    $updatedPolicyObject.Add($_) | Out-Null
                }
            }
        } else {
            if (($templateObject.objectMap).count -gt 0) {
                # Begin user prompt
                $initialUserInput = Show-JCPolicyValues -policyObject $templateObject.objectMap -policyValues $foundPolicy.values
                # User selects edit all fields
                if ($initialUserInput.fieldSelection -eq 'A') {
                    for ($i = 0; $i -le $initialUserInput.fieldCount; $i++) {
                        $updatedPolicyObject = Set-JCPolicyConfigField -templateObject $templateObject.objectMap -fieldIndex $i -policyValues $foundPolicy.values
                    }
                    # Display policy values
                    # Show-JCPolicyValues -policyObject $updatedPolicyObject -ShowTable $true
                }
                # User selects edit individual field
                elseif ($initialUserInput.fieldSelection -ne 'C' -or $initialUserInput.fieldSelection -ne 'A') {
                    $updatedPolicyObject = Set-JCPolicyConfigField -templateObject $templateObject.objectMap -fieldIndex $initialUserInput.fieldSelection -policyValues $foundPolicy.values
                    # For set-jcpolicy, add the help & label options
                    $updatedPolicyObject | ForEach-Object {
                        if ($_.configFieldID -in $templateObject.objectMap.configFieldID) {
                            $_ | Add-Member -MemberType NoteProperty -Name "help" -Value $templateObject.objectMap[$templateObject.objectMap.configFieldID.IndexOf($($_.configFieldID))].help
                            $_ | Add-Member -MemberType NoteProperty -Name "label" -Value $templateObject.objectMap[$templateObject.objectMap.configFieldID.IndexOf($($_.configFieldID))].label
                        }
                    }
                    Do {
                        # Hide option to edit all fields
                        $userInput = Show-JCPolicyValues -policyObject $updatedPolicyObject -HideAll $true -policyValues $foundPolicy.values
                        $updatedPolicyObject = Set-JCPolicyConfigField -templateObject $templateObject.objectMap -fieldIndex $userInput.fieldSelection -policyValues $foundPolicy.values
                    } while ($userInput.fieldSelection -ne 'C')
                }
            }
        }
        if ($updatedPolicyObject) {
            $body = [PSCustomObject]@{
                name     = $policyNameFromProcess
                template = @{id = $foundPolicy.Template.Id }
                values   = @($updatedPolicyObject)
                notes    = $Notes
            } | ConvertTo-Json -Depth 99
        } else {
            $body = [PSCustomObject]@{
                name     = $policyNameFromProcess
                template = @{id = $foundPolicy.Template.Id }
                notes    = $Notes
            } | ConvertTo-Json -Depth 99
        }
        $headers = @{
            'x-api-key'    = $env:JCApiKey
            'x-org-id'     = $env:JCOrgId
            'content-type' = "application/json"
        }
        $response = Invoke-RestMethod -Uri "https://console.jumpcloud.com/api/v2/policies/$($foundPolicy.id)" -Method PUT -Headers $headers -ContentType 'application/json' -Body $body
        if ($response) {
            $response | Add-Member -MemberType NoteProperty -Name "templateID" -Value $response.template.id
        }
    }
    end {
        return $response | Select-Object -Property "name", "id", "templateID", "values", "template", "notes"
    }
}