Public/Get-IBObject.ps1

function Get-IBObject
{
    [CmdletBinding(DefaultParameterSetName='ByType')]
    param(
        [Parameter(ParameterSetName='ByType',Mandatory,Position=0)]
        [Parameter(ParameterSetName='ByTypeNoPaging',Mandatory,Position=0)]
        [Alias('type')]
        [string]$ObjectType,

        [Parameter(ParameterSetName='ByRef',Mandatory,Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [Alias('_ref','ref')]
        [string]$ObjectRef,

        [Parameter(ParameterSetName='ByType',Position=1)]
        [Parameter(ParameterSetName='ByTypeNoPaging',Position=1)]
        [Alias('Filters')]
        [object]$Filter,

        [Parameter(ParameterSetName='ByType',Position=2)]
        [int]$MaxResults=[int]::MaxValue,
        [Parameter(ParameterSetName='ByType',Position=3)]
        [ValidateRange(1,1000)]
        [int]$PageSize=1000,
        [Parameter(ParameterSetName='ByTypeNoPaging')]
        [switch]$NoPaging,

        [Alias('fields','ReturnFields')]
        [string[]]$ReturnField,
        [Alias('base','ReturnBaseFields')]
        [switch]$ReturnBase,
        [Alias('all','ReturnAllFields')]
        [switch]$ReturnAll,

        [Parameter(ParameterSetName='ByRef')]
        [switch]$BatchMode,
        [Parameter(ParameterSetName='ByRef')]
        [ValidateRange(1,2147483647)]
        [int]$BatchGroupSize = 1000,

        [switch]$ProxySearch,

        [ValidateScript({Test-ValidProfile $_ -ThrowOnFail})]
        [string]$ProfileName,
        [ValidateScript({Test-NonEmptyString $_ -ThrowOnFail})]
        [Alias('host')]
        [string]$WAPIHost,
        [ValidateScript({Test-VersionString $_ -ThrowOnFail})]
        [Alias('version')]
        [string]$WAPIVersion,
        [PSCredential]$Credential,
        [switch]$SkipCertificateCheck
    )

    Begin {
        # grab the variables we'll be using for our REST and sub calls
        try { $opts = Initialize-CallVars @PSBoundParameters } catch { $PsCmdlet.ThrowTerminatingError($_) }

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

        # Filter must be one of:
        # [string] like 'name=foo'
        # [string[]] like 'name=foo','view=bar'
        # [IDictionary] like @{ 'name~'='foo' }
        # Because filters end up in the URL querystring, they should be properly URL
        # encoded to avoid WAPI misinterpreting what is being asked for. But historically,
        # string based filters are assumed to have been properly URL encoded in advance
        # by the user because if we blindly encoded the input values, the "=" character
        # separating the field name and value would also be encoded and not work.
        # Consider this Regex based filter:
        # name~=foo\d+
        # The properly URL encoded version of this should be:
        # name%7E=foo%5Cd%2B
        # The IDictionary option was added in 4.0 so that users no longer have to pre-encode
        # their filters. We can blindly encode the key and value pairs individually before
        # joining them with a non-encoded "=".
        if ($Filter) {
            if ($Filter -is [string]) {
                # add as-is
                $queryargs.Add($Filter)
            }
            elseif ($Filter -is [array] -and $Filter[0] -is [string]) {
                # add as-is
                $queryargs.AddRange([string[]]$Filter)
            }
            elseif ($Filter -is [Collections.IDictionary]) {
                # URL encode the pairs and join with '=' before adding
                $Filter.GetEnumerator().foreach{
                    $queryargs.Add(
                        ('{0}={1}' -f [Web.HttpUtility]::UrlEncode($_.Key),[Web.HttpUtility]::UrlEncode($_.Value))
                    )
                }
            }
            else {
                $PSCmdlet.ThrowTerminatingError([Management.Automation.ErrorRecord]::new(
                    "Filter parameter is not a supported type. Must be string, string array, or hashtable.",
                    $null, [Management.Automation.ErrorCategory]::InvalidArgument, $null
                ))
            }
        }

        # Process the return field options if there are any and if ReturnAll
        # was not specified. ReturnAll requires a schema query based on the
        # object type to get the field names. So we'll postpone that work until the
        # Process {} section in case they passed multiple different object types
        # via _ref.
        if (-not $ReturnAll -and $ReturnField.Count -gt 0) {
            if ($ReturnBase) {
                $queryargs.Add("_return_fields%2B=$($ReturnField -join ',')")
            }
            else {
                $queryargs.Add("_return_fields=$($ReturnField -join ',')")
            }
        }

        # Make sure we can do schema queries if ReturnAll was specified
        if ($ReturnAll) {
            # make a basic schema query if a cache for this host doesn't already exist
            if (-not $script:Schemas[$opts.WAPIHost]) {
                try { $null = Get-IBSchema @opts }
                catch { $PSCmdlet.ThrowTerminatingError($_) }
            }
        }

        # Deal with ProxySearch flag. From the WAPI docs
        # If set to ‘GM’, the request is redirected to Grid master for processing.
        # If set to ‘LOCAL’, the request is processed locally. This option is applicable
        # only on vConnector grid members. The default is ‘LOCAL’.
        if ($ProxySearch) {
            $queryargs.Add("_proxy_search=GM")
        }

        if ($BatchMode) {
            # create a list to save the objects in
            $deferredObjects = [Collections.Generic.List[PSObject]]::new()
        }
    }

    Process {

        # Determine what object we're querying and whether we're paging
        if ($PsCmdlet.ParameterSetName -like 'ByType*') {
            # ByType
            $queryObj = $ObjectType
            $UsePaging = $true

            if ($NoPaging) {
                $UsePaging = $false
            } elseif ([Version]$opts.WAPIVersion -lt [Version]'1.5') {
                Write-Verbose "Paging not supported for WAPIVersion $($opts.WAPIVersion)"
                $UsePaging = $false
            }
        } else {
            # ByRef
            $queryObj = $ObjectRef
            # paging not supported on objref queries
            $UsePaging = $false

            if ($BatchMode) {
                $deferredObjects.Add(@{ object = $ObjectRef })
            }
        }

        # deal with -ReturnAll now
        if ($ReturnAll) {
            # Returning all fields requires doing a schema query against the object
            # type so we can compile the list of fields to request.
            $oType = $queryObj
            if ($ObjectRef) { $oType = $oType.Substring(0,$oType.IndexOf("/")) }

            $readFields = Get-ReadFieldsForType -ObjectType $oType @opts

            if ($BatchMode) {
                $deferredObjects[-1].args = @{
                    '_return_fields' = $readFields -join ','
                }
            } else {
                $queryargs.Add("_return_fields=$($readFields -join ',')")
            }
        }

        if ($BatchMode) {
            # everything else is deferred to End{}, so just return
            return
        }

        # if we're not paging, just return the single call
        if (-not $UsePaging) {
            $query = ('{0}?{1}' -f $queryObj,($queryargs -join '&')).TrimEnd('?')
            try {
                Invoke-IBWAPI -Query $query @opts -EA Stop
            } catch { $PSCmdlet.WriteError($_) }

            return
        }

        # By default, the WAPI will return an error if the result count exceeds 1000
        # unless you make multiple calls using paging. We want to remove this
        # limitation by automatically paging on behalf of the caller. This will also
        # allow the MaxResults parameter in this function to be arbitrarily large
        # (within the bounds of Int32) and not capped at 1000 like a normal WAPI call.

        # separate the MaxResults value from the caller's request to error on "over max"
        $ErrorOverMax = $false
        if ($MaxResults -lt 0) {
            $MaxResults = [Math]::Abs($MaxResults)
            $ErrorOverMax = $true
        }

        # make sure the $PageSize is never more than 1 over $MaxResults so we don't
        # retrieve more data than necessary but "over max" errors will still trigger
        if ($MaxResults -lt $PageSize -and $MaxResults -lt 1000) {
            $PageSize = $MaxResults + 1
        }

        $querystring = "?_paging=1&_return_as_object=1&_max_results=$PageSize"
        if ($queryargs.Count -gt 0) {
            $querystring += "&$($queryargs -join '&')"
        }
        $pageNum = 0
        $resultCount = 0
        $results = do {
            $pageNum++
            Write-Verbose "Fetching page $pageNum"
            if ($pageNum -gt 1) {
                $querystring = "?_page_id=$($response.next_page_id)"
            }

            $query = '{0}{1}' -f $queryObj,$querystring

            try {
                $response = Invoke-IBWAPI -Query $query @opts -EA Stop
                if ('result' -notin $response.PSObject.Properties.Name) {
                    # A normal response from WAPI will contain a 'result' object even
                    # if that object is empty because it couldn't find anything.
                    # But if there's no result object, something is wrong.
                    $PSCmdlet.ThrowTerminatingError([Management.Automation.ErrorRecord]::new(
                        "No 'result' object found in server response",
                        $null, [Management.Automation.ErrorCategory]::ObjectNotFound, $null
                    ))
                }
                $resultCount += $response.result.Count
                $response.result
            } catch {
                $PSCmdlet.WriteError($_)
            }

        } while ($response.next_page_id -and $resultCount -lt $MaxResults)

        # Error if they specified a negative MaxResults value and the result
        # count exceeds that value. Otherwise, just truncate the results to the MaxResults
        # value. This is basically copying how the _max_results query string argument works.
        if ($ErrorOverMax -and $resultCount -gt $MaxResults) {
            $PSCmdlet.WriteError([Management.Automation.ErrorRecord]::new(
                "Result count exceeded MaxResults parameter.",
                $null, [Management.Automation.ErrorCategory]::LimitsExceeded, $null
            ))
        }
        else {
            $results | Select-Object -First $MaxResults
        }

    }

    End {
        if (-not $BatchMode -or $deferredObjects.Count -eq 0) { return }
        Write-Verbose "BatchMode deferred objects: $($deferredObjects.Count), group size $($BatchGroupSize)"

        # build the 'args' value for each object
        $retArgs = @{}
        if ($ReturnField.Count -gt 0) {
            if ($ReturnBase) {
                $retArgs.'_return_fields+' = $ReturnField -join ','
            } else {
                $retArgs.'_return_fields'  = $ReturnField -join ','
            }
        } else {
            $retArgs.'_return_fields+' = ''
        }

        # make calls based on the group size
        for ($i=0; $i -lt $deferredObjects.Count; $i += $BatchGroupSize) {
            $groupEnd = [Math]::Min($deferredObjects.Count, ($i+$BatchGroupSize-1))

            # build the json for this group's objects
            $body = $deferredObjects[$i..$groupEnd] | ForEach-Object {
                @{
                    method = 'GET'
                    object = $_.object
                    args = if ($_.args) { $_.args } else { $retArgs }
                }
            }

            try {
                Invoke-IBWAPI -Query 'request' -Method 'POST' -Body $body @opts -EA Stop
            } catch { $PSCmdlet.WriteError($_) }
        }

    }
}