FreshService.Tools.psm1

# Private Function Example - Replace With Your Function
function Get-FsMatrixPriority {
  <#
  .SYNOPSIS
      Get the FreshService MatrixPriority from the requested Priority
  .DESCRIPTION
      When FreshService is configured with "Priority Matrix" enabled, the supplied priority is ignored, and is instead calculated.

      The calculation is based on the Urgency and Impact of the requested ticket.

      The associated assets can override the Impact as well.

      This function back-calculates an appropriate value that can be substituted into both Urgency and Impact to achieve the desired Priority.
  .PARAMETER TicketPriority
      The desired, resultant priority of your ticket
  .INPUTS
      None
  .OUTPUTS
      Magic value that can be used as Urgency and Impact when raising a ticket
  .EXAMPLE
      PS C:\> $MatrixMagic = Get-FsMatrixPriority -TicketPriority 3
  .LINK
      https://github.com/jberkers42/Elastic.Helper
  #>


  [CmdletBinding()]

  Param (
    [int] [Parameter(Mandatory=$true)] $TicketPriority
  )

  Write-Verbose "Calculating 'Magic' value for Priority: $TicketPriority"
  # FreshService Priority Matrix stuff
    # We want to set the priority to a specific value, but with Priority Matrix on, the priority is essentially ignored
    # We do some math to make up the 'MatrixMagic' number that is then used to calculate the correct Priority
    [int]$MatrixPriority = [int]$TicketPriority + 2
    [decimal]$MatrixHalf = $MatrixPriority / 2
    [int]$MatrixMagic = [math]::Floor($MatrixHalf)
    Write-Debug "Priority: $TicketPriority"
    Write-Debug "Matrix Magic: $MatrixMagic"

  Write-Output @($MatrixMagic,$MatrixMagic)
}

Function Write-ModuleCredential {
    <#
    .SYNOPSIS
        Writes the specified API Credential to the configuration directory of
        FreshService.Tools.
        Prompts to overwrite if the credential exists.
    .PARAMETER AppId
        The object identifier for an application from Lrt.Config.Input (example: "FreshService")
    .PARAMETER AppName
        The value of the "Name" field of an application from Lrt.Config.Input (example: "FreshService")
    .PARAMETER Username
        The value of the "Username" part of the credential object
    .PARAMETER UserCredential
        Switch from asking for an API Key to ask for a User Password. The value of the "Username" part of the credential object
    .EXAMPLE
        PS C:\>
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $AppId,


        [Parameter(Mandatory = $true, Position = 2)]
        [ValidateNotNullOrEmpty()]
        [string] $AppName,


        [Parameter(Mandatory = $false, Position = 3)]
        [ValidateNotNullOrEmpty()]
        [string] $Username,


        [Parameter(Mandatory = $false, Position = 4)]
        [switch] $UserCredential = $false
    )

    $LocalAppData = [Environment]::GetFolderPath("LocalApplicationData")


    # Configuration directory: config.json & api keys will be stored in Local ApplicationDatas
    $ConfigDirPath = Join-Path `
        -Path $LocalAppData `
        -ChildPath $ModuleName


    # Determine the filename and save location for this key
    if ($UserCredential) {
        $KeyFileName = $AppId + ".Credential.xml"
    } else {
        $KeyFileName = $AppId + ".ApiKey.xml"
    }

    $KeyPath = Join-Path -Path $ConfigDirPath -ChildPath $KeyFileName

    # Prompt to Overwrite existing key
    if(Test-Path -Path $KeyPath) {
        $OverWrite = Confirm-YesNo -Message " Credential Exists for $KeyFileName, overwrite?" -ForegroundColor Yellow
        if (! $OverWrite) {
            return $null
        }
    }


    # Prompt for Key / Password
    $Key = ""
    if ($UserCredential) {
            $Key = Read-Host -AsSecureString -Prompt " > Password for $AppName"
    } else {
        while ($Key.Length -lt 10) {
            $Key = Read-Host -AsSecureString -Prompt " > API Key for $AppName"
            if ($Key.Length -lt 10) {
                # Hint - API Keys should probably be longer, if possible
                $SaveColour = $host.UI.RawUI.ForegroundColor
                $host.UI.RawUI.ForegroundColor = 'Magenta'
                Write-Output " Key less than 10 characters." -ForegroundColor Magenta
                $host.UI.RawUI.ForegroundColor = $SaveColour
            }
        }
    }

    # Create credential - with username if provided
    if (! [string]::IsNullOrEmpty($Username)) {
        $_cred = [PSCredential]::new($Username, $Key)
    } else {
        $_cred = [PSCredential]::new($AppId, $Key)
    }

    $OutObject = [PSCustomObject]@{
        Valid = $null
        Error = $null
    }

    Try {
        Export-Clixml -Path $ConfigDirPath\$KeyFileName -InputObject $_cred
        return
    } Catch {
        $OutObject.Valid = $false
        $OutObject.Error = $_.ErrorDetails.Message
        return $OutObject
      }

}

# Function to get AgentId by Agent Name

function Get-FsAgent {
  <#
  .DESCRIPTION
    Gets details of the Agent, given the name or Id. If neither are provided, all Agents are returned.
  .SYNOPSIS
    Get details of a Agent, or a list of all Agents
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Agent
  .PARAMETER Email
    Email Address of the Agent
  .PARAMETER Filter
    Filter Query for list of agents
  .PARAMETER Id
    Display Id of the Agent (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Agent properties if found
    Returns a list of PSCustomObject containing all Agents
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAgent -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of the Agent to retrieve details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByEmail',
      HelpMessage = 'Email Address of the Agent to retrieve details for')]
    [string] $Email,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByFilter',
      HelpMessage = 'Filter criteria to match agents to return')]
    [string] $Filter,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Id of the Agent to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($PSBoundParameters.ContainsKey('Id')) {
      $AgentApiUrl_Get = [io.path]::combine($Uri, "agents/", $Id)
    } else {
      $AgentApiUrl_Get = [io.path]::combine($Uri, "agents")
    }

    $Parameters = @()
    if (-not $id -and -not $name ) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    if ($Name) {
      # An Agent name was specified
      $AgentFilter =  'query="name:''' + $Name + '''"'
      $Parameters += $AgentFilter
    }

    if ($Email) {
      # An Agent e-mail address was specified
      $AgentFilter = 'query="email:''' + $Email + '''"'
      $Parameters += $AgentFilter
    }

    if ($Filter) {
      # An Agent filter was specified
      $AgentFilter = 'query="' + $Filter + '"'
      $Parameters += $AgentFilter
    }

    $ParamString = $Parameters -join '&'

    $AgentApiUrl_Get += "?" + $ParamString

    Write-Verbose "API URL $AgentApiUrl_Get"
  }

  Process {
    $Agents = $null
    # API Call to get the tag
    $Agents = Invoke-FreshServiceApiRequest -Uri $AgentApiUrl_Get -Credential $Credential

    if ($Agents -is [HashTable] -and $Agents.Error) {
      if ($Agents.Code -eq 404 -and $Id) {
        # An Agent with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Agents API. Response Code: $($Agents.Code) Note: $($Agents.Note)" -ErrorId $Agents.Code -CategoryReason $Agents.Note
        return $Agents
      }
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Agent
      Write-Output $Agents.Agent
    } else {
      Write-Output $Agents.Agents
    }

  }

}


function Invoke-FreshServiceAlertRequest {
  <#
  .SYNOPSIS
    Invoke the FreshService Alert interface
  .DESCRIPTION
    This function is intended to be called by other functions for specific resources/interactions
  .PARAMETER Uri
    Alert URL for the specific Alert Integration. Obtain from Admin -> IT Operations Management -> ALert Profiles -> Profiles -> Integrations
  .PARAMETER Credential
    PSCredential Object with the Auth Key stored in the Password property of the object.
  .PARAMETER Method
    Valid HTTP Method to use: GET, POST (Default), DELETE, PUT
  .PARAMETER Body
    PSCustomObject containing data to be sent as HTTP Request Body in JSON format.
  .OUTPUTS
    PSCustomObject containing results if successful. May be $null if no data is returned
    ErrorObject containing details of error if one is encountered.
  #>

  [CmdletBinding()]

  param(
    [Parameter(Mandatory=$true,
      HelpMessage = 'Full URI to requeste resource, including URI parameters')]
        [string]  $Uri,

    [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing the Auth Key in the Password property')]
    [PSCredential]  $Credential,

    [Parameter(Mandatory=$false,
      HelpMessage = 'Method to use when making the request. Defaults to GET')]
    [ValidateSet("Post","Get","Put","Delete")]
    [string] $Method = "POST",

    [Parameter(Mandatory=$true,
      HelpMessage = 'PsCustomObject containing data that will be sent as the Json Body')]
    [PsCustomObject] $Body
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose $Me

    if (($Method -eq 'GET') -and $Body) {
      throw "Cannot specify Request Body for Method GET."
    }

    $Header = @{}
    $AuthKey = $Credential.GetNetworkCredential().Password
    $Header.Add('Authorization', ("auth-key {0}" -f $AuthKey))
    $Header.Add('Content-Type', 'application/json')

  }

  Process {
    # Setup Error Object structure
    $ErrorObject = [PSCustomObject]@{
      Code                  =   $null
      Error                 =   $false
      Type                  =   $null
      Note                  =   $null
      Raw                   =   $_
    }

    $Results = $null

    # Enforce TLSv1.2
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

    # Make the Alert Call
    # Make the Alert Call, using the supplied Body. Contents of $Body are the responsibility of the calling code.
    Write-Verbose "$Me : Body supplied"

    try {
      $Results = Invoke-RestMethod -Method $Method -Uri $Uri -Headers $Header -Body ($Body|ConvertTo-Json -Depth 10) -ResponseHeadersVariable ResponseHeaders
    }
    catch {
      $Exception = $_.Exception
      Write-Verbose "$Me : Exception : $($Exception.StatusCode)"
      $ErrorObject.Error = $true
      $ErrorObject.Code = $Exception.StatusCode
      $ErrorObject.Note = $Exception.Message
      $ErrorObject.Raw = $Exception
      Write-Debug ($ErrorObject | ConvertTo-Json -Depth 2)

      return $ErrorObject
    }
    Write-Debug ($ResponseHeaders | ConvertTo-Json -Depth 5)
    Write-Debug ($Results | ConvertTo-Json -Depth 10)

    Write-Output $Results

  }

  End {

  }
}

# Function to get AssetId by Asset Name

function Get-FsAsset {
  <#
  .DESCRIPTION
    Gets details of the Asset, given the name or Id. If neither are provided, all Assets are returned.
  .SYNOPSIS
    Get details of a Asset, or a list of all Assets
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    (string) Name of the Asset
  .PARAMETER IncludeTypeFields
    (switch) Should Type fields be included in the result?
  .PARAMETER Id
    (int64) Display Id of the Asset (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Asset properties if found
    Returns a list of PSCustomObject containing all Assets
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of the Asset/company to retrieve details for')]
    [string] $Name,

        [Parameter(Mandatory=$false,
      HelpMessage = 'Include fields that are specific to the asset type')]
    [switch] $IncludeTypeFields,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the Asset/company to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($PSBoundParameters.ContainsKey('Id')) {
      $AssetApiUrl_Get = [io.path]::combine($Uri, "assets/", $Id)
    } else {
      $AssetApiUrl_Get = [io.path]::combine($Uri, "assets")
    }

    $Parameters = @()
    if (-not $id -and -not $name ) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    if ($Name) {
      # An asset name was specified
      $AssetFilter =  'query="name:''' + $Name + '''"'
      $Parameters += $AssetFilter
    }

    if ($IncludeTypeFields) {
      $Parameters += 'include=type_fields'
    }

    $ParamString = $Parameters -join '&'

    $AssetApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $AssetApiUrl_Get"
  }

  Process {
    $Assets = $null
    # API Call to get the tag
    $Assets = Invoke-FreshServiceApiRequest -Uri $AssetApiUrl_Get -Credential $Credential

    if ($Assets -is [HashTable] -and $Assets.Error) {
      if ($Assets.Code -eq 404 -and $Id) {
        # An Asset with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Assets API. Response Code: $($Assets.Code) Note: $($Assets.Note)" -ErrorId $Assets.Code -CategoryReason $Assets.Note
        return $Assets
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($Asset in $Assets.Assets) {
          # Check each Asset's name to see if it is the one we are looking for
          Write-Debug "Evaluating against: $Asset.name"
          if ($Asset.name -eq $Name) {
              Write-Output $Asset
          }
      }
    } elseif ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Asset
      Write-Output $Assets.Asset
    } else {
      Write-Output $Assets.Assets
    }

  }

}

# Function to get AssetId by Asset Types Name

function Get-FsAssetTypes {
  <#
  .DESCRIPTION
    Gets details of the Asset Types, given the name or Id. If neither are provided, all Asset Types are returned.
  .SYNOPSIS
    Get details of a Asset Type, or a list of all Asset Types
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Asset Type
  .PARAMETER Id
    Display Id of the Asset Type (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Asset Type properties if found
    Returns a list of PSCustomObject containing all Asset Types
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the Asset Type to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($PSBoundParameters.ContainsKey('Id')) {
      $AssetTypeApiUrl_Get = [io.path]::combine($Uri, "asset_types/", $Id)
    } else {
      $AssetTypeApiUrl_Get = [io.path]::combine($Uri, "asset_types")
    }

    $Parameters = @()
    if (-not $Id) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    $ParamString = $Parameters -join '&'

    $AssetTypeApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $AssetTypeApiUrl_Get"
  }

  Process {
    $AssetTypes = $null
    # API Call to get the tag
    $AssetTypes = Invoke-FreshServiceApiRequest -Uri $AssetTypeApiUrl_Get -Credential $Credential

    if ($AssetTypes -is [HashTable] -and $AssetTypes.Error) {
      if ($AssetTypes.Code -eq 404 -and $Id) {
        # An Asset Type with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Asset Types API. Response Code: $($AssetTypes.Code) Note: $($AssetTypes.Note)" -ErrorId $AssetTypes.Code -CategoryReason $AssetTypes.Note
        return $AssetTypes
      }
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Asset Type
      Write-Output $AssetTypes.asset_type
    } else {
      Write-Output $AssetTypes.asset_types
    }

  }

}

# Function to get DepartmentId by Department Name

function Get-FsChange {
  <#
  .DESCRIPTION
    Gets details of a specific Change, given the Id, or a list of Changes given a set of criteria.
  .SYNOPSIS
    Get details of a Change, or a list of all Changes matching a set of criteria
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Id
    Id of the Change
  .PARAMETER UpdatedSince
    Only list Changes updated since
  .OUTPUTS
    Returns a PSCustomObject containing the Change properties if found
    Returns a list of PSCustomObject containing all matching Changes
    Returns $null if no match
    Returns ErrorObject if an error is encountered (Error = $true)
  .EXAMPLE
    Return all Changes

    C:\ PS> Get-FsChange -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential)

  .EXAMPLE
    Return all Changes created since 2021-08-18

    C:\ PS> Get-FsChange -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential) -UpdatedSince '2021-08-18'
  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      ValueFromPipeline = $true,
      HelpMessage = 'Id of the Change to retrieve details for')]
    $Id,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'UpdatedSince',
      HelpMessage = 'Return only Changes updated since this date')]
    [string] $UpdatedSince
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    $Parameters = @{}
    $Parameters.Add('per_page', $PerPage)

    If ($UpdatedSince) {
      $Parameters.Add("updated_since", $UpdatedSince)
    }

    $ParamList = @()
    foreach ($Parameter in $Parameters.Keys) {
      $ParamList += '{0}={1}' -f $Parameter, $Parameters.Item($Parameter)
    }

    $ParamString = $ParamList -join '&'

    $_int = 0
  }

  Process {
    # Setup Error Object structure
    $ErrorObject = [PSCustomObject]@{
      Code                  =   $null
      Error                 =   $false
      Type                  =   $null
      Note                  =   $null
      Raw                   =   $_
    }

    if($Id) {
      if ($Id.GetType().Name -eq 'PSCustomObject') {
        if ($Id.id) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $ChangeId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } elseif ($Id.GetType().Name -eq 'HashTable' ) {
        if ($Id.ContainsKey('id')) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $ChangeId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } else {
        # Check if ID value is an integer
        if ([int]::TryParse($Id, [ref]$_int)) {
          Write-Verbose "[$Me]: Id parses as integer."
          $ChangeId = $Id
        } else {
          Write-Verbose "[$Me]: Id does not parse as integer."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Id String [$Id] not a valid Change ID."
          return $ErrorObject
        }
      }
    }

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($ChangeId) {
      $ChangeApiUrl_Get = [io.path]::combine($Uri, "changes/", $ChangeId)
    } else {
      $ChangeApiUrl_Get = [io.path]::combine($Uri, "changes")
      $ChangeApiUrl_Get += '?' + $ParamString
    }

    Write-Debug "API URL $ChangeApiUrl_Get"


    $Changes = $null
    # API Call to get the tag
    $Changes = Invoke-FreshServiceApiRequest -Uri $ChangeApiUrl_Get -Credential $Credential

    if ($Changes -is [HashTable] -and $Changes.Error) {
      if ($Changes.Code -eq 404 -and $ChangeId) {
        # Change with specified ID does not exist, return nothing
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Change API. Response Code: $($Changes.Code) Note: $($Changes.Note)" -ErrorId $Changes.Code -CategoryReason $Changes.Note
        return $Changes
      }
    }

    if ($ChangeId) {
      # We should have got only one Change
      Write-Output $Changes.Change
    } else {
      Write-Output $Changes.Changes
    }

  }

}

# Function to get AssetId by Change Fields Name

function Get-FsChangeFields {
  <#
  .DESCRIPTION
    Gets details of the Change Fields, given the name or Id. If neither are provided, all Change Fields are returned.
  .SYNOPSIS
    Get details of a Change Field, or a list of all Change Fields
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Change Field
  .PARAMETER Id
    Display Id of the Change Field (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Change Field properties if found
    Returns a list of PSCustomObject containing all Change Fields
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of field to get details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the Change Field to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI
    $ChangeFieldApiUrl_Get = [io.path]::combine($Uri, "change_form_fields")

    $Parameters = @()
    if (-not $Id) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    $ParamString = $Parameters -join '&'

    $ChangeFieldApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $ChangeFieldApiUrl_Get"
  }

  Process {
    $ChangeFields = $null
    # API Call to get the tag
    $ChangeFields = Invoke-FreshServiceApiRequest -Uri $ChangeFieldApiUrl_Get -Credential $Credential

    if ($ChangeFields -is [HashTable] -and $ChangeFields.Error) {
      if ($ChangeFields.Code -eq 404 -and $Id) {
        # An Change Field with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Change Fields API. Response Code: $($ChangeFields.Code) Note: $($ChangeFields.Note)" -ErrorId $ChangeFields.Code -CategoryReason $ChangeFields.Note
        return $ChangeFields
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($ChangeField in $ChangeFields.change_fields) {
        # Check each Department's name to see if it is the one we are looking for
        Write-Debug "Evaluating against: $($ChangeField.name)"
        if ($ChangeField.name -eq $Name) {
            Write-Output $ChangeField
        }
    }
  } elseif ($PSBoundParameters.ContainsKey('Id')) {
    foreach ($ChangeField in $ChangeFields.change_fields) {
      # Check each Department's name to see if it is the one we are looking for
      Write-Debug "Evaluating against: $($ChangeField.id)"
      if ($ChangeField.id -eq $Id) {
          Write-Output $ChangeField
      }
  }
  } else {
      Write-Output $ChangeFields.change_fields
    }

  }

}

# Function to get DepartmentId by Department Name

function Get-FsDepartment {
  <#
  .DESCRIPTION
    Gets details of the department, given the name or Id. If neither are provided, all departments are returned.
  .SYNOPSIS
    Get details of a department, or a list of all departments
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the department
  .PARAMETER Id
    Id of the Department
  .OUTPUTS
    Returns a PSCustomObject containing the Department properties if found
    Returns a list of PSCustomObject containing all Departments
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsDepartment -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of the department/company to retrieve details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the department/company to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($PSBoundParameters.ContainsKey('Id')) {
      $DepartmentApiUrl_Get = [io.path]::combine($Uri, "departments/", $Id)
    } else {
      $DepartmentApiUrl_Get = [io.path]::combine($Uri, "departments/")
    }

    Write-Debug "API URL $DepartmentApiUrl_Get"
  }

  Process {

    $Departments = $null
    # API Call to get the tag
    $Departments = Invoke-FreshServiceApiRequest -Uri $DepartmentApiUrl_Get -Credential $Credential

    if ($Departments -is [HashTable] -and $Departments.Error) {
      if ($Departments.Code -eq 404 -and $Id) {
        # A department with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Departments API. Response Code: $($Departments.Code) Note: $($Departments.Note)" -ErrorId $Departments.Code -CategoryReason $Departments.Note
        return $Departments
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($Department in $Departments.departments) {
          # Check each Department's name to see if it is the one we are looking for
          Write-Debug "Evaluating against: $Department.name"
          if ($Department.name -eq $Name) {
              Write-Output $Department
          }
      }
    } elseif ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one department
      Write-Output $Departments.department
    } else {
      Write-Output $Departments.departments
    }

  }

}

# Function to get AssetId by Department Fields Name

function Get-FsDepartmentFields {
  <#
  .DESCRIPTION
    Gets details of the Department Fields, given the name or Id. If neither are provided, all Department Fields are returned.
  .SYNOPSIS
    Get details of a Department Field, or a list of all Department Fields
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Department Field
  .OUTPUTS
    Returns a PSCustomObject containing the Department Field properties if found
    Returns a list of PSCustomObject containing all Department Fields
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of field to get details for')]
    [string] $Name
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI
    $DepartmentFieldApiUrl_Get = [io.path]::combine($Uri, "department_fields")

    $Parameters = @()
    $Parameters += "per_page={0}" -f $PerPage

    $ParamString = $Parameters -join '&'

    $DepartmentFieldApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $DepartmentFieldApiUrl_Get"
  }

  Process {
    $DepartmentFields = $null
    # API Call to get the tag
    $DepartmentFields = Invoke-FreshServiceApiRequest -Uri $DepartmentFieldApiUrl_Get -Credential $Credential

    if ($DepartmentFields -is [HashTable] -and $DepartmentFields.Error) {
      if ($DepartmentFields.Code -eq 404 -and $Id) {
        # An Department Field with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Department Fields API. Response Code: $($DepartmentFields.Code) Note: $($DepartmentFields.Note)" -ErrorId $DepartmentFields.Code -CategoryReason $DepartmentFields.Note
        return $DepartmentFields
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($DepartmentField in $DepartmentFields.department_fields) {
        # Check each Department's name to see if it is the one we are looking for
        Write-Debug "Evaluating against: $($DepartmentField.name)"
        if ($DepartmentField.name -eq $Name) {
            Write-Output $DepartmentField
        }
      }
    } else {
      Write-Output $DepartmentFields.department_fields
    }

  }

}


function Invoke-FreshServiceApiRequest {
  <#
  .SYNOPSIS
    Invoke the FreshService API
  .DESCRIPTION
    This function is intended to be called by other functions for specific resources/interactions
  .PARAMETER Uri
    Base API URL for the API Call
  .PARAMETER Credential
    PSCredential Object with the API Key stored in the Password property of the object.
  .PARAMETER Method
    Valid HTTP Method to use: GET (Default), POST, DELETE, PUT
  .PARAMETER Body
    PSCustomObject containing data to be sent as HTTP Request Body in JSON format.
  .PARAMETER Depth
    How deep are we going?
  .OUTPUTS
    PSCustomObject containing results if successful. May be $null if no data is returned
    ErrorObject containing details of error if one is encountered.
  #>

  [CmdletBinding()]

  param(
    [Parameter(Mandatory=$true,
      HelpMessage = 'Full URI to requeste resource, including URI parameters')]
        [string]  $Uri,

    [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing the API Key in the Password property')]
    [PSCredential]  $Credential,

    [Parameter(Mandatory=$false,
      HelpMessage = 'Method to use when making the request. Defaults to GET')]
    [ValidateSet("Post","Get","Put","Delete")]
    [string] $Method = "GET",

    [Parameter(Mandatory=$false,
      HelpMessage = 'PsCustomObject containing data that will be sent as the Json Body')]
    [PsCustomObject] $Body,

    [Parameter(Mandatory=$false,
      HelpMessage = 'How deep are we?')]
    [int] $Depth = 0
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose $Me

    if (($Method -eq 'GET') -and $Body) {
      throw "Cannot specify Request Body for Method GET."
    }

    $Header = @{}
    $ApiKey = $Credential.GetNetworkCredential().Password
    $Creds = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $ApiKey,$null)))
    $Header.Add('Authorization', ("Basic {0}" -f $Creds))
    $Header.Add('Content-Type', 'application/json')

    $MaxRelLink = 10
    $RateLimitInterval = 60

    $OldRelLink = ''
  }

  Process {
    # Setup Error Object structure
    $ErrorObject = [PSCustomObject]@{
      Code                  =   $null
      Error                 =   $false
      Type                  =   $null
      Note                  =   $null
      Raw                   =   $_
    }

    $Results = $null

    # Enforce TLSv1.2
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

    # Make the API Call
    if ($Body) {
      # Make the API Call, using the supplied Body. Contents of $Body are the responsibility of the calling code.
      Write-Verbose "$Me : Body supplied"

      try {
        $Results = Invoke-RestMethod -Method $Method -Uri $Uri -Headers $Header -Body ($Body|ConvertTo-Json -Depth 10) -FollowRelLink -MaximumFollowRelLink $MaxRelLink -ResponseHeadersVariable ResponseHeaders
      }
      catch {
        $Exception = $_.Exception
        Write-Verbose "$Me : Exception : $($Exception.StatusCode)"
        $ErrorObject.Error = $true
        $ErrorObject.Code = $Exception.StatusCode
        $ErrorObject.Note = $Exception.Message
        $ErrorObject.Raw = $Exception
        Write-Debug ($ErrorObject | ConvertTo-Json -Depth 2)

        return $ErrorObject
      }
      Write-Debug ($ResponseHeaders | ConvertTo-Json -Depth 5)
      Write-Debug ($Results | ConvertTo-Json -Depth 10)
    } else {
      # Make the API Call without a body. This is for GET requests, where details of what we want to get is in the URI
      Write-Verbose "$Me : No Body supplied"
      try {
        $Results = Invoke-RestMethod -Method $Method -Uri $Uri -Headers $Header -FollowRelLink -MaximumFollowRelLink $MaxRelLink -ResponseHeadersVariable ResponseHeaders
      }
      catch {
        $Exception = $_.Exception
        Write-Verbose "$Me : Exception : $($Exception.StatusCode)"
        $ErrorObject.Error = $true
        $ErrorObject.Code = $Exception.Response.StatusCode.value__
        $ErrorObject.Note = $Exception.Message
        $ErrorObject.Raw = $Exception
        # Write-Debug ($ErrorObject | ConvertTo-Json -Depth 2)

        Throw "$Me : Encountered error getting response. $($ErrorObject.Code) : $($ErrorObject.Note) from: $RelLink"

        return $ErrorObject
      }
    }
    Write-Verbose ($ResponseHeaders | ConvertTo-Json -Depth 5)

    Write-Output $Results

    # Check if we have a link to a next page
    if (($ResponseHeaders.ContainsKey('Link')) -and ($null -ne $ResponseHeaders.Link) -and ('' -ne $ResponseHeaders.Link)) {
      $Depth += 1
      Write-Verbose "Next Link: $($ResponseHeaders.Link) at Depth: $Depth"
      # Extract the URL from the link text which looks like '<https::domain.freshservice.com/api/v2/tickets?per_page=100&page=21>; Rel="next"'
      $RelLink = [regex]::match($ResponseHeaders.Link,'\<([^\>]+)\>.*').Groups[1].Value

      # If the link has not changed, don't follow it
      if ($RelLink -ne $OldRelLink) {
        # Check Rate Limiting
        $RateLimitMax = $ResponseHeaders.'X-RateLimit-Total'
        $RateLimitRemaining = $ResponseHeaders.'X-RateLimit-Remaining'
        $RateLimitUsedCurrentRequest = $ResponseHeaders.'X-RateLimit-Used-CurrentRequest'
        Write-Verbose "RateLimitMax: $RateLimitMax; RateLimitRemaining: $RateLimitRemaining; RateLimitUsedCurrentRequest: $RateLimitUsedCurrentRequest"

        if (($RateLimitUsedCurrentRequest * $MaxRelLink) -ge $RateLimitRemaining) {
          # Nearing Rate Limit
          Write-Verbose "Sleeping to evade API Rate Limit"
          Start-Sleep -Seconds $RateLimitInterval
        }

        Write-Verbose "Requesting Next set of results from $RelLink"
        # Make a nested call to myself to get the next batch of results within API limits
        # Since you cannot have multiple pages of results for "Creating" resources, a $Body is never required here
        $Results = Invoke-FreshServiceApiRequest -Uri $RelLink -Credential $Credential -Method $Method -Depth $Depth

        Write-Output $Results

        if ($Results -is [HashTable] -and $Results.ContainsKey('Error') -and $Results.Error) {
          Write-Debug ($Results | ConvertTo-Json)
          Throw "$Me : Encountered error getting additional results. $($ErrorObject.Code) : $($ErrorObject.Note) from: $RelLink"
        } else {
          Write-Output $Results
        }
        $OldRelLink = $RelLink
      }
    }
  }

  End {
    Write-Verbose "Returning from Depth: $Depth"
    return
  }
}

<# function New-FreshServiceConfiguration {

  [CmdletBinding(SupportsShouldProcess)]

  # FreshService Configuration Values
  $FreshService = @{}
  $FreshService.Add("ApiUrl","")
  $FreshService.Add("ApiKey","")
  $FreshService.Add("DefaultTicketGroupId","")
  $FreshService.Add("DefaultTicketSource","")
  $FreshService.Add("DefaultCustomFields", @{})

  # Add as Subsection in Config file
  $FstConfig = @{}
  $FstConfig.Add("FreshService",$FreshService)

  if ($PSCmdlet.ShouldProcess($FstConfigFile, 'Write Configuration'))
  {
    # Write the configuration
    Write-Verbose "Writing Configuration"
  }
  else {
    # Write some output
  }

  Return $FstConfig
}
#>


function Update-FreshServiceConfiguration {
  [CmdletBinding(SupportsShouldProcess=$true)]
  param (
        [string] [Parameter(Mandatory=$true)] $APIUrl,
        [pscredential] [Parameter(Mandatory=$true)] $ApiKey,
    [Int64] [Parameter(Mandatory=$true)] $DefaultTicketGroupId,
    [int] [Parameter(Mandatory=$true)] $DefaultTicketSource,
    [pscustomobject] [Parameter(Mandatory=$false)] $DefaultCustomFields
  )

  # Where is the configuration saved?
  $ConfigDirPath = Join-Path `
    -Path ([Environment]::GetFolderPath("LocalApplicationData"))`
    -ChildPath $ModuleName

  $ConfigFileInfo = [System.IO.FileInfo]::new((Join-Path -Path $ConfigDirPath -ChildPath $PreferencesFileName))

  # Create Config Dir (if it does not exist)
  if (! (Test-Path -Path $ConfigDirPath)) {
    New-Item -Path $LocalAppData -Name $ModuleInfo.Name -ItemType Directory | Out-Null
    Write-Verbose "Created configuration directory at $ConfigDirPath"
  }

  $FstConfig.FreshService.ApiUrl = $APIUrl
  $FstConfig.FreshService.ApiKey = $ApiKey
  $FstConfig.FreshService.DefaultTicketGroupId = $DefaultTicketGroupId
  $FstConfig.FreshService.DefaultTicketSource = $DefaultTicketSource
  $FstConfig.FreshService.DefaultCustomFields = $DefaultCustomFields

  if ($ConfigFileInfo.Exists) {
    if ($PSCmdlet.ShouldProcess($ConfigFileInfo, "Overwrite configuration")) {
      # Overwrite Configuration
    }
  } else {
    if ($PSCmdlet.ShouldProcess($ConfigFileInfo, "Save Configuration")) {
      # Save Configuration
    }
  }
}

# Function to get DepartmentId by Department Name

function Get-FsProblem {
  <#
  .DESCRIPTION
    Gets details of a specific Problem, given the Id, or a list of Problems given a set of criteria.
  .SYNOPSIS
    Get details of a Problem, or a list of all Problems matching a set of criteria
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Id
    Id of the Problem
  .PARAMETER UpdatedSince
    Only list Problems updated since
  .OUTPUTS
    Returns a PSCustomObject containing the Problem properties if found
    Returns a list of PSCustomObject containing all matching Problems
    Returns $null if no match
    Returns ErrorObject if an error is encountered (Error = $true)
  .EXAMPLE
    Return all Problems

    C:\ PS> Get-FsProblem -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential)

  .EXAMPLE
    Return all Problems created since 2021-08-18

    C:\ PS> Get-FsProblem -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential) -UpdatedSince '2021-08-18'
  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      ValueFromPipeline = $true,
      HelpMessage = 'Id of the Problem to retrieve details for')]
    $Id,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'UpdatedSince',
      HelpMessage = 'Return only Problems updated since this date')]
    [string] $UpdatedSince
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    $Parameters = @{}
    $Parameters.Add('per_page', $PerPage)

    If ($UpdatedSince) {
      $Parameters.Add("updated_since", $UpdatedSince)
    }

    $ParamList = @()
    foreach ($Parameter in $Parameters.Keys) {
      $ParamList += '{0}={1}' -f $Parameter, $Parameters.Item($Parameter)
    }

    $ParamString = $ParamList -join '&'

    $_int = 0
  }

  Process {
    # Setup Error Object structure
    $ErrorObject = [PSCustomObject]@{
      Code                  =   $null
      Error                 =   $false
      Type                  =   $null
      Note                  =   $null
      Raw                   =   $_
    }

    if($Id) {
      if ($Id.GetType().Name -eq 'PSCustomObject') {
        if ($Id.id) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $ProblemId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } elseif ($Id.GetType().Name -eq 'HashTable' ) {
        if ($Id.ContainsKey('id')) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $ProblemId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } else {
        # Check if ID value is an integer
        if ([int]::TryParse($Id, [ref]$_int)) {
          Write-Verbose "[$Me]: Id parses as integer."
          $ProblemId = $Id
        } else {
          Write-Verbose "[$Me]: Id does not parse as integer."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Id String [$Id] not a valid Problem ID."
          return $ErrorObject
        }
      }
    }

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($ProblemId) {
      $ProblemApiUrl_Get = [io.path]::combine($Uri, "problems/", $ProblemId)
    } else {
      $ProblemApiUrl_Get = [io.path]::combine($Uri, "problems")
      $ProblemApiUrl_Get += '?' + $ParamString
    }

    Write-Debug "API URL $ProblemApiUrl_Get"

    $Problems = $null
    # API Call to get the tag
    $Problems = Invoke-FreshServiceApiRequest -Uri $ProblemApiUrl_Get -Credential $Credential

    if ($Problems -is [HashTable] -and $Problems.Error) {
      if ($Problems.Code -eq 404 -and $Id) {
        # Problem with specified ID does not exist, return nothing
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Problem API. Response Code: $($Problems.Code) Note: $($Problems.Note)" -ErrorId $Problems.Code -CategoryReason $Problems.Note
        return $Problems
      }
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Problem
      Write-Output $Problems.Problem
    } else {
      Write-Output $Problems.Problems
    }

  }

}

# Function to get AssetId by Problem Fields Name

function Get-FsProblemFields {
  <#
  .DESCRIPTION
    Gets details of the Problem Fields, given the name or Id. If neither are provided, all Problem Fields are returned.
  .SYNOPSIS
    Get details of a Problem Field, or a list of all Problem Fields
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Problem Field
  .PARAMETER Id
    Display Id of the Problem Field (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Problem Field properties if found
    Returns a list of PSCustomObject containing all Problem Fields
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of field to get details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the Problem Field to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI
    $ProblemFieldApiUrl_Get = [io.path]::combine($Uri, "problem_form_fields")

    $Parameters = @()
    if (-not $Id) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    $ParamString = $Parameters -join '&'

    $ProblemFieldApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $ProblemFieldApiUrl_Get"
  }

  Process {
    $ProblemFields = $null
    # API Call to get the tag
    $ProblemFields = Invoke-FreshServiceApiRequest -Uri $ProblemFieldApiUrl_Get -Credential $Credential

    if ($ProblemFields -is [HashTable] -and $ProblemFields.Error) {
      if ($ProblemFields.Code -eq 404 -and $Id) {
        # An Problem Field with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Problem Fields API. Response Code: $($ProblemFields.Code) Note: $($ProblemFields.Note)" -ErrorId $ProblemFields.Code -CategoryReason $ProblemFields.Note
        return $ProblemFields
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($ProblemField in $ProblemFields.problem_fields) {
        # Check each Department's name to see if it is the one we are looking for
        Write-Debug "Evaluating against: $($ProblemField.name)"
        if ($ProblemField.name -eq $Name) {
            Write-Output $ProblemField
        }
    }
  } elseif ($PSBoundParameters.ContainsKey('Id')) {
    foreach ($ProblemField in $ProblemFields.problem_fields) {
      # Check each Department's name to see if it is the one we are looking for
      Write-Debug "Evaluating against: $($ProblemField.id)"
      if ($ProblemField.id -eq $Id) {
          Write-Output $ProblemField
      }
  }
  } else {
      Write-Output $ProblemFields.problem_fields
    }

  }

}

# Function to get RequesterId by Requester Name

function Get-FsRequester {
  <#
  .DESCRIPTION
    Gets details of the Requester, given the name or Id. If neither are provided, all Requesters are returned.
  .SYNOPSIS
    Get details of a Requester, or a list of all Requesters
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Requester
  .PARAMETER Email
    Email address of the Requester
  .PARAMETER Filter
    Filter Query for list of Requesters
  .PARAMETER Id
    Display Id of the Requester (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Requester properties if found
    Returns a list of PSCustomObject containing all Requesters
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsRequester -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of the Requester to retrieve details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByEmail',
      HelpMessage = 'Email Address of the Requester to retrieve details for')]
    [string] $Email,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ByFilter',
      HelpMessage = 'Filter criteria to match Requesters to return')]
    [string] $Filter,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Id of the Requester to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($PSBoundParameters.ContainsKey('Id')) {
      $RequesterApiUrl_Get = [io.path]::combine($Uri, "requesters/", $Id)
    } else {
      $RequesterApiUrl_Get = [io.path]::combine($Uri, "requesters")
    }

    $Parameters = @()
    if (-not $id -and -not $name ) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    if ($Name) {
      # An Requester name was specified
      $RequesterFilter =  'query="name:''' + $Name + '''"'
      $Parameters += $RequesterFilter
    }

    if ($Email) {
      # An Requester e-mail address was specified
      $RequesterFilter = 'query="primary_email:''' + $Email + '''"'
      $Parameters += $RequesterFilter
    }

    if ($Filter) {
      # An Requester filter was specified
      $RequesterFilter = 'query="' + $Filter + '"'
      $Parameters += $RequesterFilter
    }

    $ParamString = $Parameters -join '&'

    $RequesterApiUrl_Get += "?" + $ParamString

    Write-Verbose "API URL $RequesterApiUrl_Get"
  }

  Process {
    $Requesters = $null
    # API Call to get the tag
    $Requesters = Invoke-FreshServiceApiRequest -Uri $RequesterApiUrl_Get -Credential $Credential

    if ($Requesters -is [HashTable] -and $Requesters.Error) {
      if ($Requesters.Code -eq 404 -and $Id) {
        # An Requester with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Requesters API. Response Code: $($Requesters.Code) Note: $($Requesters.Note)" -ErrorId $Requesters.Code -CategoryReason $Requesters.Note
        return $Requesters
      }
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Requester
      Write-Output $Requesters.Requester
    } else {
      Write-Output $Requesters.Requesters
    }

  }

}

# Function to get DepartmentId by Department Name

function Get-FsTicket {
  <#
  .DESCRIPTION
    Gets details of a specific ticket, given the Id, or a list of tickets given a set of criteria.
  .SYNOPSIS
    Get details of a ticket, or a list of all tickets matching a set of criteria
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Id
    Id of the Ticket
  .PARAMETER Filter
    Lucene formatted filter query to match tickets. The parameter values must be surrounded by single quotes.

    Note:
      Queries can be combined using AND or OR.
      https://domain.freshservice.com/api/v2/tickets/filter?query="priority: 1 AND status: 2 OR urgency: 3"
    Supported operators
      1. priority: 1 (priority equal to 1)
      2. priority:> 1 (priority greater than 1)
      3. priority :< 1 (priority less than 1)
    Formatting
      1. String fields to be enclosed in single quotes ('')
      2. Number fields to be given as number without quotes.
      3. Date and date_time fields to be enclosed in single quotes('yyyy-mm-dd')
      4. only :> and :< are supported for date and date_time fields. Both fields expect input in the same format as 'yyyy-mm-dd'
  .PARAMETER UpdatedSince
    Only list tickets updated since
  .OUTPUTS
    Returns a PSCustomObject containing the Ticket properties if found
    Returns a list of PSCustomObject containing all matching Tickets
    Returns $null if no match
    Returns ErrorObject if an error is encountered (Error = $true)
  .EXAMPLE
    Return all tickets

    C:\ PS> Get-FsTicket -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential)

  .EXAMPLE
    Return all tickets created since 2021-08-18

    C:\ PS> Get-FsTicket -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential) -UpdatedSince '2021-08-18'

  .EXAMPLE
    Return all tickets created since 2021-08-18, using filter criteria

    C:\ PS> Get-FsTicket -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential) -Filter "created_at:>'2021-08-18'"

  .EXAMPLE
    Return all tickets created between 2021-08-07 and 2021-08-14, using filter criteria

    C:\ PS> Get-FsTicket -Uri "https://ipsec.freshservice.com/api/v2" -Credential (Get-PSCredential) -Filter "created_at:>'2021-08-07' AND created_at:<'2021-08-14'"
  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      ValueFromPipeline = $true,
      HelpMessage = 'Id of the Ticket to retrieve details for')]
    $Id,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'ByFilter',
      HelpMessage = 'Criteria to match tickets by')]
    [string] $Filter,

    [Parameter(Mandatory=$false,
      HelpMessage = 'Include statistics in output')]
    [switch] $IncludeStats,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'UpdatedSince',
      HelpMessage = 'Return only tickets updated since this date')]
    [string] $UpdatedSince
    )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    $Parameters = @{}

    if ($MyInvocation.ExpectingInput) {
      Write-Verbose "[$Me]: Accepting Pipeline Input"
    } elseif ($PSBoundParameters.ContainsKey('Id')) {
      Write-Verbose "[$Me]: Accepting Id Parameter"
    } else {
      # Only do per_page if we are not getting a single ticket
      $Parameters.Add('per_page', $PerPage)
    }

    If ($Filter) {
      $Parameters.Add('query', [System.Web.HttpUtility]::UrlEncode( '"' + $Filter + '"'))
    }

    If ($UpdatedSince) {
      $Parameters.Add("updated_since", $UpdatedSince)
    }

    if ($IncludeStats) {
      $Parameters.Add('include', 'stats')
    }

    $ParamList = @()
    foreach ($Parameter in $Parameters.Keys) {
      $ParamList += '{0}={1}' -f $Parameter, $Parameters.Item($Parameter)
    }

    $ParamString = $ParamList -join '&'

    $_int = 0
  }

  Process {
    # Setup Error Object structure
    $ErrorObject = [PSCustomObject]@{
      Code                  =   $null
      Error                 =   $false
      Type                  =   $null
      Note                  =   $null
      Raw                   =   $_
    }

    if($Id) {
      if ($Id.GetType().Name -eq 'PSCustomObject') {
        if ($Id.id) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $TicketId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } elseif ($Id.GetType().Name -eq 'HashTable' ) {
        if ($Id.ContainsKey('id')) {
          Write-Verbose "[$Me]: PipeInput object has property named id."
          $TicketId = $Id.id
        } else {
          Write-Verbose "[$Me]: No Property found named id."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Supplied object does not have a property named id."
          return $ErrorObject
        }
      } else {
        # Check if ID value is an integer
        if ([int]::TryParse($Id, [ref]$_int)) {
          Write-Verbose "[$Me]: Id parses as integer."
          $TicketId = $Id
        } else {
          Write-Verbose "[$Me]: Id does not parse as integer."
          $ErrorObject.Error = $true
          $ErrorObject.Note = "Id String [$Id] not a valid Ticket ID."
          return $ErrorObject
        }
      }
    }

    # Build up the URI based on whether we are looking up by Name or Id, or getting all
    if ($TicketId) {
      $TicketApiUrl_Get = [io.path]::combine($Uri, "tickets/", $TicketId)
    } elseif ($PSBoundParameters.ContainsKey('Filter')) {
      $TicketApiUrl_Get = [io.path]::combine($Uri, "tickets/filter")
    } else {
      $TicketApiUrl_Get = [io.path]::combine($Uri, "tickets")
    }
    $TicketApiUrl_Get += '?' + $ParamString

    Write-Debug "API URL $TicketApiUrl_Get"

    $Tickets = $null
    # API Call to get the tag
    $Tickets = Invoke-FreshServiceApiRequest -Uri $TicketApiUrl_Get -Credential $Credential

    if ($Tickets -is [HashTable] -and $Tickets.Error) {
      if ($Tickets.Code -eq 404 -and $TicketId) {
        # Ticket with specified ID does not exist, return nothing
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Ticket API. Response Code: $($Tickets.Code) Note: $($Tickets.Note)" -ErrorId $Tickets.Code -CategoryReason $Tickets.Note
        return $Tickets
      }
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
      # We should have got only one Ticket
      Write-Output $Tickets.ticket
    } else {
      Write-Output $Tickets.tickets
    }

  }

}

# Function to get AssetId by Ticket Fields Name

function Get-FsTicketFields {
  <#
  .DESCRIPTION
    Gets details of the Ticket Fields, given the name or Id. If neither are provided, all Ticket Fields are returned.
  .SYNOPSIS
    Get details of a Ticket Field, or a list of all Ticket Fields
  .PARAMETER Uri
    The base URL for the customer's FreshService environment
  .PARAMETER Credential
    PSCredential Object containing the API Key in the Password field
  .PARAMETER Name
    Name of the Ticket Field
  .PARAMETER Id
    Display Id of the Ticket Field (not the internal ID that FreshService uses)
  .OUTPUTS
    Returns a PSCustomObject containing the Ticket Field properties if found
    Returns a list of PSCustomObject containing all Ticket Fields
    Returns $null if no match
    Returns ErrorObject if an error is encountered
  .EXAMPLE
    Get-FsAsset -Uri "https://ipsec.freshservice.com/api/v2" -FsApiKey (Get-PSCredential) -Name "IPSec"

  #>

  [CmdletBinding(DefaultParameterSetName = 'All')]
  param(
        [Parameter(Mandatory=$true,
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [string] $Uri,

        [Parameter(Mandatory=$true,
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [PsCredential] $Credential,

    [Parameter(Mandatory=$true,
      ParameterSetName = 'ByName',
      HelpMessage = 'Name of field to get details for')]
    [string] $Name,

        [Parameter(Mandatory=$true,
      ParameterSetName = 'ById',
      HelpMessage = 'Name of the Ticket Field to retrieve details for')]
    [int64] $Id
    )

  Begin {
    # Use Max Page Size to reduce the number of requests
    $PerPage = 100

    # Build up the URI
    $TicketFieldApiUrl_Get = [io.path]::combine($Uri, "ticket_form_fields")

    $Parameters = @()
    if (-not $Id) {
      $Parameters += "per_page={0}" -f $PerPage
    }

    $ParamString = $Parameters -join '&'

    $TicketFieldApiUrl_Get += "?" + $ParamString

    Write-Debug "API URL $TicketFieldApiUrl_Get"
  }

  Process {
    $TicketFields = $null
    # API Call to get the tag
    $TicketFields = Invoke-FreshServiceApiRequest -Uri $TicketFieldApiUrl_Get -Credential $Credential

    if ($TicketFields -is [HashTable] -and $TicketFields.Error) {
      if ($TicketFields.Code -eq 404 -and $Id) {
        # An Ticket Field with the specified ID does not exist (Not Found)
      } else {
        # An error was encountered
        Write-Error -Message "Error calling Ticket Fields API. Response Code: $($TicketFields.Code) Note: $($TicketFields.Note)" -ErrorId $TicketFields.Code -CategoryReason $TicketFields.Note
        return $TicketFields
      }
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
      foreach ($TicketField in $TicketFields.ticket_fields) {
        # Check each Department's name to see if it is the one we are looking for
        Write-Debug "Evaluating against: $($TicketField.name)"
        if ($TicketField.name -eq $Name) {
            Write-Output $TicketField
        }
    }
  } elseif ($PSBoundParameters.ContainsKey('Id')) {
    foreach ($TicketField in $TicketFields.ticket_fields) {
      # Check each Department's name to see if it is the one we are looking for
      Write-Debug "Evaluating against: $($TicketField.id)"
      if ($TicketField.id -eq $Id) {
          Write-Output $TicketField
      }
  }
  } else {
      Write-Output $TicketFields.ticket_fields
    }

  }

}

# Function to create a new ticket in FreshService

Function New-FsTicket {
  <#
    .DESCRIPTION
    Create a Ticket in FreshService using the supplied details.
    .PARAMETER ApiUrl
      (string): The URL to use for FreshService
    .PARAMETER ApiKey
      (string): The API Key used to authenticate to FreshService
    .PARAMETER Subject
      (string): The Subject of the new ticket.
    .PARAMETER Requester
      (string): The name of the Requester in FreshService
    .PARAMETER RequesterEmail
      (string): The email address of the Requester in FreshService
    .PARAMETER Description
      (string): The body of the incident description.
    .PARAMETER TicketPriority
      (string): The Priority to set the FreshService Ticket to
          1 = Low, 2 = Medium, 3 = High , 4 = Urgent
    .PARAMETER TicketStatus
      (string): The Status that the ticket will be set to
          2 = Open, 3 = Pending, 4 = Resolved, 5 = Closed
    .PARAMETER TicketSource
      (string): The Numeric ID of the source associated with this ticket.
          LogRhythm = 1001 in our deployment.
          Use the FreshService API to discover other values.
    .PARAMETER AssignGroup
      (string): The Numeric ID of the group to assign this ticket to on creation.
          This is obtained using the FreshService API.
    .PARAMETER Category
      (string): The Category of ticket to assign.
          Device = Device Incident.
          Security = Security Incident.
    .PARAMETER DepartmentId
      (string): The Numeric ID of the department this ticket is assocated with.
          Use the FreshService API to determine a DepartmentId from a Department Name
    .PARAMETER Assets
      (array of hastable): List of Assets to associate to the ticket
    .PARAMETER Tags
      (string): Tag(s) to add to the ticket
    .PARAMETER CustomFields
      (pscustomobject): Custom Fields/values to be added to ticket

    .OUTPUTS
      Returns TicketId if a ticket was successfully created.
    .EXAMPLE
      New-FsTicket -FsApiUrl FsApiUrl -FsApiKey FsApiKey -Subject "Ticket Subject" -Requester "Requester Name" -RequesterEmail "requester@company.com" -Description "Formatted description body of the ticket" -TicketPriority 4 -TicketStatus 2 -TicketSource 1001 -Group 10003675 -Category "Security" -Department 10034567

  #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low', DefaultParameterSetName = 'PriorityMatrix')]
    param(
    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Base Uri for the FreshService API for your organisation')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Uri,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'PSCredential Object containing FreshService API Key in Password field')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [PsCredential] $Credential,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'The Subject of the new Ticket')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Subject,

    [Parameter(Mandatory = $false,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'ID of the Ticket Requester (FreshService ID)')]
    [Parameter(Mandatory = $false,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Requester,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Email address of Ticket Requester')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $RequesterEmail,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'The Ticket Description - ie. The main body of the ticket')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Description,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'The Priority of the new Ticket')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [ValidateSet("1","2","3","4")]
    [Int] $TicketPriority,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Status to assign to ticket when creating')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
      [ValidateSet("2","3","4","5")]
      [string] $TicketStatus,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Source of ticket (Phone, email, specific system)')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $TicketSource,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Group (Queue) to assign ticket to')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Group,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Ticket Category')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $Category,

    [Parameter(Mandatory = $true,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Id of Department (Customer) to which Ticket belongs')]
    [Parameter(Mandatory = $true,
      ParameterSetName = 'NoPriorityMatrix')]
    [string] $DepartmentId,

    [Parameter(Mandatory = $false,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Custom Fields with values to apply to ticket')]
    [Parameter(Mandatory = $false,
      ParameterSetName = 'NoPriorityMatrix')]
    [PsCustomObject] $CustomFields,

    [Parameter(Mandatory = $false,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Assets to be associated to ticket')]
    [Parameter(Mandatory = $false,
      ParameterSetName = 'NoPriorityMatrix')]
    $Assets,

    [Parameter(Mandatory = $false,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Tags to be applied to ticket')]
    [Parameter(Mandatory = $false,
      ParameterSetName = 'NoPriorityMatrix')]
    $Tags,

    [Parameter(Mandatory = $false,
      ParameterSetName = 'PriorityMatrix',
      HelpMessage = 'Is FreshService configured with Priority Matrix enabed?')]
    [Parameter(Mandatory = $false,
      ParameterSetName = 'NoPriorityMatrix')]
    [System.Boolean] [Parameter(Mandatory=$false)] $PriorityMatrix = $true
    )

  Begin {
    # Where tickets are created
    $FsTicketEndpoint = "/tickets/"
    $FsTicketUrl = $Uri + $FsTicketEndpoint

    $Method = 'POST'

    # If PriorityMatrix is enabled in FreshService (assumed default), override the Urgency and Impact to get the desired ticket priority
    if ($PriorityMatrix) {
      $Urgency, $Impact = Get-FsMatrixPriority -TicketPriority $TicketPriority
    }

    Write-Debug "Assets: $Assets"
  }

  Process {

    # Build up ticket contents
    $TicketAttributes = @{}
    $TicketAttributes.Add('name', $Requester)
    $TicketAttributes.Add('email', $RequesterEmail)
    $TicketAttributes.Add('subject', $Subject)
    $TicketAttributes.Add('description', $Description)
    $TicketAttributes.Add('priority', [int]$TicketPriority)
    $TicketAttributes.Add('urgency', [int]$Urgency)
    $TicketAttributes.Add('impact', [int]$Impact)
    $TicketAttributes.Add('status', [int]$TicketStatus)
    $TicketAttributes.Add('source', [int]$TicketSource)
    $TicketAttributes.Add('group_id', [int64]$Group)
    $TicketAttributes.Add('department_id', [int64]$DepartmentId)
    $TicketAttributes.Add('category', $Category)

    # Add Custom Fields if supplied
    if ($CustomFields) {
      $TicketAttributes.Add('custom_fields', $CustomFields)
    }

    # Add Tags if supplied
    if ($Tags) {
      $TicketAttributes.Add('tags', $Tags)
    }

    # Add Assets if supplied
    if ($Assets) {
      $TicketAttributes.Add('assets', $Assets)
    }

    # Ticket attributes need to be inside a ticket object, then converted to JSON
    $TicketAttributes = @{'ticket' = $TicketAttributes}
    $Json = ConvertTo-Json -Depth 5 $TicketAttributes

    Write-Debug $Json

    try {
      # Create the ticket
      if ($PSCmdlet.ShouldProcess("Ticket: $Subject", "Create FreshService Ticket?")) {
        $Ticket = Invoke-FreshServiceApiRequest -Uri $FsTicketUrl -Credential $Credential -Method $Method -Body $TicketAttributes
      }
    }
    catch {
      # Error Handling
      $result = $_
      $message = $result.Exception.Message
      $code = $result.Exception.Response.StatusCode.value__

      Write-Debug "Message from Server: $code"

      if ($message -eq "The remote server returned an error: (400) Bad Request.") {
        Write-Output "API call Unsuccessful."
        Write-Error "Error: Unable to communicate to FreshService-API. Please check the service instance is up and healthy."
        throw "ExecutionFailure"
      }
      elseif ($message -eq "The underlying connection was closed: The connection was closed unexpectedly.") {
        Write-Output "Invalid API Key."
        Write-Error "Error: Invalid or Incorrect API key provided."
        throw "ExecutionFailure"
      }
      elseif ($message -eq "Unable to connect to the remote server") {
        Write-Output "Invalid API URL."
        Write-Error "Error: Could not resolve API URL. Invalid or Incorrect API URL."
        throw "ExecutionFailure"
      }
      elseif($message -eq "The remote server returned an error: (401) Unauthorized.") {
        Write-Output "Invalid API Key."
        Write-Error "Error: Invalid or Incorrect API key provided."
        throw "ExecutionFailure"
      }
      else{
        Write-Output $message
        write-error "API Call Unsuccessful."
        throw "ExecutionFailure"
      }
    }

    if ((-not $Ticket ) -or ($Ticket.id))
    {
      Write-Error "An unknown error occurred creating the ticket; Ticket API did not return an ID. API responded: $Ticket"
      Exit 1
    } else {
      Write-Verbose "Created Case Number: $($Ticket.ticket.id) , Title: $($Ticket.ticket.subject) "
    }

    $TicketId = $Ticket.ticket.id

    return $TicketId
  }
}

Export-ModuleMember -Function Get-FsAgent, Invoke-FreshServiceAlertRequest, Get-FsAsset, Get-FsAssetTypes, Get-FsChange, Get-FsChangeFields, Get-FsDepartment, Get-FsDepartmentFields, Invoke-FreshServiceApiRequest, New-FreshServiceConfiguration, Update-FreshServiceConfiguration, Get-FsProblem, Get-FsProblemFields, Get-FsRequester, Get-FsTicket, Get-FsTicketFields, New-FsTicket