functions/Set-XdrEndpointConfigurationCustomCollectionRule.ps1

function Set-XdrEndpointConfigurationCustomCollectionRule {
    <#
    .SYNOPSIS
        Updates an existing custom collection rule for Microsoft Defender for Endpoint.

    .DESCRIPTION
        Updates custom collection rules for Microsoft Defender for Endpoint by importing YAML files or PSObjects.
        The YAML files should follow the schema format used by Get-XdrEndpointConfigurationCustomCollectionRule.
        Each rule is validated before submission to ensure proper schema structure and that the rule exists.

        More information about the schema can be found here: https://github.com/FalconForceTeam/TelemetryCollectionManager

    .PARAMETER FilePath
        Path to one or more YAML files containing custom collection rule definitions.
        Supports wildcards for batch processing.
        When using YAML files, the RuleId parameter must be provided.

    .PARAMETER RuleId
        The GUID of the rule to update. Required when using FilePath parameter.
        This ensures you're updating the correct rule when using YAML files.

    .PARAMETER InputObject
        PSObject containing the rule to update. The object must include a ruleId property.
        Typically obtained from Get-XdrEndpointConfigurationCustomCollectionRule.

    .PARAMETER BypassCache
        Bypasses any existing cache entries when updating the rule.
        Will slow down processing if multiple rules are updated in succession.

    .PARAMETER Confirm
        Prompts for confirmation before creating each rule.

    .PARAMETER WhatIf
    Shows what would happen if the cmdlet runs. The cmdlet is not run.

    .EXAMPLE
        Set-XdrEndpointConfigurationCustomCollectionRule -FilePath "C:\Rules\FileMonitoring.yaml" -RuleId "12345678-1234-1234-1234-123456789012"
        Updates a single custom collection rule from the specified YAML file.

    .EXAMPLE
        Set-XdrEndpointConfigurationCustomCollectionRule -FilePath "C:\Rules\*.yaml" -RuleId "12345678-1234-1234-1234-123456789012"
        Updates custom collection rules from all YAML files in the specified directory.

    .EXAMPLE
        Get-XdrEndpointConfigurationCustomCollectionRule |
            Where-Object { $_.ruleName -eq "My Rule" } |
            Set-XdrEndpointConfigurationCustomCollectionRule
        Updates a rule by passing a PSObject from Get cmdlet through the pipeline.

    .EXAMPLE
        $rule = Get-XdrEndpointConfigurationCustomCollectionRule | Where-Object { $_.ruleName -eq "My Rule" }
        $rule.isEnabled = $false
        Set-XdrEndpointConfigurationCustomCollectionRule -InputObject $rule
        Gets a rule, modifies it, and updates it.

    .OUTPUTS
        Object
        Returns the updated custom collection rule object(s) from the API.

    .NOTES
        Required YAML properties:
        - name: Rule name (string)
        - enabled: Rule enabled status (boolean)
        - platform: Target platform (Windows, Linux, macOS)
        - scope: Rule scope (Organization)
        - table: Target table (DeviceFileEvents, DeviceNetworkEvents, etc.)
        - actionType: Event type to collect
        - filters: Filter expressions object

        Optional YAML properties:
        - description: Rule description (string)

        When using InputObject, the following API properties are required:
        - ruleId: The rule identifier (GUID)
        - ruleName, isEnabled, platform, scope, table, actionType, filters
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [CmdletBinding(DefaultParameterSetName = 'PSObject', SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'YAML', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName', 'Path')]
        [string[]]$FilePath,

        [Parameter(Mandatory = $true, ParameterSetName = 'YAML')]
        [ValidatePattern('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')]
        [string]$RuleId,

        [Parameter(Mandatory = $true, ParameterSetName = 'PSObject', ValueFromPipeline = $true)]
        [object]$InputObject,

        [switch]$BypassCache
    )

    begin {
        Update-XdrConnectionSettings

        # Check if ConvertFrom-Yaml is available when using YAML parameter set
        if ($PSCmdlet.ParameterSetName -eq 'YAML' -and -not (Get-Command ConvertFrom-Yaml -ErrorAction SilentlyContinue)) {
            # Try to import powershell-yaml module for PowerShell 5.1
            if (-not (Get-Module -Name powershell-yaml -ListAvailable)) {
                throw "YAML parsing requires either PowerShell 7+ or the 'powershell-yaml' module. Install with: Install-Module -Name powershell-yaml"
            }
            Import-Module powershell-yaml -ErrorAction Stop
        }

        # Get current user for lastModifiedBy field
        $tenantContext = Get-XdrTenantContext -ErrorAction Stop -Force:$BypassCache
        $lastModifiedBy = $tenantContext.AuthInfo.UserName
        if (-not $lastModifiedBy) {
            throw "Unable to determine current user principal name from tenant context"
        }

        # Get all existing rules for validation
        $existingRules = Get-XdrEndpointConfigurationCustomCollectionRule -Force:$BypassCache -ErrorAction Stop
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'YAML') {
            # Process YAML files
            foreach ($file in $FilePath) {
                # Resolve wildcards and get actual file paths
                $resolvedPaths = Resolve-Path -Path $file -ErrorAction SilentlyContinue

                if (-not $resolvedPaths) {
                    Write-Error "File not found: $file"
                    continue
                }

                foreach ($resolvedPath in $resolvedPaths) {
                    try {
                        Write-Verbose "Processing file: $($resolvedPath.Path)"

                        # Read YAML content
                        $yamlContent = Get-Content -Path $resolvedPath.Path -Raw -ErrorAction Stop

                        # Parse YAML using ConvertFrom-Yaml
                        $rule = ConvertFrom-Yaml -Yaml $yamlContent -ErrorAction Stop

                        # Validate required properties
                        $requiredProps = @('name', 'enabled', 'platform', 'scope', 'table', 'actionType', 'filters')
                        foreach ($prop in $requiredProps) {
                            if ($rule.GetEnumerator().Name -notcontains $prop) {
                                throw "Missing required property: $prop"
                            }
                        }

                        # Verify the rule exists
                        $existingRule = $existingRules | Where-Object { $_.ruleId -eq $RuleId }
                        if (-not $existingRule) {
                            throw "No custom collection rule found with ruleId '$RuleId'. Use New-XdrEndpointConfigurationCustomCollectionRule to create a new rule."
                        }

                        # Build API request body
                        $body = @{
                            ruleId              = $RuleId
                            ruleName            = $rule.name
                            ruleDescription     = if ($rule.description) { $rule.description } else { "" }
                            isEnabled           = $rule.enabled
                            table               = $rule.table
                            platform            = $rule.platform
                            actionType          = $rule.actionType
                            scope               = $rule.scope
                            filters             = ConvertTo-ApiFilterFormat -Filters $rule.filters
                            tags                = $existingRule.tags
                            createdBy           = $existingRule.createdBy
                            creationDateTimeUtc = $existingRule.creationDateTimeUtc
                            lastModifiedBy      = $lastModifiedBy
                            version             = [int]$existingRule.version
                            updateKey           = $existingRule.updateKey
                        } | ConvertTo-Json -Depth 20

                        $Uri = "https://security.microsoft.com/apiproxy/mtp/mdeCustomCollection/rules/$($RuleId)"

                        # If WhatIf is specified, output the JSON body
                        if ($WhatIfPreference) {
                            Write-Host "Uri: $Uri"
                            Write-Host "JSON Body for rule '$($rule.name)' (ID: $RuleId):"
                            Write-Host $body
                            continue
                        }

                        if ($PSCmdlet.ShouldProcess("$($rule.name) (ID: $RuleId)", "Update custom collection rule")) {
                            Write-Verbose "Updating custom collection rule: $($rule.name) (ID: $RuleId)"

                            $result = Invoke-RestMethod -Uri $Uri -Method Put -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers

                            # Clear the cache for the Get cmdlet
                            Clear-XdrCache -CacheKey "XdrEndpointConfigurationCustomCollectionRule" -ErrorAction SilentlyContinue

                            Write-Verbose "Successfully updated rule with ID: $($result.ruleId)"
                            Write-Host $result
                        }
                    } catch {
                        Write-Error "Failed to update custom collection rule from file '$($resolvedPath.Path)': $($_.Exception.Message)"
                    }
                }
            }
        } else {
            # Process PSObject
            try {
                # Validate that InputObject has ruleId
                if (-not $InputObject.ruleId) {
                    throw "InputObject must contain a 'ruleId' property. Ensure you're passing an object from Get-XdrEndpointConfigurationCustomCollectionRule."
                }

                # Verify the rule exists
                $existingRule = $existingRules | Where-Object { $_.ruleId -eq $InputObject.ruleId }
                if (-not $existingRule) {
                    throw "No custom collection rule found with ruleId '$($InputObject.ruleId)'. Use New-XdrEndpointConfigurationCustomCollectionRule to create a new rule."
                }

                # Validate required properties
                $requiredProps = @('ruleName', 'isEnabled', 'platform', 'scope', 'table', 'actionType', 'filters')
                foreach ($prop in $requiredProps) {
                    if (-not $InputObject.PSObject.Properties[$prop]) {
                        throw "Missing required property: $prop"
                    }
                }

                # Build API request body
                $body = @{
                    ruleId              = $InputObject.ruleId
                    ruleName            = $InputObject.ruleName
                    ruleDescription     = if ($InputObject.ruleDescription) { $InputObject.ruleDescription } else { "" }
                    isEnabled           = $InputObject.isEnabled
                    table               = $InputObject.table
                    platform            = $InputObject.platform
                    actionType          = $InputObject.actionType
                    scope               = $InputObject.scope
                    filters             = $InputObject.filters
                    tags                = $InputObject.tags
                    createdBy           = $existingRule.createdBy
                    creationDateTimeUtc = $existingRule.creationDateTimeUtc
                    lastModifiedBy      = $lastModifiedBy
                    version             = [int]$existingRule.version
                    updateKey           = $existingRule.updateKey
                } | ConvertTo-Json -Depth 20

                $Uri = "https://security.microsoft.com/apiproxy/mtp/mdeCustomCollection/rules/$($InputObject.ruleId)"

                # If WhatIf is specified, output the JSON body
                if ($WhatIfPreference) {
                    Write-Host "Uri: $Uri"
                    Write-Host "JSON Body for rule '$($InputObject.ruleName)' (ID: $($InputObject.ruleId)):"
                    Write-Host $body
                    return
                }

                if ($PSCmdlet.ShouldProcess("$($InputObject.ruleName) (ID: $($InputObject.ruleId))", "Update custom collection rule")) {
                    Write-Verbose "Updating custom collection rule: $($InputObject.ruleName) (ID: $($InputObject.ruleId))"

                    $result = Invoke-RestMethod -Uri $Uri -Method Put -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers

                    # Clear the cache for the Get cmdlet
                    Clear-XdrCache -CacheKey "XdrEndpointConfigurationCustomCollectionRule" -ErrorAction SilentlyContinue

                    Write-Verbose "Successfully updated rule with ID: $($result.ruleId)"
                    Write-Host $result
                }
            } catch {
                Write-Error "Failed to update custom collection rule '$($InputObject.ruleName)': $($_.Exception.Message)"
            }
        }
    }

    end {

    }
}