Private/Get-AtwsData.ps1

<#
    .COPYRIGHT
    Copyright (c) Office Center H�nefoss AS. All rights reserved. Licensed under the MIT license.
    See https://github.com/ecitsolutions/Autotask/blob/master/LICENSE.md for license information.

#>


Function Get-AtwsData {
    <#
      .SYNOPSIS
      This function queries the Autotask Web API for entities matching a specified type and filter.
      .DESCRIPTION
      This function queries the Autotask Web API for entities matching a specified type and filter.
      Valid operators:
      -and, -or

      Valid comparison operators:
      -eq, -ne, -lt, -le, -gt, -ge, -isnull, -isnotnull, -isthisday

      Valid text comparison operators:
      -contains, -like, -notlike, -beginswith, -endswith, -soundslike

      Special operators to nest conditions:
      -begin, -end

      .INPUTS
      Nothing.
      .OUTPUTS
      Autotask.Entity[]. One or more Autotask entities returned from Autotask Web API.
      .EXAMPLE
      Get-AtwsData -Entity Ticket -Filter {id -gt 0}
      Gets all tickets with an id greater than 0 from Autotask Web API
      .NOTES
      NAME: Get-AtwsData
      .LINK
      Set-AtwsData
      New-AtwsData
      Remove-AtwsData
  #>


    [cmdletbinding()]
    [OutputType([collections.generic.list[psobject]])]
    param
    (
        [Parameter(
            Mandatory = $true,
            Position = 0
        )]
        [string]
        $Entity,

        [Parameter(
            Mandatory = $true,
            ValueFromRemainingArguments = $true,
            Position = 1
        )]
        [string[]]
        $Filter,

        [string]
        $GetReferenceEntityById,

        [switch]
        $NoPickListLabel
    )

    begin {
        # Enable modern -Debug behavior
        if ($PSCmdlet.MyInvocation.BoundParameters['Debug'].IsPresent) { $DebugPreference = 'Continue' }

        Write-Debug ('{0}: Begin of function' -F $MyInvocation.MyCommand.Name)

        if (-not($Script:Atws.integrationsValue)) {
            # Not connected. Try to connect, prompt for credentials if necessary
            Write-Verbose ('{0}: Not connected. Calling Connect-AtwsWebApi without parameters for possible autoload of default connection profile.' -F $MyInvocation.MyCommand.Name)
            Connect-AtwsWebAPI
        }

        $result = [collections.generic.list[psobject]]::new()
    }

    process {
        # $Filter may in some cases be passed as a flat string. Make sure it is formatted properly
        if ($Filter.Count -eq 1 -and $Filter -match ' ' ) {
            $Filter = . Update-AtwsFilter -Filterstring $Filter
        }

        # Create array with entity as first element
        [Array]$Query = @($Entity) + $Filter

        Write-Verbose ('{0}: Converting query string into QueryXml. string as array looks like: {1}' -F $MyInvocation.MyCommand.Name, $($Query -join ', '))
        [xml]$QueryXml = ConvertTo-QueryXML @Query

        Write-Debug ('{0}: QueryXml looks like: {1}' -F $MyInvocation.MyCommand.Name, $QueryXml.InnerXml.Tostring())

        Write-Verbose ('{0}: Adding looping construct to query to handle more than 500 results.' -F $MyInvocation.MyCommand.Name)

        # Native XML is rather tedious...
        $field = $QueryXml.CreateElement('field')
        $expression = $QueryXml.CreateElement('expression')
        $expression.SetAttribute('op', 'greaterthan')
        $expression.InnerText = 0
        $field.InnerText = 'id'
        [void]$field.AppendChild($expression)

        $FirstPass = $true
        Do {
            Write-Verbose ('{0}: Passing QueryXML to Autotask API' -F $MyInvocation.MyCommand.Name)

            # Get the first batch - the API returns max 500 items
            $lastquery = $Script:Atws.query($Script:Atws.IntegrationsValue, $QueryXml.InnerXml)

            # Handle any errors
            if ($lastquery.Errors.Count -gt 0) {
                foreach ($atwsError in $lastquery.Errors) {
                    $exception = New-Object System.Configuration.Provider.ProviderException $atwsError.Message
                    $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified
                    $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'AutotaskError', $errorCategory, $atwsError)
                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                }
                return
            }

            # Add all returned objects to the Result - if any
            if ($lastquery.EntityResults.Count -gt 0) {
                # Powershell 5.1 adaption
                # On 5.1 the cast ends up as a nested list with 1 item - an array with a single member...
                if ($lastquery.EntityResults.Count -gt 1) {
                    # Use addrange for an array of objects
                    $result.AddRange([collections.generic.list[psobject]]$lastquery.EntityResults)
                }
                else {
                  # Add a single item
                  $result.Add($lastquery.EntityResults[0])
                }
            }

            # Results are sorted by object Id. The Id of the last object is the highest object id in the result
            $upperBound = $lastquery.EntityResults[$lastquery.EntityResults.GetUpperBound(0)].id

            # Add the higest Id (so far) to the id -gt ? condition
            $expression.InnerText = $upperBound

            # If this is the first pass we append the expression to the query
            if ($FirstPass) {
                # Insert looping construct into query
                [void]$QueryXml.queryxml.query.AppendChild($field)
                $FirstPass = $false
            }
        }
        # The last query we have to make will have between 0 and 499 items
        Until ($lastquery.EntityResults.Count -lt 500)

    }

    end {
        # Some last minute changes
        if ($result) {
            # Should we return an indirect object?
            if ($GetReferenceEntityById) {
                Write-Verbose ('{0}: User has asked for external reference objects by {1}' -F $MyInvocation.MyCommand.Name, $GetReferenceEntityById)
                $field = Get-AtwsFieldInfo -Entity $Entity -FieldName $GetReferenceEntityById
                $resultValues = $result.$GetReferenceEntityById | Where-Object { $null -ne $_ }
                if ($resultValues.Count -lt $result.Count) {
                    Write-Warning ('{0}: Only {1} of the {2}s in the primary query had a value in the property {3}.' -F $MyInvocation.MyCommand.Name,
                        $resultValues.Count,
                        $Entity,
                        $GetReferenceEntityById) -WarningAction Continue
                }
                $Filter = 'id -eq {0}' -F $($resultValues -join ' -or id -eq ')
                $result = Get-Atwsdata -Entity $field.ReferenceEntityType -Filter $Filter
            }
            else {
              # Expand UDFs and/or picklists and/or convert dates according to user settings
              $null = ConvertTo-LocalObject -InputObject $result
            }

            Write-Debug ('{0}: End of function' -F $MyInvocation.MyCommand.Name)
            Return $result
        }
    }

}