Private/New-SCAQueryString.ps1

function New-SCAQueryString {
    <#
    .SYNOPSIS
        Builds a URL-encoded query string from a hashtable of parameters.
    .DESCRIPTION
        Skips keys whose value is $null, empty, or an empty array so that optional filters are
        omitted from the URI rather than sent as blank query parameters. Array values are emitted
        as repeated key=value pairs, which is how the CyberArk SaaS APIs expect multi-value
        filters.
    .PARAMETER Parameters
        Hashtable of query parameter names and values.
    .EXAMPLE
        New-SCAQueryString -Parameters @{ status = 'Pending'; limit = 50; unused = $null }

        Returns 'status=Pending&limit=50'.
    .OUTPUTS
        System.String
    #>

    [Diagnostics.CodeAnalysis.SuppressMessage(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Pure string-building helper despite the New- verb; there is no state change to confirm.')]
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0)]
        [hashtable]$Parameters
    )

    if (-not $Parameters -or $Parameters.Count -eq 0) {
        return ''
    }

    $pairs = [System.Collections.Generic.List[string]]::new()

    foreach ($key in $Parameters.Keys) {
        $value = $Parameters[$key]

        if ($null -eq $value) { continue }
        if ($value -is [string] -and $value -eq '') { continue }
        if ($value -is [array] -and $value.Count -eq 0) { continue }

        $values = if ($value -is [array]) { $value } else { , $value }

        foreach ($item in $values) {
            $encodedKey = [System.Uri]::EscapeDataString($key)
            $encodedValue = [System.Uri]::EscapeDataString([string]$item)
            $pairs.Add("$encodedKey=$encodedValue")
        }
    }

    return ($pairs -join '&')
}