IvantiPS.psm1

### --- PUBLIC FUNCTIONS --- ###
#Region - Connect-IvantiTenant.ps1
function Connect-IvantiTenant {
<#
    .SYNOPSIS
        Connect to an Ivanti Service Manager tenant
 
    .DESCRIPTION
        Connect to an Ivanti Service Manager tenant
 
    .PARAMETER Credential
        Credential object to use to authenticate with the ISM tenant
 
    .PARAMETER SessionID
        Existing session value to use instead of credentials
 
    .EXAMPLE
        Connect-IvantiTenant -Credential (Get-Credential)
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Session-ID-Log-In.htm
 
    #>


    [CmdletBinding()]
    #[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')]
    param(
        [Parameter(ParameterSetName="Credential")]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,
        [Parameter(ParameterSetName="Session")]
        [string]$SessionID,
        [Parameter(ParameterSetName="APIKey")]
        [string]$APIKey
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function started"

        $config = Get-IvantiPSConfig -ErrorAction Stop
        $tenant = $config.IvantiTenantID
        $LoginURL = "https://$($tenant)/api/rest/authentication/login"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        if ($SessionID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Existing SessionID passed in, using it"
            # If existing session id was passed in, use it
            #
            $result = $SessionID
        } elseif ($Credential) {
            # Create payload for call to login endpoint
            #
            $Payload = @{
                tenant = $tenant
                username = $Credential.username
                password = $Credential.GetNetworkCredential().password
                role = $config.DefaultRole
            }
            try {
                Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Invoking RestMethod on $LoginURL"
                $result = Invoke-RestMethod -Uri $LoginURL -Body $Payload -Method POST
            } catch {
                Write-Warning "[$($MyInvocation.MyCommand.Name)] Problem calling $LoginURL"
                $_
            }
        } elseif ($APIKey) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Existing APIKey passed in, using it"
            # If existing session id was passed in, use it
            #
            $result = $APIKey
        } else {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No Credentials, SessionID, or APIKey passed in. Exiting..."
            return
        }

        # The resulting session value from a valid call to the login url will be
        # saved in the module private data
        #
        if ($MyInvocation.MyCommand.Module.PrivateData) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Adding session result to existing module PrivateData"
            $MyInvocation.MyCommand.Module.PrivateData.Session = $result
        }
        else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Creating module PrivateData"
            $MyInvocation.MyCommand.Module.PrivateData = @{
                'Session' = $result
            }
        }
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] SessionID: $result"
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
Export-ModuleMember -Function Connect-IvantiTenant
#EndRegion - Connect-IvantiTenant.ps1
#Region - Get-IvantiAgency.ps1
function Get-IvantiAgency {
    <#
    .SYNOPSIS
        Get Agency business objects from Ivanti. Defaults to all.
 
    .DESCRIPTION
        Get Agency business objects from Ivanti. Defaults to all.
 
    .PARAMETER RecID
        Ivanti Record ID for a specific Agency business object
 
    .PARAMETER Agency
        Full agency name to filter on
 
    .PARAMETER AgencyShortName
        Agency short name to filter on. Usually an abbreviation.
 
    .EXAMPLE
        Get-IvantiAgency ACM
 
        Returns all agencies with shortname value of ACM
 
    .EXAMPLE
        Get-IvantiAgency
 
        Returns all agencies
 
    .EXAMPLE
        Get-IvantiAgency -RecID DC218F83EC504222B148EF1344E15BCB
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Filter.htm
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Search.htm
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]$ShortName,
        [string]$RecID,
        [string]$Name
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # If one or more parameters are passed in, use only one of them
        # Order of preference is RecID, Agency, then AgencyShortName
        # if no parameters are passed in, then do not set any get parameters
        #
        if ($RecID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] RecID [$RecID] passed in, setting filter"
            $GetParameter = @{'$filter' = "RecID eq '$($RecID)'"}
        } elseif ($Name) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Name [$Name] passed in, setting filter"
            $GetParameter = @{'$filter' = "Agency eq '$($Name)'"}
        } elseif ($ShortName) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ShortName [$ShortName] passed in, setting filter"
            $GetParameter = @{'$filter' = "AgencyShortName eq '$($ShortName)'"}
        } else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No parameters passed in"
        }

        # Build the URL. It will look something like the below for agency business objects
        # Note the 's' at the end
        # https://tenant.ivanticloud.com/api/odata/businessobject/agencys
        #
        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID
        $uri = "https://$IvantiTenantID/api/odata/businessobject/agencys"

    } # end begin

    process {
        Invoke-IvantiMethod -URI $uri -GetParameter $GetParameter
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiAgency
#EndRegion - Get-IvantiAgency.ps1
#Region - Get-IvantiBusinessObject.ps1
function Get-IvantiBusinessObject {
    <#
    .SYNOPSIS
        Get business objects from Ivanti. Defaults to all.
 
    .DESCRIPTION
        Get business objects from Ivanti. Defaults to all.
 
    .PARAMETER BusinessObject
        Ivanti Business Object to return
 
    .EXAMPLE
        Get-IvantiBusinessObject -BusinessObject agency
 
    .EXAMPLE
        Get-IvantiBusinessObject -BusinessObject change
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$BusinessObject,
        [string]$RecID
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # Build the URL. It will look something like the below for agency business objects
        # Note the 's' at the end
        # https://tenant.ivanticloud.com/api/odata/businessobject/agencys
        #
        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID
        $uri = "https://{0}/api/odata/businessobject/{1}s" -f $IvantiTenantID,$BusinessObject

        if ($RecID) {
            $uri = "{0}('{1}')" -f $uri,$RecID
        }

    } # end begin

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] $uri"
        Invoke-IvantiMethod -URI $uri
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiBusinessObject
#EndRegion - Get-IvantiBusinessObject.ps1
#Region - Get-IvantiBusinessObjectMetadata.ps1
function Get-IvantiBusinessObjectMetadata {
<#
.SYNOPSIS
    Get the metadata for an Ivanti Business Object
 
.DESCRIPTION
    Get the metadata for an Ivanti Business Object
 
.PARAMETER BusinessObject
    A business object to get meta data for. Example value: agency
 
.PARAMETER MetaDatatype
    The type of meta data to return. May be Fields, Relationships, Actions, or SavedSearch
 
.EXAMPLE
    Get-IvantiBusinessObjectMetadata -BusinessObject incident -MetaDataType Actions
 
    Get the quick actions related to incident business object type
 
.NOTES
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Metadata.htm
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Saved-Search-API.htm
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Quick-Actions-API.htm
 
#>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='Yes, Metadata is plural. technically. but really?!?!?')]
    param(
        [Parameter(Mandatory)]
        [string]$BusinessObject,
        [Parameter(Mandatory)]
        [ValidateSet('Fields','Relationships','Actions','SavedSearch')]
        [string]$MetaDataType
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function started PSBoundParameters: $($PSBoundParameters | Out-String)"

        $tenant = (Get-IvantiPSConfig).IvantiTenantID

        $session = Get-IvantiSession
        if (-not $session) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No Ivanti session available. Exiting..."
            break
        }
        $headers = @{Authorization = $Session}

        # like this https://tenant.ivanticloud.com/api/odata/agencys/$metadata
        #
        $uri = "https://{0}/api/odata/{1}s/`$metadata" -f $tenant, $BusinessObject

        $splatParameters = @{
            Uri             = $Uri
            Method          = 'GET'
            Headers         = $headers
            ErrorAction     = "Stop"
        }

        try {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Invoke-RestMethod with `$splatParameters: $($splatParameters | Out-String)"
            # Invoke rest method
            #
            $Response = Invoke-RestMethod @splatParameters
        }
        catch {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Failed to get answer"
            Write-Warning "[$($MyInvocation.MyCommand.Name)] URI: $($splatParameters.Uri)"
            $_
        }
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Executed RestMethod"
    } # End begin

    process {
        if ($Response) {

            switch ($MetaDataType) {
                'Fields' {
                    $Response.Edmx.DataServices.Schema.EntityType |
                        Where-Object {$_.Name -eq $BusinessObject} |
                        Select-Object -ExpandProperty Property
                }
                'Relationships' {
                    $Response.Edmx.DataServices.Schema.EntityType |
                        Where-Object {$_.Name -eq $BusinessObject} |
                        Select-Object -ExpandProperty NavigationProperty |
                        Select-Object Name,Type
                }
                'Actions' {
                    $Response.Edmx.DataServices.Schema.Action |
                        Select-Object Name,@{
                            Name='ActionID';
                            Expression={
                                $null = $_.InnerXML -match 'String="([\w|-]*)" />';
                                $matches[1]
                            }
                        }
                }
                'SavedSearch' {
                    # More information on Saved Search
                    # https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Saved-Search-API.htm
                    $Response.Edmx.DataServices.Schema.Function |
                        Select-Object Name,@{
                            Name='ActionID';
                            Expression={
                                $null = $_.InnerXML -match 'String="([\w|-]*)" />';
                                $matches[1]
                            }
                        }
                }
                Default {
                    $Response
                }
            } # end switch
        } else {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] No results were returned"
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No results were returned"
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
}
Export-ModuleMember -Function Get-IvantiBusinessObjectMetadata
#EndRegion - Get-IvantiBusinessObjectMetadata.ps1
#Region - Get-IvantiBusinessObjectRelationship.ps1
function Get-IvantiBusinessObjectRelationship {
<#
.SYNOPSIS
    Get related business objects from Ivanti
 
.DESCRIPTION
    Get related business objects from Ivanti
 
.PARAMETER BusinessObject
    Ivanti Business Object to return relationships for. e.g. agency, change, incident, servicereq, etc
 
.PARAMETER RecID
    The Record ID of the business object to get relationships for
 
.PARAMETER RelationshipType
    The Type of relationships to get
 
.EXAMPLE
    Get-IvantiBusinessObjectRelationship -BusinessObject agency -RecID '407A1A749C9347B59F47BD1D51061463' -RelationshipType 'AgencyAuthorizedApprovers'
 
.NOTES
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Related-Business-Objects-API.htm
#>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$BusinessObject,
        [string]$RecID,
        [string]$RelationshipType
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID

        # Build the URL. It will look something like below for agency business object relationships
        # https://{tenant url}/api/odata/businessobject/{business object name}('{business object unique key}')/{relationship name}
        #
        $uri = "https://{0}/api/odata/businessobject/{1}s('{2}')/{3}" -f $IvantiTenantID,$BusinessObject,$RecID,$RelationshipType
    } # end begin

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] $uri"
        Invoke-IvantiMethod -URI $uri
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiBusinessObjectRelationship
#EndRegion - Get-IvantiBusinessObjectRelationship.ps1
#Region - Get-IvantiCI.ps1
function Get-IvantiCI {
    <#
    .SYNOPSIS
        Get CI (assets) business objects from Ivanti. Defaults to all.
 
    .DESCRIPTION
        Get CI (assets) business objects from Ivanti. Defaults to all.
 
    .PARAMETER RecID
        Ivanti Record ID for a specific CI business object
 
    .PARAMETER Name
        CI (Asset) Name to filter on
 
    .PARAMETER IPAddress
        IP Address to filter on
 
    .EXAMPLE
        Get-IvantiCI -Name wpdotsqll42
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Filter.htm
 
    #>

    [CmdletBinding()]
    [Alias('Get-IvantiAsset')]
    param(
        [string]$RecID,
        [string]$Name,
        [string]$IPAddress
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # If one or more parameters are passed in, use only one of them
        # Order of preference is RecID, Agency, then AgencyShortName
        # if no parameters are passed in, then do not set any get parameters
        #
        if ($RecID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] RecID [$RecID] passed in, setting filter"
            $GetParameter = @{'$filter' = "RecID eq '$($RecID)'"}
        } elseif ($Name) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Agency [$Agency] passed in, setting filter"
            $GetParameter = @{'$filter' = "Name eq '$($Name)'"}
        } elseif ($IPAddress) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] AgencyShortName [$AgencyShortName] passed in, setting filter"
            $GetParameter = @{'$filter' = "IPAddress eq '$($IPAddress)'"}
        } else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No parameters passed in"
        }

        # Select only specific fields because there are way too many
        #
        $GetParameter += @{'$select' = 'RecId, Name, IPAddress, Category, CIType, ivnt_AssetFullType, Status, AssignedDescription, ivnt_SelfServiceDescription, AssignedOS, ivnt_AssignedManufacturer, Model, SerialNumber, AssetTag, OSSWPatchMgtMethod, ScheduleRebootInterval, ivnt_Location, BuildingAndFloor, EquipmentLocation, LocationRegion, BillableCpu, BillableMemory'}

        # Build the URL. It will look something like the below for ci business objects
        # Note the 's' at the end
        # https://tenant.ivanticloud.com/api/odata/businessobject/cis
        #
        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID
        $uri = "https://$IvantiTenantID/api/odata/businessobject/cis"

    } # end begin

    process {
        Invoke-IvantiMethod -URI $uri -GetParameter $GetParameter
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiCI -Alias Get-IvantiAsset
#EndRegion - Get-IvantiCI.ps1
#Region - Get-IvantiEmployee.ps1
function Get-IvantiEmployee {
    <#
    .SYNOPSIS
        Get Employee business objects from Ivanti. Defaults to all.
 
    .DESCRIPTION
        Get Employee business objects from Ivanti. Defaults to all.
 
    .PARAMETER RecID
        Ivanti Record ID for a specific Employee business object
 
    .PARAMETER Name
        Employee name, will filter against DisplayName property
 
    .PARAMETER Email
        Employee email to filter on.
 
    .PARAMETER AllFields
        Set this parameter if returning all fields is desired
 
    .EXAMPLE
        Get-IvantiEmployee -Email john.smith@domain.name
 
    .EXAMPLE
        Get-IvantiEmployee -Name 'John Smith'
 
    .EXAMPLE
        Get-IvantiEmployee -RecID DC218F83EC504222B148EF1344E15BCB -AllFields
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Filter.htm
 
    #>

    [CmdletBinding()]
    param(
        [string]$RecID,
        [string]$Name,
        [string]$Email,
        [switch]$AllFields
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # If one or more parameters are passed in, use only one of them
        # Order of preference is RecID, Name, then Email
        # if no parameters are passed in, then do not set any get parameters
        #
        if ($RecID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] RecID [$RecID] passed in, setting filter"
            $GetParameter = @{'$filter' = "RecID eq '$($RecID)'"}
        } elseif ($Name) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Name [$Name] passed in, setting filter"
            $GetParameter = @{'$filter' = "DisplayName eq '$($Name)'"}
        } elseif ($Email) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Email [$Email] passed in, setting filter"
            $GetParameter = @{'$filter' = "PrimaryEmail eq '$($Email)'"}
        } else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No parameters passed in"
        }

        if (-not $AllFields) {
            # Select only specific fields because there are way too many
            #
            $GetParameter += @{'$select' = 'RecId, DisplayName, OREmployeeID, PrimaryEmail, ManagerEmail, Agency, IsManager, IsLDAPUserAccountEnabled, LockDate, LockType, LoginAttemptCount, Disabled, LastModBy, LastModDateTime, CreatedDateTime'}
        }
        # Build the URL. It will look something like the below for Employee business objects
        # Note the 's' at the end
        # https://tenant.ivanticloud.com/api/odata/businessobject/employees
        #
        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID
        $uri = "https://$IvantiTenantID/api/odata/businessobject/employees"

    } # end begin

    process {
        Invoke-IvantiMethod -URI $uri -GetParameter $GetParameter
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiEmployee
#EndRegion - Get-IvantiEmployee.ps1
#Region - Get-IvantiIncident.ps1
function Get-IvantiIncident {
    <#
    .SYNOPSIS
        Get incident business objects from Ivanti.
 
    .DESCRIPTION
        Get incident business objects from Ivanti. Defaults to active incidents.
 
    .PARAMETER RecID
        Ivanti Record ID for a specific incident
 
    .PARAMETER AgencyName
        Filter to get incidents from a specific agency name
 
    .PARAMETER Status
        Status of the incidents to filter for. Defaults to Active. Valid values: closed, resolved, cancelled, all
 
    .PARAMETER AllFields
        If set, will return *all* available fields. Defaults to false. You've been warned!
 
    .EXAMPLE
        Get-IvantiIncident -AgencyName ABC
 
        Returns all Active incidents for Agency with name ABC
 
    .EXAMPLE
        Get-IvantiIncident -Status All
 
        Returns all Active incidents
 
    .EXAMPLE
        Get-IvantiAgency -RecID DC218F83EC504222B148EF1344E15BCB
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Filter.htm
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Search.htm
 
    #>

    [CmdletBinding()]
    param(
        [string]$RecID,
        [string]$AgencyName,
        [ValidateSet('Closed','Active','Resolved','Cancelled','All')]
        [string]$Status = 'Active',
        [switch]$AllFields = $false

    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # Build field list to select from so we don't get a bunch of extra fields we don't want
        #
        $fields = "RecID, IncidentNumber, Status, Subject, CreatedDateTime, CreatedBy, "
        $fields += "Category, Subcategory, Service, Impact, Urgency, Priority, Vendor, "
        $fields += "ImpactedAgenciesShort, LastModDateTime, LastModBy, LastCustomerUpdate, "
        $fields += "ResolvedDateTime, ResolvedBy, Resolution, ResolutionCategory"

        if ($AllFields) {
            $GetParameter = @{}
        } else {
            $GetParameter = @{
                '$select' = $fields
            }
        }

        # If one or more parameters are passed in, use only one of them
        # Order of preference is RecID, Agency, then AgencyShortName
        # if no parameters are passed in, then do not set any get parameters
        #
        if ($RecID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] RecID [$RecID] passed in, setting filter"
            $GetParameter += @{'$filter' = "RecID eq '$($RecID)'"}
        } elseif ($Status) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Status [$Status] set"
            if ($Status -eq 'All') {
                # Status is all, do not put a filter on things
            } else {
                $GetParameter += @{'$filter' = "Status eq '$($Status)'"}
            }
        } else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No RecID or Status parameters passed in"
        }

        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID

        if ($AgencyName) {
            # https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Related-Business-Objects-API.htm
            $AgencyRecID = (Get-IvantiAgency -ShortName $AgencyName).RecID

            # relationship types that may be of interest
            # IncidentAssocAgency
            # ServiceReqAssocAgency
            # ChangeAssocAgency
            #
            #$RelationshipType = 'IncidentAssocAgency'

            # Build the URL. It will look something like below for agency business object relationships
            # https://{tenant url}/api/odata/businessobject/{business object name}('{business object unique key}')/{relationship name}
            #
            $uri = "https://{0}/api/odata/businessobject/agencys('{1}')/IncidentAssocAgency" -f $IvantiTenantID,$AgencyRecID
        } else {
            # Build the URL. It will look something like the below for incident business objects
            # Note the 's' at the end
            # https://tenant.ivanticloud.com/api/odata/businessobject/incidents
            #
            $uri = "https://$IvantiTenantID/api/odata/businessobject/incidents"
        }

    } # end begin

    process {
        Invoke-IvantiMethod -URI $uri -GetParameter $GetParameter
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiIncident
#EndRegion - Get-IvantiIncident.ps1
#Region - Get-IvantiPSConfig.ps1
function Get-IvantiPSConfig {
<#
.SYNOPSIS
    Get default configurations for IvantiPS from config.json file
 
.DESCRIPTION
    Get default configurations for IvantiPS from config.json file
 
.EXAMPLE
    Get-IvantiPSConfig
#>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param ()

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function started"
        $config = "$([Environment]::GetFolderPath('ApplicationData'))\IvantiPS\config.json"
    }

    process {
        if ($config) {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Getting config from [$config]"
            [PSCustomObject](Get-Content -Path "$config" -ErrorAction Stop | ConvertFrom-Json)
        } else {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No config found at [$config]"
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Use Set-IvantiPSConfig first!"
            break
        }
    }
    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function complete"
    }
} # end function
Export-ModuleMember -Function Get-IvantiPSConfig
#EndRegion - Get-IvantiPSConfig.ps1
#Region - Get-IvantiServiceRequest.ps1
function Get-IvantiServiceRequest {
    <#
    .SYNOPSIS
        Get service request business objects from Ivanti.
 
    .DESCRIPTION
        Get service request business objects from Ivanti. Defaults to active service requests.
 
    .PARAMETER RecID
        Ivanti Record ID for a specific service request
 
    .PARAMETER AgencyName
        Filter to get service requests from a specific agency name
 
    .PARAMETER Status
        Status of the service requests to filter for. Set to All if wanting all Status values. Defaults to Active.
 
        Valid values for Status
        ------
        All
        Closed
        Cancelled
        Fulfilled
        Active
        Waiting for Customer
 
    .PARAMETER AllFields
        If set, will return *all* available fields. Defaults to false. You've been warned!
 
    .EXAMPLE
        Get-IvantiServiceRequest -AgencyName ABC
 
        Returns all Active service requests for Agency with name ABC
 
    .EXAMPLE
        Get-IvantiServiceRequest -Status All
 
        Returns all ServiceRequests
 
    .NOTES
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Filter.htm
        https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Business-Object-by-Search.htm
 
    #>

    [CmdletBinding()]
    param(
        [string]$RecID,
        [string]$AgencyName,
        [ValidateSet('Closed','Active','Fulfilled','Cancelled','Waiting For Customer','All')]
        [string]$Status = 'Active',
        [switch]$AllFields = $false
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function Started. PSBoundParameters: $($PSBoundParameters | Out-String)"

        # Build field list to select from so we don't get a bunch of extra fields we don't want
        #
        $fields = "RecID, ServiceReqNumber, Status, Subject, CreatedDateTime, CreatedBy, "
        $fields += "Service, Urgency, Priority, "
        $fields += "ImpactedAgenciesShort, Owner, OwnerTeam, OwnerTeamEmail, LastModDateTime, LastModBy, "
        $fields += "ResolvedDateTime, ResolvedBy, Resolution, BillingAgency, BillingAgencyNumber, ROParams"

        # If all fields is set, we don't both adding the fields to select
        # this will return *everything* available
        #
        if ($AllFields) {
            $GetParameter = @{}
        } else {
            $GetParameter = @{
                '$select' = $fields
            }
        }

        # If one or more parameters are passed in, use only one of them
        # Order of preference is RecID, Agency, then AgencyShortName
        # if no parameters are passed in, then do not set any get parameters
        #
        if ($RecID) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] RecID [$RecID] passed in, setting filter"
            $GetParameter += @{'$filter' = "RecID eq '$($RecID)'"}
        } elseif ($Status) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Status [$Status] set"
            if ($Status -eq 'All') {
                # Status is all, do not put a filter on things
            } else {
                $GetParameter += @{'$filter' = "Status eq '$($Status)'"}
            }
        } else {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] No RecID or Status parameters passed in"
            $GetParameter += @{'$filter' = "Status eq 'Active'"}
        }

        $IvantiTenantID = (Get-IvantiPSConfig).IvantiTenantID

        if ($AgencyName) {
            # https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Get-Related-Business-Objects-API.htm
            $AgencyRecID = (Get-IvantiAgency -ShortName $AgencyName).RecID

            # Build the URL. It will look something like below for agency business object relationships
            # https://{tenant url}/api/odata/businessobject/{business object name}('{business object unique key}')/{relationship name}
            #
            $uri = "https://{0}/api/odata/businessobject/agencys('{1}')/ServiceReqAssocAgency" -f $IvantiTenantID,$AgencyRecID
        } else {
            # Build the URL. It will look something like the below for service request business objects
            # Note the 's' at the end
            # https://tenant.ivanticloud.com/api/odata/businessobject/servicereqs
            #
            $uri = "https://{0}/api/odata/businessobject/servicereqs" -f $IvantiTenantID
        }

    } # end begin

    process {
        Invoke-IvantiMethod -URI $uri -GetParameter $GetParameter
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function ended"
    }
} # end function
Export-ModuleMember -Function Get-IvantiServiceRequest
#EndRegion - Get-IvantiServiceRequest.ps1
#Region - Get-IvantiSession.ps1
function Get-IvantiSession {
<#
    .SYNOPSIS
        Get the session id from module's privatedata
 
    .DESCRIPTION
        Get the session id from module's privatedata
 
    .EXAMPLE
        Get-IvantiSession
 
#>

    [CmdletBinding()]
    param()

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function started"
    }

    process {
        if ($MyInvocation.MyCommand.Module.PrivateData.Session) {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Using Session saved in PrivateData"
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Using Session saved in PrivateData"
            Write-Output $MyInvocation.MyCommand.Module.PrivateData.Session
        } else {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No session found in PrivateData. Use Connect-IvantiTenant first!"
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function complete"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Function complete"
    }
}
Export-ModuleMember -Function Get-IvantiSession
#EndRegion - Get-IvantiSession.ps1
#Region - Set-IvantiPSConfig.ps1
function Set-IvantiPSConfig {
<#
.SYNOPSIS
    Set the URL, default Role, and Auth Type to use when connecting to Ivanti Service Manager
 
.DESCRIPTION
    Set the URL, default Role, and Auth Type to use when connecting to Ivanti Service Manager.
    Saves the information to IvantiPS/config.json file in user profile
 
.PARAMETER IvantiTenantID
    Ivanti Tenant ID for IvantiCloud tenant. example: tenantname.ivanticloud.com
 
.PARAMETER DefaultRole
    Default role to use to connect. Example values: Admin, SelfService, SelfServiceViewer
 
.PARAMETER AuthType
    Type of authentication to use to access ISM. SessionID, APIKey, or OIDC
 
.EXAMPLE
    Set-IvantiPSConfig -IvantiTenantID tenantname.ivanticloud.com -DefaultRole SelfService -AuthType SessionID
 
.NOTES
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Authentication_of_APIs.htm
 
#>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification='This function is trivial enough that we do not need ShouldProcess')]
    Param (
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [Uri]$IvantiTenantID,
        [parameter(Mandatory)]
        [string]$DefaultRole,
        [parameter(Mandatory)]
        [string]$AuthType
    )
    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        #region configuration
        $configPath = "$([Environment]::GetFolderPath('ApplicationData'))\IvantiPS\config.json"
        Write-Verbose "Configuration will be stored in $($configPath)"
        #endregion configuration

        if (-not (Test-Path $configPath)) {
            # If the config file doesn't exist, created it
            $null = New-Item -Path $configPath -ItemType File -Force
        }
    }

    process {

        $config = [ordered]@{
            IvantiTenantID = $IvantiTenantID
            DefaultRole = $DefaultRole
            AuthType = $AuthType
        }

        $config | ConvertTo-Json | Set-Content -Path "$configPath"
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
} # end function
Export-ModuleMember -Function Set-IvantiPSConfig
#EndRegion - Set-IvantiPSConfig.ps1
### --- PRIVATE FUNCTIONS --- ###
#Region - ConvertTo-GetParameter.ps1
function ConvertTo-GetParameter {
    <#
    .SYNOPSIS
    Generate the GET parameter string for an URL from a hashtable
    #>

    [CmdletBinding()]
    param (
        [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )]
        [hashtable]$InputObject
    )

    process {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Making HTTP get parameter string out of a hashtable"
        Write-Verbose ($InputObject | Out-String)
        [string]$parameters = "?"
        foreach ($key in $InputObject.Keys) {
            $value = $InputObject[$key]
            $parameters += "$key=$($value)&"
        }
        $parameters -replace ".$"
    }
}
#EndRegion - ConvertTo-GetParameter.ps1
#Region - ConvertTo-ParameterHash.ps1
function ConvertTo-ParameterHash {
    [CmdletBinding( DefaultParameterSetName = 'ByString' )]
    param (
        # URI from which to use the query
        [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'ByUri' )]
        [Uri]$Uri,

        # Query string
        [Parameter( Position = 0, Mandatory, ParameterSetName = 'ByString' )]
        [String]$Query
    )

    process {
        $GetParameter = @{}

        if ($Uri) {
            $Query = $Uri.Query
        }

        if ($Query -match "^\?.+") {
            $Query.TrimStart("?").Split("&") | ForEach-Object {
                $key, $value = $_.Split("=")
                $GetParameter.Add($key, $value)
            }
        }

        Write-Output $GetParameter
    }
}
#EndRegion - ConvertTo-ParameterHash.ps1
#Region - Invoke-IvantiMethod.ps1
function Invoke-IvantiMethod {
<#
.SYNOPSIS
    Call the Ivanti Service Manager (ISM) end point. This is used by other functions, and is not meant to be called directly.
 
.DESCRIPTION
    Call the Ivanti Service Manager (ISM) end point. This is used by other functions, and is not meant to be called directly.
 
.PARAMETER URI
    URI for the ISM end point
 
.PARAMETER Method
    Method to use. Must be a valid Web Request Method. Defaults to GET
 
.PARAMETER Headers
    Headers to include with the request
 
.PARAMETER GetParameter
    Get parameters to append to the URI.
 
.PARAMETER Level
    Indicates level of recursion
 
.NOTES
    https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Authentication_of_APIs.htm
 
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [Uri]$URI,
        [Microsoft.PowerShell.Commands.WebRequestMethod]$Method = "GET",
        [Hashtable]$Headers,
        [Hashtable]$GetParameter = @{},
        [System.Object]$Body,
        [int]$Level = 1
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function started"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function started. PSBoundParameters: $($PSBoundParameters | Out-String)"
#region Headers
        # Construct the Headers with the following priority:
        # - Headers passes as parameters
        # - Module's default Headers
        #
        $session = Get-IvantiSession
        if (-not $session) {
            Write-Warning "[$($MyInvocation.MyCommand.Name) $Level] Must first establish session with Connect-IvantiTenant. Exiting..."
            break
        }
        $AuthHeader = @{Authorization = $Session}
        # If headers hash was passed in, join with auth header
        #
        if ($Headers) {
            $_headers = Join-Hashtable -Hashtable $Headers, $AuthHeader
        } else {
            $_headers = $AuthHeader
        }
#endregion Headers

#region Manage URI
        # Amend query from URI with GetParameter
        $uriQuery = ConvertTo-ParameterHash -Uri $Uri
        $internalGetParameter = Join-Hashtable $uriQuery, $GetParameter

        # Use default 100 for top records, unless top parm is used
        #
        if (-not $internalGetParameter.ContainsKey('$top')) {
            $internalGetParameter['$top'] = 100
        }

        # remove URL from from URI
        #
        [Uri]$Uri = $Uri.GetLeftPart("Path")
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Left portion of URI: [$uri]"
        $PaginatedUri = $Uri
        [Uri]$PaginatedUri = "{0}{1}" -f $PaginatedUri, (ConvertTo-GetParameter $internalGetParameter)

#endregion Manage URI

#region Construct IRM Parameter
        $splatParameters = @{
            Uri             = $PaginatedUri
            Method          = $Method
            Headers         = $_headers
            ErrorAction     = "Stop"
            Verbose         = $false
        }
        if ($body) {
            Write-Debug "[$($MyInvocation.MyCommand.Name) $LevelOfRecursion] Added body to splatparm: $($body | Out-String)"
            $splatParameters += @{
                Body = $body
            }
        }
#endregion Constructe IRM Parameter

#region Execute the actual query
        try {
            Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] $($splatParameters.Method) $($splatParameters.Uri)"
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Invoke-RestMethod with `$splatParameters: $($splatParameters | Out-String)"
            # Invoke the API
            #
            $RestResponse = Invoke-RestMethod @splatParameters
        }
        catch {
            Write-Warning "[$($MyInvocation.MyCommand.Name) $Level] Failed to get answer"
            Write-Warning "[$($MyInvocation.MyCommand.Name) $Level] URI: $($splatParameters.Uri)"
            $_
        }

        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Executed RestMethod"

        # Test to see if there was an error code in the
        # response from invoke-restmethod
        #
        Test-ServerResponse -InputObject $Response
#endregion Execute the actual query
    } # End begin

    process {
        if ($RestResponse) {
            # Value should have the data. If not, then just dump whatever was returned to pipeline
            #
            if (-not $RestResponse.Value) {
                $RestResponse
            } else {
                $result = $RestResponse.Value
                Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] (`$response).Count: $(($result).Count)"

                # The @odata.count property has the total number records
                #
                $ODataCount = $RestResponse."@odata.count"
                Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] ODataCount: $ODataCount"

                if ($GetParameter) {
                    if ($GetParameter['$top']) {
                        $top = $GetParameter['$top']
                    }
                    if ($GetParameter['$skip']) {
                        $skip = $GetParameter['$skip']
                    }
                }
                $TopPlusSkip = $top + $skip

                Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] TopPlusSkip: $TopPlusSkip"

                if ($TopPlusSkip -lt $ODataCount) {
                    $GetMore = $true
                    if (-not $top) {
                        $top = 100
                    }
                } else {
                    $GetMore = $false
                }
                Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] `$GetMore: $GetMore"

    #region paging
                if ($GetMore -eq $true) {
                    # Remove Parameters that don't need propagation
                    $null = $PSBoundParameters.Remove('$top')
                    $null = $PSBoundParameters.Remove('$skip')

                    if (-not $PSBoundParameters["GetParameter"]) {
                        $PSBoundParameters["GetParameter"] = $internalGetParameter
                    }

                    $total = 0
                    do {
                        $total += $result.Count

                        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Invoking pagination, [`$Total: $Total]"
                        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Output results [Level: $Level] [Results count: $(($result|Measure-Object).Count)]"

                        # Output results from this loop
                        $result

                        if ($Total -ge $ODataCount) {
                            Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Stopping paging, as [`$Total: $Total] reached [`$ODataAcount: $ODataCount]"
                            return
                        } else {
                            Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Continuing paging, as [`$Total: $Total] has not reached [`$ODataAcount: $ODataCount]"
                        }

                        # calculate the size of the next page
                        $PSBoundParameters["GetParameter"]['$skip'] = $Total + $skip
                        $expectedTotal = $PSBoundParameters["GetParameter"]['$skip'] + $top
                        if ($expectedTotal -gt $ODataCount) {
                            $reduceBy = $expectedTotal - $ODataCount
                            $PSBoundParameters["GetParameter"]['$top'] = $top - $reduceBy
                        }

                        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] URI: $($PSBoundParameters["Uri"])"
                        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] GetParameter top: $($PSBoundParameters["GetParameter"]['$top'])"
                        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] GetParameter skip: $($PSBoundParameters["GetParameter"]['$skip'])"

                        # increment the recursion level for debugging
                        $PSBoundParameters["Level"] = $Level + 1

                        # Get the next page aka recurse
                        $result = Invoke-IvantiMethod @PSBoundParameters
                    } while ($result.Count -gt 0)
    #endregion paging
                } else {
                    Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Final output results [$(($result | Measure-Object).Count)]"
                    $result
                }
            }
        } else {
            Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] No Web result object was returned"
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] `$RestResponse was empty"
        }
    }

    end {
        #Set-TlsLevel -Revert

        Write-Verbose "[$($MyInvocation.MyCommand.Name) $Level] Function ended"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name) $Level] Function ended"
    }
}
#EndRegion - Invoke-IvantiMethod.ps1
#Region - Join-Hashtable.ps1
function Join-Hashtable {
    <#
    .SYNOPSIS
        Combines multiple hashtables into a single table.
    .DESCRIPTION
        Combines multiple hashtables into a single table.
        On multiple identic keys, the last wins.
    .EXAMPLE
        PS C:\> Join-Hashtable -Hashtable $Hash1, $Hash2
        Merges the hashtables contained in $Hash1 and $Hash2 into a single hashtable.
#>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param (
        # The tables to merge.
        [Parameter( Mandatory, ValueFromPipeline )]
        [AllowNull()]
        [System.Collections.IDictionary[]]
        $Hashtable
    )
    begin {
        $table = @{ }
    }

    process {
        foreach ($item in $Hashtable) {
            foreach ($key in $item.Keys) {
                $table[$key] = $item[$key]
            }
        }
    }

    end {
        $table
    }
}
#EndRegion - Join-Hashtable.ps1
#Region - Test-ServerResponse.ps1
function Test-ServerResponse {
    [CmdletBinding()]
    <#
        .SYNOPSIS
            Evaluate the response of the API call
 
        .LINK
            https://help.ivanti.com/ht/help/en_US/ISM/2020/admin/Content/Configure/API/Session-ID-Log-In.htm
 
        .NOTES
            Thanks to Lipkau:
            https://github.com/AtlassianPS/JiraPS/blob/master/JiraPS/Private/Test-ServerResponse.ps1
    #>

    param (
        # Response of Invoke-WebRequest
        [Parameter( ValueFromPipeline )]
        [PSObject]$InputObject
    )

    begin {
    }

    process {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Checking response for error"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Checking response for error"

        if ($InputObject.Code) {
            Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Error code found, throwing error"
            throw ("{0}: {1}: {2}" -f $InputObject.Code,$InputObject.description,($InputObject.message -join ','))
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Done checking response for error"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] Done checking response for error"
    }
}
#EndRegion - Test-ServerResponse.ps1
#Region - Write-DebugMessage.ps1
function Write-DebugMessage {
    <#
    .SYNOPSIS
        Utility to write out debug message
    .NOTES
        Thanks Atlassian!
    #>

    [CmdletBinding()]
    param(
        [Parameter( ValueFromPipeline )]
        [String]$Message
    )

    begin {
        $oldDebugPreference = $DebugPreference
        if (-not ($DebugPreference -eq "SilentlyContinue")) {
            $DebugPreference = 'Continue'
        }
    }

    process {
        Write-Debug $Message
    }

    end {
        $DebugPreference = $oldDebugPreference
    }
}
#EndRegion - Write-DebugMessage.ps1

# SIG # Begin signature block
# MIIFjQYJKoZIhvcNAQcCoIIFfjCCBXoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUzln6oS0Vc6/s/IVJSh7p7UC4
# B0+gggMnMIIDIzCCAgugAwIBAgIQfxlXoOWZRbhMi6xrw92ZDzANBgkqhkiG9w0B
# AQsFADAbMRkwFwYDVQQDDBBzZWxmLnNpZ25lZC5jZXJ0MB4XDTIyMDQzMDAyNDYw
# MFoXDTIzMDQzMDAzMDYwMFowGzEZMBcGA1UEAwwQc2VsZi5zaWduZWQuY2VydDCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALUk2x84obgu2AqpvrBQ47LK
# T01imCRSOYU3wIVEyd0H0WE/gZkc/Aji68mojxlKcdLKGRNiEFDsXbWX2fWM6KC2
# PVS/txe1fgCpz5eeq9CyHqTuUz8m3XDRMtAX91R8xyiLQSFWgrfDPJWRBWHv3sNv
# ZD4c00hle+YHLhuw76oc2z22ikMhCND8GfVlSWxoIiI2hcNN5oCkqiNjxYs3fWD5
# sUcYTDWj62AL00Zml+FI6CvRO2XSdKVRMvAqN2vskwwO6ayMASYrEcJ4WTcQj1dp
# WjCZdgqMrBGVxvc6wg6YWVDzlHRCg4AxVZjt12LGkqYwkjROHN2wzE/4/0svU00C
# AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsG
# A1UdEQQUMBKCEHNlbGYuc2lnbmVkLmNlcnQwHQYDVR0OBBYEFMYfeufU5fggCTcR
# +V0v1lQC6bOkMA0GCSqGSIb3DQEBCwUAA4IBAQAsI+0UFKIeVPZ36nodt9JqhyQK
# yC1SsuvIwGew0QsdeDaM2mLwbZfmKumAPq27tOIhozaydme4vnbKyDv4MShR6MLk
# UwquAJ70k+mMrivol7SL+I208iO0itt5Kxxd+cNKl2G1tRIfGvE9qoZ02WIjvNQH
# BazJHjldlW/QXkoy0Ip/02mR3KvnGGRiipU8DjLi/lUbAUJAVO3zZ99iiXg2w5Be
# 2gOt7n7Csx10Fe2KJfKrbXVYShcim6wMbrHtvYBrtagFaAT2RXzZyQfCoGYtP2Vw
# w8fHDmJ8yFy3fRCHL53A7FsJ499gJDbj0afH0AE7uDShNoc2F8WoHaLLe7UfMYIB
# 0DCCAcwCAQEwLzAbMRkwFwYDVQQDDBBzZWxmLnNpZ25lZC5jZXJ0AhB/GVeg5ZlF
# uEyLrGvD3ZkPMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAA
# MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgor
# BgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQh8iZT5rIQs0yva88D0CFHD3DtfzAN
# BgkqhkiG9w0BAQEFAASCAQBwmlof508k+XKCy1sPBYJvYiG+AhFtMgvU/Bl10YWN
# 5X77ybiEEo2rlbhW8AmhBfGNADaALZHewORWFKak14MTmRaz1saBuu0XRYs/sx6R
# QIG0LQpBe9XtRqHntX0z49P7kmhOB/zmlDDxG2FMnNV6mXSX9GAxKcUkAfq/UPMy
# 5/f6gOqKDGL0u5VJQadcb51nFEHoHWAw1QHcNcqYD3iKwuOU2POMp0tab+AYx8Il
# L6iUIJLrl1888p3H9Z5+1jo0fW/p1W6aGiRy/MEGmEOMdSFznJdcirdyb2lD7moq
# 7Ubna8IRw4QR7+bgvvcja2Bi8tPRuKJL4gIU3nVXwE+B
# SIG # End signature block