Private/Build-QueryFromParams.ps1

function Build-QueryFromParams {
    <#
        .EXAMPLES
        Use inside of a function with parameters to build a query string from the bound parameters.
        function Test {
            [CmdletBinding()]
            param(
                [switch]$QueryAll,
                [string]$HashType = 'SHA256',
                [int]$Limit,
                [string[]]$Tags
            )
            $query = Build-QueryFromParams -PSCmdlet $PSCmdlet
            $query
        }
        Test -QueryAll -Tags red,blue

        Or

        Build-QueryFromParams -PSCmdlet $PSCmdlet -Include 'HashType','Limit'
        Build-QueryFromParams -PSCmdlet $PSCmdlet -Exclude 'Tags'
    #>

    [CmdletBinding(DefaultParameterSetName = 'Hashtable')]
    param(
        [Parameter(ValueFromPipeline = $true, ParameterSetName = 'PSCmdlet')]
        [Alias('PSCmdlet')]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet, 
        [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Hashtable')]
        [Hashtable]
        $Query, 
        [string[]]
        $Include, 
        [string[]]
        $Exclude,
        [switch]
        $IncludeOnlyWhenTrue
    )

    
    if (-not $PSBoundParameters.ContainsKey('Verbose')) {
        $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
    }

    switch ($PSCmdlet.ParameterSetName) {
        'Hashtable' {
            if (-not $Query) { return $null }
            $Params = $Query
        }
        'PSCmdlet' {
            if (-not $Cmdlet) { return $null }
            $h = @{}
            foreach ($name in $PSCmdlet.MyInvocation.MyCommand.Parameters.Keys) {
                # pull the local param variable (already holds default if not bound)
                $val = Get-Variable -Name $name -ValueOnly -ErrorAction SilentlyContinue
                $h[$name] = $val
            }
            # Optionally strip common params so they don't end up in your query
            $common = 'Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction',
            'ErrorVariable', 'WarningVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable','Cmdlet'
            $common | ForEach-Object { $h.Remove($_) | Out-Null }

            $Params = $h
        }
    }
    

    

    $keys = $Params.Keys
    if ($Include) { $keys = $keys | Where-Object { $_ -in $Include } }
    if ($Exclude) { $keys = $keys | Where-Object { $_ -notin $Exclude } }

    $pairs = foreach ($k in $keys) {
        $v = $Params[$k]
        if ($null -eq $v) { continue }

        # switches/bools: include only when true
        if($IncludeOnlyWhenTrue.IsPresent){
            if ($v -is [bool]) { if (-not $v) { continue }; $v = $true }
        }

        # arrays: repeat the key
        if ($v -is [System.Collections.IEnumerable] -and -not ($v -is [string])) {
            foreach ($item in $v) {
                '{0}={1}' -f [uri]::EscapeDataString($k), [uri]::EscapeDataString([string]$item)
            }
            continue
        }

        '{0}={1}' -f [uri]::EscapeDataString($k), [uri]::EscapeDataString([string]$v)
    }

    if (-not $pairs) { return $null }
    '?' + ($pairs -join '&')
}