Ivanti Heat

Use the Ivanti Heat integration to manage issues and create Cortex XSOAR incidents from Ivanti Heat.

Case Management · Ivanti Heat

Details

IDIvanti Heat
ProviderIvanti
CategoryCase Management
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Ivanti Heat service manager
This integration was integrated and tested with version 2020.1.0.20200313 of Ivanti Heat

Configure Ivanti Heat in Cortex

Parameter Description Required
url Server URL (e.g. https://example.net) True
isFetch Fetch incidents False
incidentType Incident type False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False
token API Token True
fetch_time First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) False
incident_name_field The object field to use for incident name True

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

ivanti-heat-objects-list


Fetches business object records based on the defined filter values.

Base Command

ivanti-heat-objects-list

Input

Argument Name Description Required
rec-id Buisiness object ID Optional
from Start date of object records to return in the following format 2012-01-01T00:00:00Z. Optional
to End date of object records to return in the following format 2012-01-01T00:00:00Z. Optional
limit The maximum number of object records to return Optional
offset Starting record index to begin retrieving object records from Optional
search-query Fetches business object records based on the defined search keyword Optional
object-type Type of object record Required

Context Output

Path Type Description
IvantiHeat.incidents.RecId String Incident ID
IvantiHeat.incidents.Subject String Incident subject
IvantiHeat.incidents.Status String Incident status
IvantiHeat.incidents.CreatedDateTime Date Incident createion time
IvantiHeat.incidents.Symptom String Incident description
IvantiHeat.incidents.OwnerTeam String Incident owner team
IvantiHeat.incidents.IncidentNumber Number The incident number
IvantiHeat.incidents.CreatedBy String The user who created the incident
IvantiHeat.incidents.Owner String Incident owner
IvantiHeat.incidents.Category String Incident category
IvantiHeat.incidents.Priority Number Incident priority
IvantiHeat.incidents.TypeOfIncident String Incident type
IvantiHeat.incidents.ActualCategory String Incident actual category
IvantiHeat.incidents.SocialTextHeader String Incident text header
IvantiHeat.incidents.Email String Incident owner Email

Command Example

!ivanti-heat-objects-list object-type=problems search-query=email from=2011-09-01 to=2012-01-01

Context Example

{
    "IvantiHeat": {
        "problems": {
            "Category": "Accessibility",
            "Category_Valid": "48686D",
            "ClosedBy": null,
            "ClosedDateTime": "2020-07-01T08:42:33Z",
            "Cost": 188,
            "CreatedBy": "johnny cash",
            "CreatedDateTime": "2011-09-18T23:42:38Z",
            "Description": "Email down",
            "Environment": "",
            "Environment_Valid": "",
            "ErrorMessage": "",
            "FirstIncidentLinkedDateTime": null,
            "Impact": "Low",
            "Impact_Valid": "0ABE7B3BC5E9B19B258",
            "IncidentToRootCauseDuration": 277203595,
            "IsInFinalState": false,
            "IsUnRead": false,
            "IsWorkAround": false,
            "KPIAfterIdentification": 0,
            "KPIBeforeIdentification": 0,
            "KPITimeUntilRootCause": 0,
            "KnownErrorDate": "2011-09-18T23:44:37Z",
            "KnownErrorDuration": 0,
            "LastModBy": "Admin",
            "LastModDateTime": "2020-07-01T08:42:33Z",
            "OrganizationalUnit": "",
            "OrganizationalUnit_Valid": "",
            "Owner": "ADale",
            "OwnerEmailAddress": "user@domain.com",
            "OwnerTeam": "Problem Management",
            "OwnerTeam_Valid": "2D8B665640EC10B522A",
            "Owner_Valid": "C19505062E42B83CB",
            "Priority": "4",
            "Priority_Valid": "DD1B90A",
            "ProblemDuration": 0,
            "ProblemLifetime": 0,
            "ProblemNumber": 10053,
            "ReadOnly": false,
            "RecId": "83837179C323966",
            "Resolution": "I cant see my emails",
            "ResolutionAction": "Request for Change",
            "ResolutionAction_Valid": "87D63706706DBE12A1",
            "ResolutionEscLink": "",
            "ResolutionEscLink_Category": "",
            "ResolutionEscLink_RecID": "",
            "ResponseEscalationLink": "",
            "ResponseEscalationLink_Category": "",
            "ResponseEscalationLink_RecID": "",
            "RootCause": "Missing DLL",
            "RootCauseDateTimeCreated": "2011-09-22T23:42:38Z",
            "Service": "",
            "Service_Valid": "",
            "SocialTextHeader": "Problem 103: Email down",
            "Source": "Incident Management",
            "Source_Valid": "A7D73C90FB4AA0D549FF03C36",
            "Status": "Resolved",
            "Status_Valid": "6C9D23834",
            "Subject": "Email down",
            "TargetResolutionTime": null,
            "TotalTimeSpent": 208,
            "TotalWaitingDuration": 0,
            "TypeOfProblem": "Known Error",
            "TypeOfProblem_Valid": "50075354BCAAE1",
            "Urgency": "Medium",
            "Urgency_Valid": "44021B4B3306053AE",
            "WaitingEscLink": "",
            "WaitingEscLink_Category": "",
            "WaitingEscLink_RecID": "",
            "Workaround": ""
        }
    }
}

Human Readable Output

problems results

RecId Subject Status CreatedDateTime Urgency OwnerTeam CreatedBy Owner Category Description Priority ClosedDateTime SocialTextHeader
8383718895204FFBB3EC95E79C323966 Email down Resolved 2011-09-18T23:42:38Z Medium Problem Management johnny cash owner Accessibility Email down 4 2020-07-01T08:42:33Z Problem 103: Email down

ivanti-heat-object-update


Update details of a business object such as a change, problem, or incident.

Base Command

ivanti-heat-object-update

Input

Argument Name Description Required
rec-id Buisiness object ID to update Required
fields Fields values in json format to update in the record, e.g: {“Priority”:5} Required
object-type Type of object record Required

Context Output

Path Type Description
IvantiHeat.incidents.RecId String Incident ID
IvantiHeat.incidents.Subject String Incident subject
IvantiHeat.incidents.Status String Incident status
IvantiHeat.incidents.CreatedDateTime Date Incident createion time
IvantiHeat.incidents.Symptom String Incident description
IvantiHeat.incidents.OwnerTeam String Incident owner team
IvantiHeat.incidents.IncidentNumber Number The incident number
IvantiHeat.incidents.CreatedBy String The user who created the incident
IvantiHeat.incidents.Owner String Incident owner
IvantiHeat.incidents.Category String Incident category
IvantiHeat.incidents.Priority Number Incident priority
IvantiHeat.incidents.TypeOfIncident String Incident type
IvantiHeat.incidents.ActualCategory String Incident actual category
IvantiHeat.incidents.SocialTextHeader String Incident text header
IvantiHeat.incidents.Email String Incident owner Email

Command Example

!ivanti-heat-object-update fields={"Description":desc","Priority":1} object-type=problems rec-id=5874A667

Context Example

{
    "IvantiHeat": {
        "problems": {
            "@odata.context": "https://*ivanti-host*/api/odata/$metadata#problems/$entity",
            "Category": "Applications",
            "Category_Valid": "2E3A3EC0",
            "ClosedBy": null,
            "ClosedDateTime": "2020-07-05T12:28:12Z",
            "Cost": 48,
            "CreatedBy": "Admin",
            "CreatedDateTime": "2012-10-22T23:35:21Z",
            "Description": "desc",
            "Environment": "",
            "Environment_Valid": "",
            "ErrorMessage": "File format not supported",
            "FirstIncidentLinkedDateTime": "2015-06-10T21:26:17Z",
            "Impact": "Medium",
            "Impact_Valid": "1AFFC174C7EA4AB79CCA6B15EB67006D",
            "IncidentToRootCauseDuration": 243003171,
            "IsInFinalState": false,
            "IsUnRead": false,
            "IsWorkAround": false,
            "KPIAfterIdentification": 0,
            "KPIBeforeIdentification": 0,
            "KPITimeUntilRootCause": 0,
            "KnownErrorDate": null,
            "KnownErrorDuration": 0,
            "LastModBy": "Admin",
            "LastModDateTime": "2020-07-05T12:28:12Z",
            "OrganizationalUnit": "",
            "OrganizationalUnit_Valid": "",
            "Owner": "ADale",
            "OwnerEmailAddress": "user@domain.com",
            "OwnerTeam": "Problem Management",
            "OwnerTeam_Valid": "2D8BB522A",
            "Owner_Valid": "C195083CB",
            "Priority": "1",
            "Priority_Valid": "1F2B8",
            "ProblemDuration": 0,
            "ProblemLifetime": 0,
            "ProblemNumber": 106,
            "ReadOnly": false,
            "RecId": "588467A667",
            "Resolution": "File format not supported",
            "ResolutionAction": "Vendor Fix",
            "ResolutionAction_Valid": "443D4AB",
            "ResolutionEscLink": "",
            "ResolutionEscLink_Category": "",
            "ResolutionEscLink_RecID": "",
            "ResponseEscalationLink": "",
            "ResponseEscalationLink_Category": "",
            "ResponseEscalationLink_RecID": "",
            "RootCause": "",
            "RootCauseDateTimeCreated": "2012-11-07T04:35:27Z",
            "Service": "",
            "Service_Valid": "",
            "SocialTextHeader": "Problem 106: Unable to open Office 2007/2010 files",
            "Source": "Incident Management",
            "Source_Valid": "A76",
            "Status": "Resolved",
            "Status_Valid": "6C934",
            "Subject": "Unable to open Office 2007/2010 files",
            "TargetResolutionTime": null,
            "TotalTimeSpent": 377,
            "TotalWaitingDuration": 0,
            "TypeOfProblem": "Problem",
            "TypeOfProblem_Valid": "B14E72CCA804",
            "Urgency": "Medium",
            "Urgency_Valid": "44021B6053AE",
            "WaitingEscLink": "",
            "WaitingEscLink_Category": "",
            "WaitingEscLink_RecID": "",
            "Workaround": ""
        }
    }
}

Human Readable Output

54A667 updated successfully

RecId Subject Status CreatedDateTime Urgency OwnerTeam CreatedBy Owner Category Description Priority ClosedDateTime SocialTextHeader
54A667 Unable to open Office 2007/2010 files Resolved 2012-10-22T23:35:21Z Medium Problem Management Admin johnny cash Applications desc 1 2020-07-05T12:28:12Z Problem 106: Unable to open Office 2007/2010 files

ivanti-heat-object-delete


Delete a business object such as a change, problem, or incident by its Record ID.

Base Command

ivanti-heat-object-delete

Input

Argument Name Description Required
rec-id Buisiness object ID Required
object-type Type of object record Required

Context Output

There is no context output for this command.

Command Example

!ivanti-heat-object-delete object-type=incidents rec-id=490C3

Context Example

{}

Human Readable Output

Record 490C3 deleted successfully

ivanti-heat-object-attachment-download


Get attachments from business objects by attachment ID.

Base Command

ivanti-heat-object-attachment-download

Input

Argument Name Description Required
attachment-id The attachment ID Required

Context Output

There is no context output for this command.

Command Example

!ivanti-heat-object-attachment-download attachment-id=C03CE103827944E59A4EC23498EA9C6A

Context Example

{
    "File": {
        "EntryID": "1807ab951",
        "Extension": "txt",
        "Info": "text/plain; charset=utf-8",
        "MD5": "5d1017c592",
        "Name": "a.txt",
        "SHA1": "aaf4c6182cd9aea9434d",
        "SHA256": "2cf24dba24",
        "SHA512": "9b71d224bd62cf73bcdec043",
        "SSDeep": "3:iKn:p",
        "Size": 5,
        "Type": "ASCII text, with no line terminators"
    }
}

Human Readable Output

ivanti-heat-object-attachment-upload


Upload attachments to business objects.

Base Command

ivanti-heat-object-attachment-upload

Input

Argument Name Description Required
entry-id The entry ID of the file in XSOAR’s context Required
rec-id Buisiness object ID Required
object-type Type of object record Required

Context Output

Path Type Description
IvantiHeat.Attachment.AttachmentId String The ID of the attachment
IvantiHeat.Attachment.FileName String The name of the attachment
IvantiHeat.Attachment.RecId String Attachement buisiness object ID

Command Example

!ivanti-heat-object-attachment-upload object-type=problem rec-id=D14D995B entry-id=18ab951

Context Example

{
    "IvantiHeat": {
        "Attachment": {
            "AttachmentId": "A3039BF750",
            "FileName": "11.jpg",
            "RecId": "D14D995B"
        }
    }
}

Human Readable Output

11.jpg uploaded successfully, attachment ID: A3039BF750

ivanti-heat-object-perform-action


Performs quick actions for a business object. For example, close, clone or resolve an incident or a problem.

Base Command

ivanti-heat-object-perform-action

Input

Argument Name Description Required
object-type Type of object record Required
object-id Buisiness object ID Required
action The action to perform Required
request-data The request body in JSON format Required

Context Output

There is no context output for this command.

Command Example

!ivanti-heat-object-perform-action action=Close_Incident object-id=123ABC object-type=incidents request-data=${ivantiHeat.CloseIncidentJSON}

Human Readable Output

Close_Incident action success

ivanti-heat-object-create


create business objects available out-of-the-box, such as a Change, Problem, Incident, or any custom defined business object of your choice.

Base Command

ivanti-heat-object-create

Input

Argument Name Description Required
object-type Type of object record Required
fields The request body in JSON format, or using script for create the request payload e.g IvantiHeatCloseIncidentExample Required

Context Output

Path Type Description
IvantiHeat.incidents.RecId String Incident ID
IvantiHeat.incidents.Subject String Incident subject
IvantiHeat.incidents.Status String Incident status
IvantiHeat.incidents.CreatedDateTime Date Incident createion time
IvantiHeat.incidents.Symptom String Incident description
IvantiHeat.incidents.OwnerTeam String Incident owner team
IvantiHeat.incidents.IncidentNumber Number The incident number
IvantiHeat.incidents.CreatedBy String The user who created the incident
IvantiHeat.incidents.Owner String Incident owner
IvantiHeat.incidents.Category String Incident category
IvantiHeat.incidents.Priority Number Incident priority
IvantiHeat.incidents.TypeOfIncident String Incident type
IvantiHeat.incidents.ActualCategory String Incident actual category
IvantiHeat.incidents.SocialTextHeader String Incident text header
IvantiHeat.incidents.Email String Incident owner Email

Command Example

!ivanti-heat-object-create object-type=incidents fields=`{"Category":"Connectivity","ProfileLink":"1087E597","Symptom":"the description","Subject":"test incident","Service":"Desktop Service","Owner":"johnny cash"}`

Context Example

{
    "IvantiHeat": {
        "incidents": {
            "@odata.context": "https://*ivanti-host*/api/odata/$metadata#incidents/$entity",
            "ActualCategory": "Connectivity",
            "ActualCategory_Valid": null,
            "ActualService": "Desktop Service",
            "ActualService_Valid": "164A9DCC1A",
            "AlternateContactEmail": null,
            "AlternateContactLink": null,
            "AlternateContactLink_Category": null,
            "AlternateContactLink_RecID": null,
            "AlternateContactPhone": null,
            "ApprovalStatus": null,
            "Approver": "user@domain.com",
            "Approver_Valid": "2F8539C7",
            "Category": "Connectivity",
            "Category_Valid": "6013B2C7",
            "CauseCode": null,
            "CauseCode_Valid": null,
            "ClosedBy": null,
            "ClosedDateTime": null,
            "ClosedDuration": 0,
            "ClosingEscLink": null,
            "ClosingEscLink_Category": null,
            "ClosingEscLink_RecID": null,
            "Cost": "0.0000",
            "CostPerMinute": "0.4000",
            "CostPerMinute_Currency": "USD",
            "CostPerMinute_CurrencyValid": null,
            "Cost_Currency": "USD",
            "Cost_CurrencyValid": null,
            "CreatedBy": "Admin",
            "CreatedByType": "Web Client",
            "CreatedDateTime": "2020-07-05T12:28:16Z",
            "CustomerDepartment": "IT",
            "CustomerLocation": "USA",
            "CustomerLocation_Valid": "A4DD2BCF246A",
            "Email": "user@domain.com",
            "EntityLink": "4A051AD111",
            "EntityLink_Category": "OrganizationalUnit",
            "EntityLink_RecID": "4A05123D6AD111",
            "EventCIRecId": null,
            "FirstCallResolution": false,
            "HRCaseLink": null,
            "HRCaseLink_Category": null,
            "HRCaseLink_RecID": null,
            "HoursOfOperation": "Weekly HOP",
            "HoursOfOperation_Valid": "FF57246B2E0047D193C1AEC1011D746B",
            "Impact": null,
            "Impact_Valid": null,
            "IncidentDetailSummary": null,
            "IncidentDetailWorkflowTag": null,
            "IncidentNetworkUserName": "jcash",
            "IncidentNumber": 112,
            "IsApprovalNeeded": false,
            "IsDSMTaskExisted": false,
            "IsInFinalState": false,
            "IsMasterIncident": false,
            "IsNewRecord": false,
            "IsNotification": true,
            "IsReclassifiedForResolution": false,
            "IsRelatedIncidentResolutionUpdate": false,
            "IsRelatedIncidentUpdate": false,
            "IsReportedByAlternateContact": false,
            "IsResolvedByMaster": false,
            "IsUnRead": false,
            "IsVIP": false,
            "IsWorkAround": false,
            "KnowledgeLink": null,
            "KnowledgeLink_Category": null,
            "KnowledgeLink_RecID": null,
            "LastModBy": "Admin",
            "LastModDateTime": "2020-07-05T12:28:16Z",
            "LoginId": "ATaylor",
            "MasterIncidentLink": null,
            "MasterIncidentLink_Category": null,
            "MasterIncidentLink_RecID": null,
            "NewNotes": null,
            "OrgUnitLink": "4A0AD111",
            "OrgUnitLink_Category": "OrganizationalUnit",
            "OrgUnitLink_RecID": "4A05123D611",
            "OrganizationUnitID": "GMI",
            "Owner": "ATaylor",
            "OwnerEmail": "user@domain.com",
            "OwnerTeam": "Service Desk",
            "OwnerTeamEmail": "user@domain.com",
            "OwnerTeam_Valid": "2E4BABF0D9B80C47",
            "OwnerType": "Employee",
            "Owner_Valid": "108452E597",
            "OwnershipAssignmentEmail": "user@domain.com",
            "OwningOrgUnitId": "GMI",
            "OwningOrgUnitId_Valid": "4A0AD111",
            "Phone": "+1 22.33.44",
            "PreviousState": null,
            "Priority": "3",
            "Priority_Valid": "29CD5096",
            "ProblemLink": null,
            "ProblemLink_Category": null,
            "ProblemLink_RecID": null,
            "ProfileFullName": "johnny cash",
            "ProfileLink": "108734597",
            "ProfileLink_Category": "Employee",
            "ProfileLink_RecID": "10873D64B452E597",
            "ProgressBarPosition": "3",
            "ReadOnly": false,
            "RecId": "FDF098DE",
            "ReportedBy": null,
            "ReportingOrgUnitID": null,
            "ReportingOrgUnitID_Valid": null,
            "Resolution": null,
            "ResolutionEscLink": null,
            "ResolutionEscLink_Category": null,
            "ResolutionEscLink_RecID": null,
            "ResolvedBy": null,
            "ResolvedByIncidentNumber": 0,
            "ResolvedByType": null,
            "ResolvedDateTime": null,
            "RespondedBy": null,
            "RespondedDateTime": null,
            "ResponseEscLink": null,
            "ResponseEscLink_Category": null,
            "ResponseEscLink_RecID": null,
            "SLA": "",
            "SLADisplayText": "",
            "SLALink": null,
            "SLALink_Category": null,
            "SLALink_RecID": null,
            "SendSurveyNotification": false,
            "Service": "Desktop Service",
            "ServiceOwnerEmail": null,
            "ServiceReqLink": null,
            "ServiceReqLink_Category": null,
            "ServiceReqLink_RecID": null,
            "Service_Valid": "164A2AC1A",
            "SocialTextHeader": "Incident 11152: test incident",
            "Source": "Phone",
            "Source_Valid": "EF78045C",
            "Status": "Active",
            "Status_Valid": "EAB221AA",
            "Subcategory": null,
            "Subcategory_Valid": null,
            "Subject": "test incident",
            "Symptom": "the description",
            "TeamManagerEmail": "user@domain.com",
            "TotalTimeSpent": 0,
            "TypeOfIncident": "Failure",
            "Urgency": null,
            "Urgency_Valid": null,
            "ViewType": null,
            "WaitingEscLink": null,
            "WaitingEscLink_Category": null,
            "WaitingEscLink_RecID": null,
            "helpdesk_Priority": null,
            "helpdesk_Priority_Valid": null
        }
    }
}

Human Readable Output

incidents object created successfully

RecId Subject Status CreatedDateTime Symptom OwnerTeam IncidentNumber CreatedBy Owner Category Priority Email TypeOfIncident ActualCategory SocialTextHeader
FDF08798DE test incident Active 2020-07-05T12:28:16Z the description Service Desk 152 Admin jcash Connectivity 3 user@domain.com Failure Connectivity Incident 112: test incident

Configuration parameters

  • url — Server URL (e.g. https://example.net) (required)
  • token — API Token
  • token_creds
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • incident_name_field — The object field to use for incident name (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (7)

  • ivanti-heat-object-attachment-download

    Gets attachments from business objects by attachment ID.

  • ivanti-heat-object-attachment-upload

    Uploads attachments to business objects.

  • ivanti-heat-object-create

    Creates business objects that are available out-of-the-box, such as a Change, Problem, Incident, or any custom defined business object.

  • ivanti-heat-object-delete

    Deletes a business object by record ID, such as a change, problem, or incident.

  • ivanti-heat-object-perform-action

    Performs quick actions on a business object. For example, close, clone, or resolve an incident or a problem.

  • ivanti-heat-object-update

    Updates details of a business object, such as a change, problem, or incident.

  • ivanti-heat-objects-list

    Fetches business object records based on the defined filter values.

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

""" IMPORTS """

import json

import dateparser
import urllib3

urllib3.disable_warnings()

""" CONSTANTS """
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
OBJECT_HEADERS = [
    "RecId",
    "Subject",
    "Name",
    "Status",
    "CreatedDateTime",
    "Symptom",
    "Urgency",
    "OwnerTeam",
    "IncidentNumber",
    "CreatedBy",
    "Details",
    "Owner",
    "Category",
    "Description",
    "Priority",
    "Email",
    "TypeOfIncident",
    "ClosedDateTime",
    "ActualCategory",
    "SocialTextHeader",
]


class Client(BaseClient):
    """
    Client will implement the service API, and should not contain any Demisto logic.
    Should only do requests and return data.
    """

    def do_request(self, method, url_suffix, params=None, data=None, json_data=None, resp_type="json", files=None):
        res = self._http_request(
            method,
            url_suffix,
            params=params,
            data=data,
            json_data=json_data,
            files=files,
            resp_type=resp_type,
            ok_codes=(200, 201, 204),
            return_empty_response=True,
        )

        if isinstance(res, dict | list) or resp_type == "other":
            return res
        return {}


def test_module(client):
    first_fetch_time = demisto.params().get("fetch_time", "3 days").strip()
    if demisto.params().get("isFetch", False):
        # Validate fetch_time parameter is valid (if not, parse_date_range will raise the error message)
        parse_date_range(first_fetch_time, "%Y-%m-%d %H:%M:%S")

    client.do_request("GET", "odata/businessobject/incidents")
    return "ok"


def generate_filter_param(rec_id, from_date, to_date):
    if rec_id:
        return f"recId eq '{rec_id}'"

    if not from_date and not to_date:
        return ""
    if from_date and not to_date:
        return f"CreatedDateTime gt {from_date}"
    if to_date and not from_date:
        return f"CreatedDateTime lt {to_date}"
    return f"CreatedDateTime gt {from_date} and CreatedDateTime lt {to_date}"


@logger
def objects_list_command(client, args):
    object_type = args.get("object-type")
    rec_id = args.get("rec-id")
    from_date = args.get("from")
    to_date = args.get("to")
    limit = args.get("limit")
    offset = args.get("offset")
    query = args.get("search-query")

    params = {"$orderby": "CreatedDateTime desc"}
    filter_txt = generate_filter_param(rec_id, from_date, to_date)

    if filter_txt:
        params["$filter"] = filter_txt
    if limit:
        params["$top"] = limit
    if offset:
        params["$skip"] = offset
    if query:
        params["$search"] = query

    raw_res = client.do_request("GET", f"odata/businessobject/{object_type}", params=params)
    if raw_res.get("value"):
        human_readable = tableToMarkdown(
            f"{object_type} results", t=raw_res.get("value"), headers=OBJECT_HEADERS, removeNull=True
        )

        entry_context = {f"IvantiHeat.{object_type}(val.RecId && val.RecId===obj.RecId)": raw_res.get("value")}
        return human_readable, entry_context, raw_res

    return "No records found", {}, raw_res


@logger
def update_object_command(client, args):
    object_type = args.get("object-type")
    rec_id = args.get("rec-id")
    fields = args.get("fields")

    try:
        fields = json.loads(fields)
    except Exception:
        raise Exception(f"Failed to parse fields as JSON data, received object:\n{fields}")

    raw_res = client.do_request("PUT", f"odata/businessobject/{object_type}('{rec_id}')", json_data=fields)

    human_readable = tableToMarkdown(f"{rec_id} updated successfully", t=raw_res, headers=OBJECT_HEADERS, removeNull=True)
    entry_context = {f"IvantiHeat.{object_type}(val.RecId===obj.RecId)": raw_res}
    return human_readable, entry_context, raw_res


@logger
def delete_object_command(client, args):
    object_type = args.get("object-type")
    rec_id = args.get("rec-id")
    raw_res = client.do_request("DELETE", f"odata/businessobject/{object_type}('{rec_id}')")
    return f"Record {rec_id} deleted successfully", {}, raw_res


@logger
def get_attachment_command(client, args):
    attachment_id = args.get("attachment-id")
    raw_res = client.do_request("Get", f"rest/Attachment?ID={attachment_id}", resp_type="other")

    filename_header = raw_res.headers.get("content-disposition")

    f_attr = "filename="
    if filename_header and f_attr in filename_header:  # noqa: RET503
        filename = filename_header[filename_header.index(f_attr) + len(f_attr) :]
        return fileResult(filename, raw_res.content)


@logger
def upload_attachment_command(client, args):
    object_type = args.get("object-type")
    entry_id = args.get("entry-id")
    rec_id = args.get("rec-id")

    # Validate all required arguments are present and non-empty
    for arg_name, arg_value in (("object-type", object_type), ("entry-id", entry_id), ("rec-id", rec_id)):
        if arg_value is None or (isinstance(arg_value, str) and not arg_value.strip()):
            raise ValueError(
                f'Required argument "{arg_name}" is missing or empty. '
                "Verify the calling automation provides all of: object-type, entry-id, rec-id."
            )

    body = {"ObjectID": rec_id, "ObjectType": f"{object_type}#"}

    demisto.debug(f"[debug] resolving file for {entry_id=}")
    try:
        file_tuple = get_file(entry_id)
    except Exception as exc:
        raise DemistoException(
            f'Failed to resolve attachment file for entry-id="{entry_id}". '
            "Verify the entry ID points to an existing file in the war room. "
            f"Underlying error: {exc}"
        ) from exc

    raw_res = client.do_request("POST", "rest/Attachment", data=body, files={"file": file_tuple})
    if raw_res:
        attachment = raw_res[0]
        attachment_id = attachment.get("Message")
        file_name = attachment.get("FileName")
        entry_context = {"IvantiHeat.Attachment": {"RecId": rec_id, "AttachmentId": attachment_id, "FileName": file_name}}
        return f"{file_name} uploaded successfully, attachment ID: {attachment_id}", entry_context, raw_res
    raise Exception(f"Upload attachment {rec_id} failed")


@logger
def perform_action_command(client, args):
    object_type = args.get("object-type")
    object_id = args.get("object-id")
    action = args.get("action")
    request_data = args.get("request-data")

    try:
        request_data = json.loads(request_data)
    except Exception:
        raise Exception(f"Failed to parse request-data as JSON data, received object:\n{request_data}")

    raw_res = client.do_request(
        "POST", f"odata/businessobject/{object_type}('{object_id}')/{action}", json_data=request_data, resp_type="other"
    )

    return f"{action} action success", {}, raw_res.content


@logger
def create_object_command(client, args):
    object_type = args.get("object-type")
    fields = args.get("fields")

    body = {}

    try:
        fields = json.loads(fields)
    except Exception:
        raise Exception(f"Failed to parse additional-fields as JSON data, received object:\n{fields}")

    for key in fields:
        body[key] = fields[key]

    raw_res = client.do_request("POST", f"odata/businessobject/{object_type}", json_data=body)

    human_readable = tableToMarkdown(
        f"{object_type} object created successfully", t=raw_res, headers=OBJECT_HEADERS, removeNull=True
    )

    entry_context = {f"IvantiHeat.{object_type}(val.RecId===obj.RecId)": raw_res}
    return human_readable, entry_context, raw_res


def get_file(entry_id):
    get_file_path_res = demisto.getFilePath(entry_id)
    file_path = get_file_path_res["path"]
    file_name = get_file_path_res["name"]
    with open(file_path, "rb") as fopen:
        file_bytes = fopen.read()
    return file_name, file_bytes


@logger
def fetch_incidents(client, last_run, first_fetch_time, name_field):
    # Get the last fetch time, if exists
    last_fetch = last_run.get("last_fetch")

    # Handle first time fetch
    if last_fetch is None:
        last_fetch = dateparser.parse(first_fetch_time)
    else:
        last_fetch = dateparser.parse(last_fetch)

    incidents = []

    params = {"$orderby": "CreatedDateTime asc"}
    params["$filter"] = f"CreatedDateTime gt {last_fetch.strftime(DATE_FORMAT)}"
    raw_res = client.do_request("GET", "odata/businessobject/incidents", params=params)
    for item in raw_res.get("value", []):
        incident_created_time = dateparser.parse(item.get("CreatedDateTime"))
        assert incident_created_time is not None
        incident_name = item.get(name_field, item.get("RecId"))
        incident = {
            "name": incident_name,
            "details": json.dumps(item),
            "occurred": incident_created_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            "rawJSON": json.dumps(item),
        }
        incidents.append(incident)
        last_fetch = incident_created_time

    next_run = {"last_fetch": last_fetch.strftime(DATE_FORMAT)}
    return next_run, incidents


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """

    # get the service API url
    base_url = urljoin(demisto.params()["url"], "/api")
    token = demisto.params().get("token_creds", {}).get("password") or demisto.params().get("token")
    verify_certificate = not demisto.params().get("insecure", False)

    # How much time before the first fetch to retrieve incidents
    first_fetch_time = demisto.params().get("fetch_time", "3 days").strip()

    proxy = demisto.params().get("proxy", False)

    commands = {
        "ivanti-heat-objects-list": objects_list_command,
        "ivanti-heat-object-update": update_object_command,
        "ivanti-heat-object-delete": delete_object_command,
        "ivanti-heat-object-attachment-upload": upload_attachment_command,
        "ivanti-heat-object-create": create_object_command,
        "ivanti-heat-object-perform-action": perform_action_command,
    }

    LOG(f"Command being called is {demisto.command()}")
    try:
        client = Client(
            base_url=base_url, verify=verify_certificate, headers={"Authorization": f"rest_api_key={token}"}, proxy=proxy
        )

        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(client)
            demisto.results(result)
        elif demisto.command() == "ivanti-heat-object-attachment-download":
            demisto.results(get_attachment_command(client, demisto.args()))
        elif demisto.command() == "fetch-incidents":
            # Set and define the fetch incidents command to run after activated via integration settings.
            next_run, incidents = fetch_incidents(
                client=client,
                last_run=demisto.getLastRun(),
                first_fetch_time=first_fetch_time,
                name_field=demisto.params().get("incident_name_field"),
            )

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)
        else:
            hr, outputs, raw = commands[demisto.command()](client, demisto.args())
            return_outputs(hr, outputs, raw)

    # Log exceptions
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()