Private/Set-AtwsData.ps1

<#

    .COPYRIGHT
    Copyright (c) Hugo Klemmestad. All rights reserved. Licensed under the MIT license.
    See https://github.com/ecitsolutions/Autotask/blob/master/LICENSE.md for license information.

#>


Function Set-AtwsData {
    <#
      .SYNOPSIS
      This function creates or updates one or more Autotask entities with new or modified properties.
      .DESCRIPTION
      This function creates or updates one or more Autotask entities with new or modified properties
      .INPUTS
      [PSObject[]]. One or more Autotask entities to update
      .OUTPUTS
      [PSObject[]]. The updated entities are re-downloaded from the API.
      .EXAMPLE
      Set-AtwsData -Entity $Entity
      Passes all Autotask entities in $Entity to the Autotask webservices API using the .update() method
      .EXAMPLE
      Set-AtwsData -Entity $Entity -Create
      Passes all Autotask entities in $Entity to the Autotask webservices API using the .create() method
      .NOTES
      NAME: Set-AtwsData
      .LINK
      Get-AtwsData
      Remove-AtwsData
  #>


    [cmdletbinding()]
    [OutputType([Collections.Generic.List[psobject]])]
    param
    (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Collections.Generic.List[psobject]]
        $Entity,

        [switch]
        $Create
    )


    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
        }

        # reset errorcounter
        $errorCount = 0
    }

    process {
        If ($Create.IsPresent) {
            Write-Verbose ('{0}: Calling .Create() for {1} objects of type {2}' -F $MyInvocation.MyCommand.Name, $Entity.Count, $Entity[0].GetType().Name)
        }
        Else {
            Write-Verbose ('{0}: Calling .Update() for {1} objects of type {2}' -F $MyInvocation.MyCommand.Name, $Entity.Count, $Entity[0].GetType().Name)
        }

        # Convert from local time and label settings
        # PSObjects are always passed by reference, no need to copy arrays in any way
        $null = ConvertFrom-LocalObject -InputObject $Entity
        #$Entity = $Entity | ConvertFrom-LocalObject

        # update() function can take up to 200 objects at a time
        for ($i = 0; $i -lt $Entity.count; $i += 200) {
            $j = $i + 199
            if ($j -ge $Entity.count) {
                $j = $Entity.count - 1
            }
            Write-Debug -Message ('{0}: Creating chunk from index {1} to index {2}' -F $MyInvocation.MyCommand.Name, $i, $j)

            # Explicit selection of list type. Generic lists supports .remove()
            $workingSet = [Collections.Generic.List[psobject]]::new()
            if($Entity[$i .. $j].Count -gt 1){
                $Entity[$i .. $j].Foreach{$workingSet.Add($_)}
            }else {
                $workingSet.Add($Entity[0])
            }

            # We are going to try multiple times if the first attempt fails
            Do {
                # Reset error list
                $errors = @()

                # Are we creating or updating?
                if ($Create.IsPresent) {
                    # We are creating. i.e. New-
                    $result = $atws.create($Script:Atws.integrationsValue, $workingSet)

                    # Check for duplicates
                    $duplicates = $result.EntityReturnInfoResults | Where-Object { $_.DuplicateStatus.Found -and -not $_.DuplicateStauts.Ignored }

                    foreach ($duplicate in $duplicates) {
                        Write-Warning ('The API responded: Duplicate found for Object Id {0} on {1}' -F $duplicate.EntityId, $duplicate.DuplicateStatus.MatchInfo)
                    }
                }
                else {
                    # We are updating
                    $result = $atws.update($Script:Atws.integrationsValue, $workingSet)
                }

                for ($t = 0; $t -lt $result.errors.Count; $t += 2) {
                    # Count the errors, we have a limit
                    $errorCount++

                    # First line is the error message
                    $message = $result.errors[$t].Message

                    if ($result.errors.Count -gt $t) {
                        # Next line may include the element index, first element = 1
                        if ($result.errors[$t + 1].Message -match '\[(\d+)\]') {

                            [int]$index = $Matches[1]
                        }
                        else {
                            $index = 1
                        }
                    }

                    # Powershell arrays has first element = 0
                    $index--

                    # Get the element
                    $element = $workingSet[$index]

                    # Remove element from Workingset
                    $errors += $element

                    # Notify caller of skipped element
                    Write-Warning ('Element with index {0} of type {1} with Id {2} was skipped because {3}' -F $Entity.IndexOf($element), $element.GetType().Name, $element.id, $message)
                }

                # .remove() any errors from the workingSet
                foreach ($element in $errors) {
                    $workingSet.Remove($element)
                }

                # Keep on trying until there are no errors, the workingSet is empty (every element failed)
                # or the error limit has been reached
            } Until ($result.errors.Count -eq 0 -or $workingSet.Count -eq 0 -or $errorCount -ge $Script:Atws.Configuration.ErrorLimit)


            # We have tried multiple times! Still errors?
            if ($result.errors.Count -eq 0) {
                # The API documentation explicitly states that you can only use the objects returned
                # by the .create() or .set() function to get the new objects ID.
                # so to return objects with accurately represents what has been created we have to
                # get them again by id

                # But not all objects support queries, for instance service adjustments,
                # so we need the entity info to determine if we can make a query
                $EntityInfo = Get-AtwsFieldInfo -Entity $result.EntityResultType -EntityInfo

                if ($result.EntityResults.Count -gt 0 -and $EntityInfo.CanQuery) {
                    $newObjectFilter = 'id -eq {0}' -F ($result.EntityResults.Id -join ' -or id -eq ')

                    $endResult += Get-AtwsData -Entity $result.EntityResultType -Filter $newObjectFilter
                }
            }
            else {
                # Still errors. Throw an exception.
                foreach ($atwsError in $result.Errors) {
                    $message = 'Number of errors exceeds configured errorlimit ({0}). Last errormessage: {1}' -f $Script:Atws.Configuration.ErrorLimit, $atwsError.Message
                    $exception = New-Object System.Configuration.Provider.ProviderException $message
                    $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified
                    $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'TooManyErrors', $errorCategory, $atwsError)
                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                }
                return
            }
        }
    }
    end {

        # Clean up
        # Reverse changes to inputobject
        $null = ConvertTo-LocalObject -InputObject $Entity


        if ($endResult.count -gt 0) {
            Write-Debug ('{0}: End of function, returning {1} updated {2}(s)' -F $MyInvocation.MyCommand.Name, $result.count, $result[0].GetType().Name)
            Return $endResult
        }
        else {
            Write-Debug ('{0}: End of function, no objects to return.' -F $MyInvocation.MyCommand.Name, $result.count)
        }
    }
}