Absolute

Absolute is an adaptive endpoint security solution that delivers device security, data security, and asset management of endpoints.

Endpoint · Absolute

Details

IDAbsolute
ProviderAbsolute Software
CategoryEndpoint
From Version6.0.0
Docker Imagedemisto/auth-utils:1.0.0.10133006
Supported ModulesAgentix XSIAM

README

Absolute is an adaptive endpoint security solution that delivers device security, data security, and asset management of endpoints.
This integration was integrated and tested with the API version 1.7 of Absolute.

Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.

Configure Absolute in Cortex

Parameter Description Required
Your Absolute server URL   True
Token ID Token ID and Secret Key. True
Secret Key   True
Trust any certificate (not secure)   False
Use system proxy settings   False
Events Fetch Interval Only in XSIAM False
Max number of events per fetch Only in XSIAM False
Fetch Events Only in XSIAM False

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.

absolute-custom-device-field-list


Returns a list of custom device fields associated with the given device_id, based on the authorization token.

Base Command

absolute-custom-device-field-list

Input

Argument Name Description Required
device_id The system unique identifier of the device. Required
limit Maximum number of results to return. Default is 50. Optional
all_results Whether to retrieve all results or not. Possible values are: true, false. Optional

Context Output

Path Type Description
Absolute.CustomDeviceField.DeviceUID String The system-defined unique identifier of the device.
Absolute.CustomDeviceField.CDFValues.CDFUID String The unique identifier of the custom device field.
Absolute.CustomDeviceField.CDFValues.FieldKey String The unique identifier of the custom device field in the classic version of Absolute.
Absolute.CustomDeviceField.CDFValues.FieldName String The name assigned to the custom device field.
Absolute.CustomDeviceField.CDFValues.CategoryCode String The type of custom device field. Possible values are: PREDEFINED, ESNCOLUMN, UDF.
Absolute.CustomDeviceField.CDFValues.FieldValue String The current value of the custom device field.
Absolute.CustomDeviceField.CDFValues.Type String The data type of the field value. Possible values are: Text, Date, Dropdown.

Command example

!absolute-custom-device-field-list device_id=1234

Context Example

{
    "Absolute": {
        "CustomDeviceField": {
            "CDFValues": [
                {
                    "CDFUID": "4m9fUCZqTYec1bJgDSNg",
                    "CategoryCode": "ESNCOLUMN",
                    "FieldKey": 1,
                    "FieldName": "Asset Number",
                    "FieldValue": "aa",
                    "Type": "Text"
                },
                {
                    "CDFUID": "2iS3ryiSvSDsksJ289vtQ",
                    "CategoryCode": "UDF",
                    "FieldKey": 30,
                    "FieldName": "Custom2",
                    "FieldValue": "TPB",
                    "Type": "Text"
                }
            ],
            "DeviceUID": "1234"
        }
    }
}

Human Readable Output

Absolute Custom device field list

CDF ID Field Value Filed Name
4m9fUCZqTYec1bJgDSNg Asset Number Asset Number
2iS3ryiSvSDsksJ289vtQ Custom2 Custom2

absolute-custom-device-field-update


Updates the value of the included custom device fields for the given device_id.

Base Command

absolute-custom-device-field-update

Input

Argument Name Description Required
device_id The system unique identifier of the device. Required
cdf_uid The unique identifier of the custom device field. Note: In order to get this value, use the “absolute-custom-device-field-list” command. Required
value The new value of the custom device field to be set. Required

Context Output

There is no context output for this command.

Command example

!absolute-custom-device-field-update device_id=1234 cdf_uid=4m9fUCZqTYec1bJgDSNg value="test2"

Human Readable Output

Device 1234 with value test2 was updated successfully.

absolute-device-freeze-request


Creates a new Freeze request for the devices specified in the device_ids argument.

Base Command

absolute-device-freeze-request

Input

Argument Name Description Required
device_ids A comma-separated list of the unique identifiers of devices included in the request. The recommendation is to use up to 10,000 devices per request. Required
request_name The user-defined name for the Freeze request. The name should be a non-unique string and has 1-250 characters. Required
html_message The user-defined, HTML coded message shown on devices when the Freeze is applied. The message should be in a non-unique HTML format and has 1-4000 characters. Required
message_name The user-defined name for the Freeze message. Required
device_freeze_type The type of Freeze. You cannot freeze a device that has been reported Stolen in the Absolute console.
- OnDemand: Freezes a device on its next connection to the Absolute Monitoring Center, which is typically within 15 minutes. This applies for all supported operating systems.
- Scheduled: Freezes a device on its next connection to the Absolute Monitoring Center on or after a specified date and time. This applies to Windows and Mac devices. The scheduled freeze date is specified in the scheduled_freeze_date argument. Scheduled Freeze requests are only supported on Windows and Mac devices with an active Absolute agent that is regularly connecting to the Absolute Monitoring Center.
- Offline: Freezes a device if it has been offline for a specified period of time. Applies to Windows and Mac devices. Offline period is specified in the offline_time_seconds arguments. Offline freeze is not available if your Absolute account has been migrated to Offline Freeze Rules. For more information, see the console Help.
Possible values are: OnDemand, Scheduled, Offline.
Required
scheduled_freeze_date The date and time (in UTC) when the device should be frozen in ISO 8601 format: YYYY-MM-DDThh:mm:ss.SSSZ. Required if device_freeze_type is Scheduled. For example, 2022-01-01T00:00:00.000Z. Optional
offline_time_seconds The length of time (in seconds) that a device can be offline before the device is frozen. Required if device_freeze_type is Offline. Must be between 1200 seconds (20 minutes) and 172800000 seconds (2000 days). Default value is 30 days. Default is 22592000. Optional
passcode_type The type of passcode to unfreeze a device.
- UserDefined: Manually set the passcode in passcode. You must specify the passcode argument.
- RandomForEach: A unique passcode is randomly generated for each device. You must specify the passcode_length argument.
- RandomForAll: A passcode is randomly generated and is the same for all devices. You must specify the passcode_length argument. Possible values are: UserDefined, RandomForEach, RandomForAll.
Required
passcode The passcode used to unfreeze the devices. Required if passcode_type is UserDefined. A valid passcode is a number that has 4-8 characters. For example, 12345678. Optional
passcode_length The length of the passcode when it is randomly generated. Required if passcode_type is RandomForEach or RandomForAll. A valid passcode is a number from 4-8. For example, 8. Optional
notification_emails A comma-separated list of user-entered email addresses that will receive an email notification when the status of the Freeze request changes. The API supports up to 10 email addresses. Optional

Context Output

Path Type Description
Absolute.FreezeRequest.RequestUID String The system-defined, unique identifier of the Freeze request.
Absolute.FreezeRequest.SucceededDeviceUIDs Unknown An array of the unique devices identifiers that succeeded in creating a Freeze request.
Absolute.FreezeRequest.Errors.detail.deviceUids Unknown An array of the unique identifiers of devices for the Freeze request error.
Absolute.FreezeRequest.Errors.message String The reason for the Freeze failure.
Absolute.FreezeRequest.Errors.messageKey String The reference key for the error message.

Command example

!absolute-device-freeze-request device_ids=123456 device_freeze_type=Scheduled html_message="test" message_name="new name" request_name="name1" scheduled_freeze_date=2022-04-03T13:30:00.000Z passcode_type=RandomForEach passcode_length=5

Context Example

{
    "Absolute": {
        "FreezeRequest": {
            "RequestUID": "2b62b290-d590-4237-8ba0-57e4779b9f1c",
            "SucceededDeviceUIDs": [
                "123456"
            ]
        }
    }
}

Human Readable Output

Absolute device freeze requests results

RequestUID SucceededDeviceUIDs
2b62b290-d590-4237-8ba0-57e4779b9f1c 123456

absolute-device-remove-freeze-request


Creates a new Remove Freeze request for one or more devices, regardless of their Freeze status. You can submit Remove Freeze requests to perform the following actions: unfreeze frozen devices, remove newly submitted Freeze requests, or remove outstanding Scheduled and Offline Freeze requests. In case of removing from offline: In addition to creating a Remove Freeze request for devices with a status of Frozen by Condition: Offline, removes Offline Freeze requests from devices with a status of Freeze Requested or Freeze Condition - Offline Set. In case of not removing from offline: a Remove Freeze request is created for the devices but the Conditional - Offline Freeze request is not deleted (deprecated).

Base Command

absolute-device-remove-freeze-request

Input

Argument Name Description Required
device_ids A comma-separated list of the unique identifiers of devices included in the request. The recommendation is to use up to 10,000 devices per request. Required
remove_scheduled Whether to remove only a Scheduled Freeze request. Note: When setting to true, if the Freeze request is a Scheduled Freeze request, the Freeze request is removed. Otherwise, when setting to false, if the Freeze request is not a Scheduled Freeze request, the Freeze request is not removed. Possible values are: true, false. Optional
remove_offline Whether to remove only an Offline Freeze request. Note: When setting to true, if the Freeze request is an Offline Freeze request, the Freeze request is removed. Otherwise, when setting to false, if the Freeze request is not a Offline Freeze request, the Freeze request is not removed. Possible values are: true, false. Optional

Context Output

There is no context output for this command.

Command example

!absolute-device-remove-freeze-request device_ids=123456 remove_scheduled=true

Human Readable Output

Successfully removed freeze request for devices ids: 123456.

absolute-device-freeze-request-get


Gets detailed information about the Freeze request specified by request_uid.

Base Command

absolute-device-freeze-request-get

Input

Argument Name Description Required
request_uid The system-defined, unique identifier of the Freeze request. Required

Context Output

Path Type Description
Absolute.FreezeRequestDetail.ID String The system-defined, unique identifier of the Freeze request.
Absolute.FreezeRequestDetail.AccountUid String The system-defined, unique identifier associated with this Absolute account.
Absolute.FreezeRequestDetail.ActionRequestUid String The system-defined, unique identifier of the Freeze request (the same as ID).
Absolute.FreezeRequestDetail.DeviceUid String The system-defined, unique identifier of the device.
Absolute.FreezeRequestDetail.Statuses.actionUid String The system-defined, unique identifier of the Freeze action.
Absolute.FreezeRequestDetail.Statuses.messageKey String The reference key for the error message or the info message. Error messages start with ‘dds’. Info messages start with ‘ddsui’.
Absolute.FreezeRequestDetail.Statuses.messageParams Unknown A list of strings describing the error message when the status is LaunchFailed. If the status isn’t LaunchFailed, messageParams is empty.
Absolute.FreezeRequestDetail.Statuses.message String The message for the status change that occurred.
Absolute.FreezeRequestDetail.Statuses.updatedBy String The last entity to update the Freeze request.
Absolute.FreezeRequestDetail.Statuses.updatedDateTimeUtc Date The time (in Unix epoch) when the Freeze request was last updated.
Absolute.FreezeRequestDetail.Statuses.triggerActionUid String The system-defined, unique identifier of a new Freeze request that replaces another Freeze request of the same type.
Absolute.FreezeRequestDetail.Statuses.eventType String Device freeze type event.
Absolute.FreezeRequestDetail.Statuses.ackClientTS Number The acknowledgment timestamp (in UNIX epoch local time) when the request was downloaded on the device.
Absolute.FreezeRequestDetail.Statuses.ackClientDateTimeUtc Number The acknowledgment timestamp (in UNIX epoch UTC) when the request was downloaded to the device.
Absolute.FreezeRequestDetail.Statuses.instruction String All action instructions which are sent from the device DFZ agent component.
Absolute.FreezeRequestDetail.Statuses.scheduledFreezeDateTimeUtc Number The date and time (in Unix epoch) when a Scheduled Freeze request was scheduled to be performed.
Absolute.FreezeRequestDetail.Configuration.messageName String The user-defined name for the Freeze message.
Absolute.FreezeRequestDetail.Configuration.htmlClear String The user-defined, HTML coded message shown on the device when the Freeze is applied (the same as Configuration.freezeMessage except it contains the HTML tags).
Absolute.FreezeRequestDetail.Configuration.passcodeClear String The passcode that can be used to unfreeze the device.
Absolute.FreezeRequestDetail.Configuration.passcodeOption String The type of passcode to unfreeze a device.
Absolute.FreezeRequestDetail.Configuration.freezeMessage String The content of the Freeze message without the HTML tags.
Absolute.FreezeRequestDetail.Configuration.freezeId String The user-friendly identifier of the request that is displayed in the event history in the Absolute console (same as EventHistoryId).
Absolute.FreezeRequestDetail.Configuration.configurationUid String The system-defined unique identifier assigned to the Freeze configuration.
Absolute.FreezeRequestDetail.Configuration.action String The type of action being performed on the device.
Absolute.FreezeRequestDetail.Configuration.type String The type of Freeze.
Absolute.FreezeRequestDetail.Configuration.passcodeLength Number The length of the device unfreeze passcode when it is randomly generated.
Absolute.FreezeRequestDetail.Configuration.passcodeSalt String The salt used for hashing the passcode before the passcode is sent to the device.
Absolute.FreezeRequestDetail.Configuration.passcodeHashed String The hashed value of the passcode.
Absolute.FreezeRequestDetail.Configuration.html String The encoded value of Configuration.htmlClear.
Absolute.FreezeRequestDetail.Configuration.disableRemoteLogin Boolean Whether remote login is disabled on the device.
Absolute.FreezeRequestDetail.Configuration.disableFileSharing Boolean Whether file sharing is disabled on the device.
Absolute.FreezeRequestDetail.Configuration.Conditions.secondsUntilFreeze Number The amount of time (in seconds) a device can be offline before the device is frozen.
Absolute.FreezeRequestDetail.Configuration.Conditions.scheduledFreezeDate Date The date and time (in UTC) when a Scheduled Freeze request is scheduled to be performed.
Absolute.FreezeRequestDetail.Configuration.issuedDateTimeUTC Date The date and time (in UNIX epoch) when the Freeze request was created.
Absolute.FreezeRequestDetail.Configuration.preLoginEnabled Boolean Whether pre-login is enabled on the device.
Absolute.FreezeRequestDetail.Configuration.serviceControlList String List of service controls that the server sends to the device.
Absolute.FreezeRequestDetail.Name String The user-defined name for the Freeze request.
Absolute.FreezeRequestDetail.Requester String The user ID of the entity that created the Freeze request.
Absolute.FreezeRequestDetail.CreatedUTC Date The date and time (in UNIX epoch) when the Freeze request was created.
Absolute.FreezeRequestDetail.ChangedUTC Date The date and time (in UNIX epoch) when the Freeze request was last modified.
Absolute.FreezeRequestDetail.NotificationEmails Unknown An array of user-entered email addresses that will receive an email notification when the status of the Freeze request changes. Supports up to 10 email addresses.
Absolute.FreezeRequestDetail.EventHistoryId String The user-friendly identifier of the request that is displayed in the event history in the Absolute console (same as freezeId).
Absolute.FreezeRequestDetail.PolicyGroupUid String The system-defined unique identifier of the policy group that the device belongs to.
Absolute.FreezeRequestDetail.PolicyConfigurationVersion Number The version of the configuration for the policy.
Absolute.FreezeRequestDetail.FreezePolicyUid String The unique identifier of the Freeze policy.
Absolute.FreezeRequestDetail.Downloaded Boolean Whether the Freeze request has been downloaded to the device.
Absolute.FreezeRequestDetail.IsCurrent Boolean Internal flag.

Command example

!absolute-device-freeze-request-get request_uid=c638c2dc-1dd1-4cfa-8708-46f368012398

Context Example

{
    "Absolute": {
        "FreezeRequestDetail": {
            "AccountUid": "accountID",
            "ActionRequestUid": "c638c2dc-1dd1-4cfa-8708-46f368012398",
            "ChangedBy": null,
            "ChangedUTC": "2022-03-29T10:30:55.462+00:00",
            "Configuration": {
                "action": "DFZ",
                "conditions": [
                    {
                        "scheduledFreezeDate": "2022-03-29T10:30:22.000+00:00"
                    }
                ],
                "configurationUid": "c61b0cb7-3846-4d1e-9e78-641084b7747a",
                "disableFileSharing": true,
                "disableRemoteLogin": true,
                "forceReboot": false,
                "freezeId": "DeviceFreeze-0010",
                "freezeMessage": "test",
                "htmlClear": "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body><p style=\"font-size: small\">DeviceFreeze-0010</p><hr>test</body></html>",
                "issuedUTC": "2022-03-29T10:30:24.268+00:00",
                "messageName": "new name",
                "passcodeClear": "26148",
                "passcodeHashed": "c5pr+oaojn37SFZJ3uokRe0Uy/+kAhzPNv6TyrCzXOdL1vu/KyoFF7T6rQfLK6ej2jYWXPGRzxWCfrS9f/S8JA==",
                "passcodeLength": 5,
                "passcodeOption": "RandomForEach",
                "passcodeSalt": "/4VA8uE3DUv04mWy9iGkZp5rL3zDwtEP/YsqAvL190VIn9bhPZUzYXozoSSEPro0tVSVMtG9Rfssqpy2yvsm6g==",
                "preLoginEnabled": true,
                "type": "Scheduled"
            },
            "Content": null,
            "CreatedBy": null,
            "CreatedUTC": "2022-03-29T10:30:24.279+00:00",
            "DeviceUid": "123456",
            "Downloaded": false,
            "EventHistoryId": "DeviceFreeze-0010",
            "FreezePolicyUid": null,
            "ID": "fa72b6ed-62f4-40bd-b581-ef5c114efb8e",
            "IsCurrent": false,
            "Name": "name1",
            "NotificationEmails": [],
            "PolicyConfigurationVersion": 0,
            "PolicyGroupUid": null,
            "Requester": "example@test.com",
            "RequesterUid": "778f8cce-8cc6-4de1-b025-e0538f97e072",
            "Statuses": [
                {
                    "ackClientTS": 0,
                    "ackClientUTC": 1,
                    "eventType": "Remove",
                    "scheduledFreezeDateUTC": 0,
                    "status": "Removed",
                    "statusUid": "9e413f56-8b2d-4605-9527-536427b9ad02",
                    "updatedBy": "example@test.com",
                    "updatedUTC": "2022-03-29T10:30:55.462+00:00"
                },
                {
                    "ackClientTS": 0,
                    "ackClientUTC": 0,
                    "scheduledFreezeDateUTC": 0,
                    "status": "FreezeRequested",
                    "updatedBy": "example@test.com",
                    "updatedUTC": "2022-03-29T10:30:24.268+00:00"
                }
            ]
        }
    }
}

Human Readable Output

Absolute Freeze request details for: c638c2dc-1dd1-4cfa-8708-46f368012398

ID Name AccountUid ActionRequestUid EventHistoryId CreatedUTC ChangedUTC Requester
fa72b6ed-62f4-40bd-b581-ef5c114efb8e name1 accountID c638c2dc-1dd1-4cfa-8708-46f368012398 DeviceFreeze-0010 2022-03-29T10:30:24.279+00:00 2022-03-29T10:30:55.462+00:00 example@test.com

absolute-device-freeze-message-list


Gets all the Freeze messages that are configured for the account by the given message_id. If message_id is not given all the messages will be returned.

Base Command

absolute-device-freeze-message-list

Input

Argument Name Description Required
message_id The system-defined, unique identifier of the Freeze message. Optional
limit Maximum number of results to return. Default is 50. Optional
page The page number of the results to retrieve. Default is 0. Optional

Context Output

Path Type Description
Absolute.FreezeMessage.ID String The system-defined, unique identifier of the Freeze message.
Absolute.FreezeMessage.Name String The user-defined name for the Freeze message.
Absolute.FreezeMessage.Content String The user-defined, HTML coded message that shows on a device when a Freeze is applied.
Absolute.FreezeMessage.CreatedBy String The username of the entity that created the Freeze message.
Absolute.FreezeMessage.ChangedBy String The username of the entity that last updated the Freeze message.
Absolute.FreezeMessage.CreatedUTC String The date and time when the Freeze message was created.
Absolute.FreezeMessage.ChangedUTC String The date and time when the Freeze message was last modified.

Command example


#### Context Example

```json
{
    "Absolute": {
        "FreezeMessage": {
            "ChangedBy": "778f8cce-8cc6-4de1-b025-e0538f97e072",
            "ChangedUTC": "2022-04-03T07:45:01.487+00:00",
            "Content": "some text- new",
            "CreatedBy": "example@test.com",
            "CreatedUTC": "2022-04-03T07:45:01.487+00:00",
            "ID": "711b5da9-3867-473f-9d8f-9aba3de42b7a",
            "Name": "name"
        }
    }
}

Human Readable Output

Absolute Device freeze message details

ID Name CreatedUTC ChangedUTC ChangedBy CreatedBy
711b5da9-3867-473f-9d8f-9aba3de42b7a name 2022-04-03T07:45:01.487+00:00 2022-04-03T07:45:01.487+00:00 778f8cce-8cc6-4de1-b025-e0538f97e072 example@test.com

absolute-device-freeze-message-create


Creates a new Freeze message for the account.

Base Command

absolute-device-freeze-message-create

Input

Argument Name Description Required
html_message The user-defined, HTML coded message that shows on a device when a Device Freeze is applied. Should be in HTML format with 1-4000 characters. Required
message_name The user-defined name for the Device Freeze message. The name should be a string with 1-255 characters. Required

Context Output

Path Type Description
Absolute.FreezeMessage.ID String The system-defined, unique identifier of the Freeze message.

Command example

!absolute-device-freeze-message-create html_message="some text" message_name="name name"

Context Example

{
    "Absolute": {
        "FreezeMessage": {
            "ID": "bdaf3a55-411a-4393-a0bf-7340f38fbc68"
        }
    }
}

Human Readable Output

Absolute New freeze message was created with ID: bdaf3a55-411a-4393-a0bf-7340f38fbc68

absolute-device-freeze-message-update


Updates the content of an existing Freeze message.

Base Command

absolute-device-freeze-message-update

Input

Argument Name Description Required
html_message The user-defined, HTML coded message that shows on a device when a Device Freeze is applied. Should be in HTML format with 1-4000 characters. Required
message_name The user-defined name for the Device Freeze message. The name should be a string with 1-255 characters. Required
message_id The system-defined, unique identifier of the Freeze message. Required

Context Output

There is no context output for this command.

Command example

!absolute-device-freeze-message-update html_message="new mesg" message_id=711b5da9-3867-473f-9d8f-9aba3de42b7a message_name="name-new"

Human Readable Output

Absolute Freeze message: 711b5da9-3867-473f-9d8f-9aba3de42b7a was updated successfully

absolute-device-freeze-message-delete


Deletes an existing Freeze message for the account.

Base Command

absolute-device-freeze-message-delete

Input

Argument Name Description Required
message_id The system-defined, unique identifier of the Freeze message. Required

Context Output

There is no context output for this command.

Command example

!absolute-device-freeze-message-delete message_id="711b5da9-3867-473f-9d8f-9aba3de42b7a"

Human Readable Output

Absolute Freeze message: 711b5da9-3867-473f-9d8f-9aba3de42b7a was deleted successfully

absolute-device-unenroll


Initiates an unenroll request on a list of eligible devices.

Base Command

absolute-device-unenroll

Input

Argument Name Description Required
device_ids A comma-separated list of device UIDs that should be unenrolled. Required
exclude_missing_devices Whether to exclude missing devices or not. Possible values are: true, false. Optional

Context Output

Path Type Description
Absolute.DeviceUnenroll.TotalDevices String The total number of devices in the request.
Absolute.DeviceUnenroll.Pending Number The number of pending devices.
Absolute.DeviceUnenroll.Processing Number The number of processing devices.
Absolute.DeviceUnenroll.Completed Number The number of completed device unenroll.
Absolute.DeviceUnenroll.Canceled Number The number of canceled device unenroll.
Absolute.DeviceUnenroll.Failed Number The number of failed device unenroll.
Absolute.DeviceUnenroll.RequestId String The unique ID identifier of the request.
Absolute.DeviceUnenroll.RequestUid String The unique GUID identifier of the request.
Absolute.DeviceUnenroll.RequestStatus String The status of the request.
Absolute.DeviceUnenroll.UpdatedDateTimeUtc Date The time (in Unix epoch) when the request was last updated.
Absolute.DeviceUnenroll.CreatedDateTimeUtc Date The time (in Unix epoch) when the request was created.
Absolute.DeviceUnenroll.Requester String The requester of the request.
Absolute.DeviceUnenroll.ExcludeMissingDevices Boolean Whether the request includes missing devices or not.
Absolute.DeviceUnenroll.Devices.DeviceUid String The unique GUID identifier of the device.
Absolute.DeviceUnenroll.Devices.ActionUid String The unique GUID identifier of the action.
Absolute.DeviceUnenroll.Devices.RequestUid String The unique GUID identifier of the request.
Absolute.DeviceUnenroll.Devices.DeviceName String The unique device name of the device.
Absolute.DeviceUnenroll.Devices.ActionStatus String The status of the action.
Absolute.DeviceUnenroll.Devices.ESN String The unique Electronic SerialNumber (ESN) that is assigned to the agent installed on the device.
Absolute.DeviceUnenroll.Devices.UpdatedDateTimeUtc Date The time (in Unix epoch) when the request was last updated.
Absolute.DeviceUnenroll.Devices.CreatedDateTimeUtc Date The time (in Unix epoch) when the request was created.

Command example

!absolute-device-unenroll device_ids="1"

Context Example

{
    "Absolute": {
        "DeviceUnenroll": [
            {
                "DeviceUid": "1",
                "ESN": "2BU2PJD28VAA1UYL0008",
                "EligibleStatus": 0,
                "Serial": "CNF83051BN",
                "SystemName": "user1",
                "Username": "example@test.com"
            },
            {
                "DeviceUid": "2",
                "ESN": "2BU2PJ545L0008",
                "EligibleStatus": 1,
                "Serial": "CNF43051BN",
                "SystemName": "user2",
                "Username": "example2@test.com"
            }
        ]
    }
}

Human Readable Output

Absolute unenroll devices

DeviceUid ESN EligibleStatus Serial SystemName Username
1 2BU2PJD28VAA1UYL0008 0 CNF83051BN user1 example@test.com
2 2BU2PJ545L0008 1 CNF43051BN user2 example2@test.com

absolute-device-application-list


Gets a list of device records and the corresponding software application data for each device on the account that you have access to or that meets the given filter.

Base Command

absolute-device-application-list

Input

Argument Name Description Required
filter The query by which to filter the device applications. If this argument is set, it overrides the others. For example, appName eq ‘someName’ or availableVirtualMemoryBytes lt 1073741824. Optional
return_fields A comma-separated list of all specific values to return. If not set, all possible values will be returned. Optional
device_ids A comma-separated list of the system-defined unique identifier of the devices. Optional
device_names A comma-separated list of the devices names. Optional
app_names A comma-separated list of the application names. Optional
app_publishers A comma-separated list of the name of the software publishers of the application. Optional
user_names A comma-separated list of the user names of the users logged in to the device. Optional
os A comma-separated list of the operating systems that are installed on the device. Optional
esn A comma-separated list of the system-defined unique Electronic Serial Numbers (ESN) assigned to the Absolute agent installed on the device. Optional
limit Maximum number of results to return. Default is 50. Optional
page The page number of the results to retrieve. Minimum value is 0. Default is 0. Optional

Context Output

Path Type Description
Absolute.DeviceApplication.DeviceAppId String The unique ID of the application.
Absolute.DeviceApplication.DeviceUid String The system-defined unique identifier of the device.
Absolute.DeviceApplication.AppId String The identifier of the application.
Absolute.DeviceApplication.AppName String The name of the application.
Absolute.DeviceApplication.AppPublisher String The name of the software publisher of the application.
Absolute.DeviceApplication.AppOriginalPublisher String The original name of the software publisher of the application.
Absolute.DeviceApplication.AppVersion String The version of the application.
Absolute.DeviceApplication.Ens String The system-defined unique Electronic Serial Number (ESN) assigned to the Absolute agent installed on the device.
Absolute.DeviceApplication.DeviceName String The name of the device.
Absolute.DeviceApplication.DeviceSerialNumber String The identification number that is assigned to the device by the device manufacturer.
Absolute.DeviceApplication.UserName String Includes the device name and the username of the user logged in to the device at the time of the agent call.
Absolute.DeviceApplication.InstallPath String The location where the application is installed.
Absolute.DeviceApplication.InstallDate Date The date (in UNIX epoch time) when the application was installed.
Absolute.DeviceApplication.FirstDetectUtc Date The date and time (in Unix epoch time) when the indicated version of the application was first detected on the device.
Absolute.DeviceApplication.OsName String The operating system that is installed on the device.
Absolute.DeviceApplication.LastScanTimeUtc Date The date and time (in Unix epoch time) of the most recent installed software (SNG) scan.

Command example

!absolute-device-application-list device_ids=1234 filter="appName eq 'Notepad++' or appName eq 'Settings'"

Context Example

{
    "Absolute": {
        "DeviceApplication": [
            {
                "AccountUid": "accountID",
                "AppId": "U2V0dGluZ3N8fE1pY3Jvc29mdHx8MTAuMC4yLjEwMDA=",
                "AppName": "Settings",
                "AppOriginalName": "Settings",
                "AppOriginalPublisher": "Microsoft Corporation",
                "AppOriginalVersion": "10.0.2.1000",
                "AppPublisher": "Microsoft",
                "AppVersion": "10.0.2.1000",
                "DeviceAppId": "123456_24222cfe5ccbe45e6d5a78faa58cd977921b4c1dd552884ec38653934c3b75f9",
                "DeviceName": "ABSOLUTE-ASSET-",
                "DeviceSerialNumber": "GoogleCloud-B8736A4405BF0E968020BCBC46EDA096",
                "DeviceUid": "123456",
                "Esn": "D0001",
                "FirstDetectUtc": 1648601001193,
                "InstallDate": 1536995941018,
                "InstallPath": "C:\\Windows\\ImmersiveControlPanel",
                "LastScanTimeUtc": 1648627356120,
                "OsName": "Microsoft Windows Server 2019 Datacenter"
            },
            {
                "AccountUid": "accountID",
                "AppId": "Tm90ZXBhZCsrfHxOb3RlUGFkfHw4LjMuMw==",
                "AppName": "Notepad++",
                "AppOriginalName": "Notepad++ (64-bit x64)",
                "AppOriginalPublisher": "Notepad++ Team",
                "AppOriginalVersion": "8.3.3",
                "AppPublisher": "NotePad",
                "AppVersion": "8.3.3",
                "DeviceAppId": "1234_3653d2a872cd1208dfc4845a20670a6c9364a626cd6c987cd5e916ca71f38105",
                "DeviceName": "ABSOLUTE-ASSET-",
                "DeviceSerialNumber": "GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA",
                "DeviceUid": "1234",
                "Esn": "D0004",
                "FirstDetectUtc": 1648027180153,
                "InstallDate": 1648026266334,
                "InstallPath": "C:\\Program Files\\Notepad++",
                "LastScanTimeUtc": 1648894013571,
                "OsName": "Microsoft Windows Server 2022 Datacenter"
            },
            {
                "AccountUid": "accountID",
                "AppId": "U2V0dGluZ3N8fE1pY3Jvc29mdHx8MTAuMC40LjEwMDA=",
                "AppName": "Settings",
                "AppOriginalName": "Settings",
                "AppOriginalPublisher": "Microsoft Corporation",
                "AppOriginalVersion": "10.0.4.1000",
                "AppPublisher": "Microsoft",
                "AppVersion": "10.0.4.1000",
                "DeviceAppId": "1234_f3ef442d95c3b7155fe9b138384bda2219afb3048c2a9466f1174fc1488ca671",
                "DeviceName": "ABSOLUTE-ASSET-",
                "DeviceSerialNumber": "GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA",
                "DeviceUid": "1234",
                "Esn": "D0004",
                "FirstDetectUtc": 1648027180153,
                "InstallDate": 1620462024187,
                "InstallPath": "C:\\Windows\\ImmersiveControlPanel",
                "LastScanTimeUtc": 1648894013571,
                "OsName": "Microsoft Windows Server 2022 Datacenter"
            }
        ]
    }
}

Human Readable Output

Absolute device applications list

|AccountUid|AppId|AppName|AppOriginalName|AppOriginalPublisher|AppOriginalVersion|AppPublisher|AppVersion|DeviceAppId|DeviceName|DeviceSerialNumber|DeviceUid|Esn|FirstDetectUtc|InstallDate|InstallPath|LastScanTimeUtc|OsName|
|—|—|—|—|—|—|—|—|—|—|—|—|—|—|—|—|—|—|
| accountID | U2V0dGluZ3N8fE1pY3Jvc29mdHx8MTAuMC4yLjEwMDA= | Settings | Settings | Microsoft Corporation | 10.0.2.1000 | Microsoft | 10.0.2.1000 | 123456_24222cfe5ccbe45e6d5a78faa58cd977921b4c1dd552884ec38653934c3b75f9 | ABSOLUTE-ASSET- | GoogleCloud-B8736A4405BF0E968020BCBC46EDA096 | 123456 | D0001 | 1648601001193 | 1536995941018 | C:\Windows\ImmersiveControlPanel | 1648627356120 | Microsoft Windows Server 2019 Datacenter |
| accountID | Tm90ZXBhZCsrfHxOb3RlUGFkfHw4LjMuMw== | Notepad++ | Notepad++ (64-bit x64) | Notepad++ Team | 8.3.3 | NotePad | 8.3.3 | 1234_3653d2a872cd1208dfc4845a20670a6c9364a626cd6c987cd5e916ca71f38105 | ABSOLUTE-ASSET- | GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA | 1234 | D0004 | 1648027180153 | 1648026266334 | C:\Program Files\Notepad++ | 1648894013571 | Microsoft Windows Server 2022 Datacenter |
| accountID | U2V0dGluZ3N8fE1pY3Jvc29mdHx8MTAuMC40LjEwMDA= | Settings | Settings | Microsoft Corporation | 10.0.4.1000 | Microsoft | 10.0.4.1000 | 1234_f3ef442d95c3b7155fe9b138384bda2219afb3048c2a9466f1174fc1488ca671 | ABSOLUTE-ASSET- | GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA | 1234 | D0004 | 1648027180153 | 1620462024187 | C:\Windows\ImmersiveControlPanel | 1648894013571 | Microsoft Windows Server 2022 Datacenter |
Above results are with page number: 0 and with size: 50.

absolute-device-list


Gets a list of device records and their corresponding data that meets the required filter for all devices in your account, based on your authorization token.

Base Command

absolute-device-list

Input

Argument Name Description Required
filter The query by which to filter all the devices managed by the account. If this argument is set, it overrides the others. For example, agentStatus eq ‘A’ or lastConnectedUtc lt datetime’2021-01-01T00:00:00Z’. Optional
agent_status The status of the Absolute agent on the device. Possible values are: Active, Disabled, Inactive. Optional
os_name Short description of the operating system expressed as a one-line string that includes the version of the operating system. Optional
os_version The version of the operating system. Optional
manufacturer The manufacturer of the device. Optional
model The product name from the manufacturer. Optional
user_names A comma-separated list of the usernames of the users who were logged in to the device at the time of the most recent agent call. If no user was logged in during the last agent call, the last detected username is used. Optional
limit Maximum number of results to return. Default is 50. Optional
page The page number of the results to retrieve. Minimum value is 0. Default is 0. Optional

Context Output

Path Type Description
Absolute.Device.Id String The unique identifier assigned to the device.
Absolute.Device.Esn String The unique ESN (Absolute Identifier) assigned to the agent installed on the device.
Absolute.Device.LastConnectedUtc Number The date and time (in Unix epoch) when the device last connected to the Absolute Monitoring Center.
Absolute.Device.SystemName String The name assigned to the device.
Absolute.Device.FullSystemName String The full name assigned to the device consisting of the system name and the domain name.
Absolute.Device.AgentStatus String The status of the Absolute agent on the device.
Absolute.Device.osName String Short description of the operating system expressed as a one-line string that includes the version of the operating system.
Absolute.Device.SystemManufacturer String The manufacturer of the device.
Absolute.Device.SystemModel String The product name from the manufacturer.
Absolute.Device.SystemType String The system running on the Windows-based computer.
Absolute.Device.Serial String The manufacturer-defined unique identifier assigned to the device. May correspond to the serial number of the BIOS, the motherboard, or the chassis, depending on the manufacturer.
Absolute.Device.LocalIp String Last known local IP address of this device.
Absolute.Device.PublicIp String Last known public IP address of this device.
Absolute.Device.EncryptionStatus String The summarized encryption status of the device.

Command example

!absolute-device-list os_name="Microsoft Windows Server 2019 Datacenter"

Context Example

{
    "Absolute": {
        "Device": {
            "AgentStatus": "A",
            "Esn": "D0001",
            "FullSystemName": "ABSOLUTE-ASSET-.WORKGROUP",
            "Id": "123456",
            "LastConnectedUtc": 1648971645189,
            "LocalIp": "127.0.0.1",
            "PublicIp": "127.0.0.1",
            "Serial": "GoogleCloud-B8736A4405BF0E968020BCBC46EDA096",
            "SystemManufacturer": "Google",
            "SystemModel": "GOOGLE COMPUTE ENGINE",
            "SystemName": "ABSOLUTE-ASSET-",
            "SystemType": "x64-based PC",
            "osName": "Microsoft Windows Server 2019 Datacenter"
        }
    }
}

Human Readable Output

Absolute devices list

|AgentStatus|Esn|FullSystemName|Id|LastConnectedUtc|LocalIp|PublicIp|Serial|SystemManufacturer|SystemModel|SystemName|SystemType|osName|
|—|—|—|—|—|—|—|—|—|—|—|—|—|
| A | D0001 | ABSOLUTE-ASSET-.WORKGROUP | 123456 | 1648971645189 | 127.0.0.1 | 127.0.0.1 | GoogleCloud-B8736A4405BF0E968020BCBC46EDA096 | Google | GOOGLE COMPUTE ENGINE | ABSOLUTE-ASSET- | x64-based PC | Microsoft Windows Server 2019 Datacenter |
Above results are with page number: 0 and with size: 50.

absolute-device-get


Gets a list of device records and their corresponding data that meets the required fields for all devices in your account. Note that the command supports returning a maximum of 500 distinct devices.

Base Command

absolute-device-get

Input

Argument Name Description Required
fields A comma-separated list of all specific values to return. Optional
device_ids A comma-separated list of the system-defined unique identifier of the devices. Optional
device_names A comma-separated list of the devices names. Optional
local_ips A comma-separated list of the last known local IP addresses of a device. Optional
public_ips A comma-separated list of the last known public IP addresses of a device. Optional

Context Output

Path Type Description
Absolute.Device.Id String The unique identifier assigned to the device.
Absolute.Device.Esn String The unique ESN (Absolute Identifier) assigned to the agent installed on the device.
Absolute.Device.Domain String The name of the Windows domain to which this device belongs.
Absolute.Device.LastConnectedUtc Number The date and time (in Unix epoch) when the device last connected to the Absolute Monitoring Center.
Absolute.Device.SystemName String The name assigned to the device.
Absolute.Device.FullSystemName String The full name assigned to the device consisting of the system name and the domain name.
Absolute.Device.AgentStatus String The status of the Absolute agent on the device.
Absolute.Device.Os.name String Short description of the operating system expressed as a one-line string that includes the version of the operating system.
Absolute.Device.Os.version String The version of the operating system.
Absolute.Device.Os.currentBuild String The current build number of the operating system.
Absolute.Device.Os.architecture String The architecture of the operating system.
Absolute.Device.Os.installDate Number The date and time (in Unix epoch) when the operating system was installed.
Absolute.Device.Os.productKey String The product key of the operating system.
Absolute.Device.Os.serialNumber String The serial identification number of the operating system.
Absolute.Device.Os.lastBootTime Number The date and time (in Unix epoch) when the operating system was last restarted.
Absolute.Device.Bios.id String The unique identifier of this BIOS given by the manufacturer.
Absolute.Device.Bios.serialNumber String The serial number assigned to the BIOS.
Absolute.Device.Bios.smBiosVersion String The major version number of the BIOS, as reported by SMBIOS.
Absolute.Device.Bios.version String The version of the BIOS, as reported by SMBIOS.
Absolute.Device.Bios.versionDate String A substring of the manufacturer of the BIOS + the version of the BIOS version, as reported by SMBIOS + the release date of the Window BIOS.
Absolute.Device.SystemManufacturer String The manufacturer of the device.
Absolute.Device.SystemModel String The product name from the manufacturer.
Absolute.Device.SystemType String The system running on the Windows-based computer.
Absolute.Device.Serial String The manufacturer-defined unique identifier assigned to the device. May correspond to the serial number of the BIOS, the motherboard, or the chassis, depending on the manufacturer.
Absolute.Device.LocalIp String Last known local IP address of this device.
Absolute.Device.PublicIp String Last known public IP address of this device.
Absolute.Device.EncryptionStatus String The summarized encryption status of the device.
Absolute.Device.Username String The unique username of the user that was logged in to the device at the time of the most recent agent call.
Absolute.Device.PolicyGroupUid String The unique identifier of the policy group that the device belongs to.
Absolute.Device.PolicyGroupName String The name of the policy group that the device belongs to.
Absolute.Device.IsStolen String Indicates whether this device was reported as stolen.
Absolute.Device.DeviceStatus.type String The status of the device. Possible values are STOLEN or MISSING.
Absolute.Device.DeviceStatus.reported Number The data and time (in Unix epoch) when the device was reported missing or stolen.
Absolute.Device.NetworkAdapters.networkSSID String The Service Set Identifier (SSID) of the connected Wi-Fi adapter.

Command example

!absolute-device-get device_names="ABSOLUTE-ASSET-"

Context Example

{
    "Absolute": {
        "Device": [
            {
                "AgentStatus": "A",
                "Bios": {
                    "id": "Google - 1 Google ",
                    "serialNumber": "GoogleCloud-B8736A4405BF0E968020BCBC46EDA096",
                    "smBiosVersion": "2.4",
                    "version": "Google - 1 Google ",
                    "versionDate": "Google Google, 01/01/2011"
                },
                "Domain": "WORKGROUP",
                "Esn": "D0001",
                "FullSystemName": "ABSOLUTE-ASSET-.WORKGROUP",
                "Id": "123456",
                "LastConnectedUtc": 1648971645189,
                "LocalIp": "127.0.0.1",
                "NetworkAdapters": [
                    {},
                    {}
                ],
                "Os": {
                    "architecture": "64-bit",
                    "currentBuild": "17763",
                    "installDate": 1643800616000,
                    "lastBootTime": 1646884562500,
                    "name": "Microsoft Windows Server 2019 Datacenter",
                    "productKey": "WMDGN-G9PQG-XVVXX-R3X43-63DFG",
                    "serialNumber": "00430-00000-00000-AA691",
                    "version": "10.0.17763"
                },
                "PolicyGroupName": "Global Policy Group",
                "PublicIp": "127.0.0.1",
                "Serial": "GoogleCloud-B8736A4405BF0E968020BCBC46EDA096",
                "SystemManufacturer": "Google",
                "SystemModel": "GOOGLE COMPUTE ENGINE",
                "SystemName": "ABSOLUTE-ASSET-",
                "SystemType": "x64-based PC",
                "Username": "Administrator"
            },
            {
                "AgentStatus": "A",
                "Bios": {
                    "id": "Google - 1 Google ",
                    "serialNumber": "GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA",
                    "smBiosVersion": "2.4",
                    "version": "Google - 1 Google ",
                    "versionDate": "Google Google, 01/01/2011"
                },
                "Domain": "WORKGROUP",
                "Esn": "D0004",
                "FullSystemName": "ABSOLUTE-ASSET-.WORKGROUP",
                "Id": "1234",
                "LastConnectedUtc": 1648971873079,
                "LocalIp": "127.0.0.1",
                "NetworkAdapters": [
                    {},
                    {}
                ],
                "Os": {
                    "architecture": "64-bit",
                    "currentBuild": "20348",
                    "installDate": 1648025097000,
                    "lastBootTime": 1648025060499,
                    "name": "Microsoft Windows Server 2022 Datacenter",
                    "productKey": "WX4NM-KYWYW-QJJR4-XV3QB-6VM33",
                    "serialNumber": "00454-60000-00001-AA937",
                    "version": "10.0.20348"
                },
                "PolicyGroupName": "Global Policy Group",
                "PublicIp": "127.0.0.1",
                "Serial": "GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA",
                "SystemManufacturer": "Google",
                "SystemModel": "GOOGLE COMPUTE ENGINE",
                "SystemName": "ABSOLUTE-ASSET-",
                "SystemType": "x64-based PC",
                "Username": "Administrator"
            }
        ]
    }
}

Human Readable Output

Absolute devices list

AgentStatus Bios Domain Esn FullSystemName Id LastConnectedUtc LocalIp NetworkAdapters Os PolicyGroupName PublicIp Serial SystemManufacturer SystemModel SystemName SystemType Username
A id: Google - 1 Google
serialNumber: GoogleCloud-B8736A4405BF0E968020BCBC46EDA096
version: Google - 1 Google
versionDate: Google Google, 01/01/2011
smBiosVersion: 2.4
WORKGROUP D0001 ABSOLUTE-ASSET-.WORKGROUP 123456 1648971645189 127.0.0.1 {},
{}
architecture: 64-bit
installDate: 1643800616000
lastBootTime: 1646884562500
name: Microsoft Windows Server 2019 Datacenter
productKey: WMDGN-G9PQG-XVVXX-R3X43-63DFG
serialNumber: 00430-00000-00000-AA691
version: 10.0.17763
currentBuild: 17763
Global Policy Group 127.0.0.1 GoogleCloud-B8736A4405BF0E968020BCBC46EDA096 Google GOOGLE COMPUTE ENGINE ABSOLUTE-ASSET- x64-based PC Administrator
A id: Google - 1 Google
serialNumber: GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA
version: Google - 1 Google
versionDate: Google Google, 01/01/2011
smBiosVersion: 2.4
WORKGROUP D0004 ABSOLUTE-ASSET-.WORKGROUP 1234 1648971873079 127.0.0.1 {},
{}
architecture: 64-bit
installDate: 1648025097000
lastBootTime: 1648025060499
name: Microsoft Windows Server 2022 Datacenter
productKey: WX4NM-KYWYW-QJJR4-XV3QB-6VM33
serialNumber: 00454-60000-00001-AA937
version: 10.0.20348
currentBuild: 20348
Global Policy Group 127.0.0.1 GoogleCloud-6420CF930DEE84DE8497CF40F0D56AFA Google GOOGLE COMPUTE ENGINE ABSOLUTE-ASSET- x64-based PC Administrator

absolute-device-location-get


Gets a list of devices geo locations records and their corresponding data that meets the required devices IDs.

Base Command

absolute-device-location-get

Input

Argument Name Description Required
device_ids A comma-separated list of the system-defined unique identifier of the devices. Required

Context Output

Path Type Description
Absolute.LocationReport.Coordinates Unknown A comma-separated list where the first number is the estimated latitude and the second number is the estimated longitude (in degrees) where the device is located.
Absolute.LocationReport.ID String The system-defined unique identifier of the device.
Absolute.LocationReport.City String The city where the device is located.
Absolute.LocationReport.State String The state or province where the device is located.
Absolute.LocationReport.CountryCode String The country code for the country where the device is located.
Absolute.LocationReport.Country String The country where the device is located.
Absolute.LocationReport.LocationTechnology String The technology used to get the location.
Absolute.LocationReport.Accuracy Number The estimated accuracy (in meters) of the technology used to locate the device.
Absolute.LocationReport.LastUpdate Number The date and time (in Unix epoch) when the device last changed its location.

Command example

!absolute-device-location-get device_ids=1234

Human Readable Output

No device locations found in Absolute for the given filters: {‘device_ids’: ‘1234’}

absolute-device-get-events


Retrieves a list of events from the Absolute device instance.

Base Command

absolute-device-get-events

Input

Argument Name Description Required
should_push_events Set this argument to True in order to create events, otherwise the command will only display them. Possible values are: true, false. Default is false. Optional
start_date Filters the results to the records on or after the start date. Optional
end_date Filters the results to the records until the end date. Optional
limit The maximum number of records to return per page. Note, this may be restricted by fixed system limits. Optional

Command Example

!absolute-device-get-events start_date="one minute ago" end_date="now" limit=10

Creating a filtering and sorting query

The following commands have the option to insert a filter argument:

  • absolute-device-application-list
  • absolute-device-list

Absolute uses a subset of query options from Open Data Protocol (OData) for filtering and sorting.
OData version 1 and 2 are supported. OData query parameters must be alphabetized and URI encoded.
For more information about OData, see: https://www.odata.org/documentation.

A few examples of creating a query (i.e., passing a filter argument):

  • Using the eq operator
    • Get a list of all devices with an active status: agentStatus eq ‘A’
    • Get a list of all devices that are currently frozen: dfStatus.statusCode eq ‘FRZN’
    • Get a list of all devices that have 1734 in their ESN (Identifier): substringof(‘1734’,esn) eq true
  • Using the ne operator
    • Get a list of all devices that are not active: agentStatus ne ‘A’
  • Using the gt operator
    • Get a list of all devices with greater than 1 GB (1073741824 bytes) of available physical: availablePhysicalRamBytes gt 1073741824
  • Using the or operator:
    • Get a list of all devices with less than 1 GB (1073741824 bytes) of available physical ram or less than 1 GB (1073741824 bytes) of available virtual raml: availablePhysicalMemroyBytes lt 1073741824 or availableVirtualMemoryBytes lt 1073741824

For more examples and explanations, see the Absolute docs (from page 10).

Breaking changes from the previous version of this integration - Absolute

The following sections list the changes in this version.

Arguments

The following arguments were removed in this version

In the absolute-device-application-list command, the following argument was removed:

  • account_uids

Outputs

  • In the absolute-device-unenroll command the following outputs were replaced:
    • Absolute.DeviceUnenroll.DeviceUid - replaced by Absolute.DeviceUnenroll.Devices.DeviceUid.
    • Absolute.DeviceUnenroll.ESN - replaced by Absolute.DeviceUnenroll.Devices.ESN.
  • In the absolute-device-freeze-request-get command the following outputs were replaced:
    • Absolute.FreezeRequestDetail.Configuration.issuedUtc - replaced by Absolute.FreezeRequestDetail.Configuration.issuedDateTimeUTC.
    • Absolute.FreezeRequestDetail.RequesterUid - replaced by Absolute.FreezeRequestDetail.Requester.
    • Absolute.FreezeRequestDetail.Statuses.ackClientUtc - replaced by Absolute.FreezeRequestDetail.Statuses.ackClientDateTimeUtcs.
    • Absolute.FreezeRequestDetail.Statuses.scheduledFreezeDateUTC - replaced by Absolute.FreezeRequestDetail.Statuses.scheduledFreezeDateTimeUtc.
    • Absolute.FreezeRequestDetail.Statuses.updatedUtc - replaced by Absolute.FreezeRequestDetail.Statuses.updatedDateTimeUtc.
  • In the absolute-device-application-list command the following output was removed:
    • Absolute.DeviceApplication.AccountUid
  • In the absolute-custom-device-field-list command the following output was removed:
    • Absolute.CustomDeviceField.ESN
  • In the absolute-device-freeze-request-get command the following outputs were removed:
    • Absolute.FreezeRequestDetail.Statuses.statusUid
    • Absolute.FreezeRequestDetail.RequesterUid
  • In the absolute-device-unenroll command the following outputs were removed:
    • Absolute.DeviceUnenroll.EligibleStatus
    • Absolute.DeviceUnenroll.Serial
    • Absolute.DeviceUnenroll.SystemName
    • Absolute.DeviceUnenroll.Username

<~PLATFORM>

License Requirements

The following configuration parameters require the Cortex XSIAM license:

  • Fetch Events

</~PLATFORM>

Configuration parameters

  • url — Your Absolute server URL (required)
  • credentials — Token ID (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • eventFetchInterval — Events Fetch Interval
  • max_events_per_fetch — Max number of events per fetch
  • isFetchEvents

Commands (15)

  • absolute-custom-device-field-list

    Returns a list of custom device fields associated with the given device_id, based on the authorization token.

  • absolute-custom-device-field-update

    Updates the value of the included custom device fields for the given device_id.

  • absolute-device-application-list

    Gets a list of device records and the corresponding software application data for each device on the account that you have access to or that meets the given filter.

  • absolute-device-freeze-message-create

    Creates a new Freeze message for the account.

  • absolute-device-freeze-message-delete

    Deletes an existing Freeze message for the account. Use absolute-device-freeze-message-list in order to get the message_id.

  • absolute-device-freeze-message-list

    Gets all the Freeze messages that are configured for the account by the given message_id. If message_id is not given all the messages will be returned.

  • absolute-device-freeze-message-update

    Updates the content of an existing Freeze message. Use absolute-device-freeze-message-list in order to get the message_id.

  • absolute-device-freeze-request

    Creates a new Freeze request for the devices specified in the device_ids argument.

  • absolute-device-freeze-request-get

    Gets detailed information about the Freeze request specified by request_uid.

  • absolute-device-get

    Gets a list of device records and their corresponding data that meets the required fields for all devices in your account. Note that the command supports returning a maximum of 500 distinct devices.

  • absolute-device-get-events

    Retrieves a list of events from the Absolute instance.

  • absolute-device-list

    Gets a list of device records and their corresponding data that meets the required filter for all devices in your account, based on your authorization token.

  • absolute-device-location-get

    Gets a list of devices geo location records and their corresponding data that meets the required devices IDs.

  • absolute-device-remove-freeze-request

    Creates a new Remove Freeze request for one or more devices, regardless of their Freeze status. You can submit Remove Freeze requests to perform the following actions: unfreeze frozen devices, remove newly submitted Freeze requests, or remove outstanding Scheduled and Offline Freeze requests. In case of removing from offline: In addition to creating a Remove Freeze request for devices with a status of Frozen by Condition: Offline, removes Offline Freeze requests from devices with a status of Freeze Requested or Freeze Condition - Offline Set. In case of not removing from offline: a Remove Freeze request is created for the devices but the Conditional - Offline Freeze request is not deleted (deprecated).

  • absolute-device-unenroll

    Initiates an unenroll request on a list of eligible devices.

import copy
import urllib.parse
import demistomock as demisto  # noqa: F401
import jwt
from CommonServerPython import *  # noqa: F401
from CommonServerUserPython import *  # noqa


""" CONSTANTS """

ABSOLUTE_URL_TO_API_URL = {
    "https://cc.absolute.com": "https://api.absolute.com",
    "https://cc.us.absolute.com": "https://api.us.absolute.com",
    "https://cc.eu2.absolute.com": "https://api.eu2.absolute.com",
    "https://cc.fr1.absolutegov.com": "https://api.fr1.absolutegov.com",
    "https://cc.in1.absolute.com": "https://api.in1.absolute.com",
}

ABSOLUTE_AGET_STATUS = {
    "Active": "A",
    "Disabled": "D",
    "Inactive": "I",
}

INTEGRATION = "Absolute"
DEFAULT_LIMIT = 50

DEVICE_LIST_RETURN_FIELDS = [
    "deviceUid",
    "esn",
    "lastConnectedDateTimeUtc",
    "deviceName",
    "systemModel",
    "fullSystemName",
    "agentStatus",
    "operatingSystem.name",
    "systemManufacturer",
    "serialNumber",
    "systemType",
    "localIp",
    "publicIp",
    "espInfo.encryptionStatus",
]

DEVICE_GET_COMMAND_RETURN_FIELDS = [
    "deviceUid",
    "esn",
    "domain",
    "lastConnectedDateTimeUtc",
    "deviceName",
    "systemModel",
    "systemType",
    "fullSystemName",
    "agentStatus",
    "operatingSystem.name",
    "operatingSystem.version",
    "operatingSystem.currentBuild",
    "operatingSystem.architecture",
    "operatingSystem.installDate",
    "operatingSystem.productKey",
    "operatingSystem.serialNumber",
    "operatingSystem.lastBootTime",
    "systemManufacturer",
    "serial",
    "serialNumber",
    "localIp",
    "publicIp",
    "username",
    "espInfo.encryptionStatus",
    "bios.id",
    "bios.serialNumber",
    "bios.version",
    "bios.versionDate",
    "bios.smBiosVersion",
    "policyGroupUid",
    "policyGroupName",
    "isStolen",
    "deviceStatus.type",
    "deviceStatus.reported",
    "networkSSID",
]

DEVICE_GET_LOCATION_COMMAND_RETURN_FIELDS = [
    "geoData.location.point.coordinates",
    "geoData.location.geoAddress.city",
    "geoData.location.geoAddress.state",
    "geoData.location.geoAddress.countryCode",
    "geoData.location.geoAddress.country",
    "geoData.location.locationTechnology",
    "geoData.location.accuracy",
    "geoData.location.lastUpdateDateTimeUtc",
]


DEVICE_OUTPUT_TO_XSOAR_CONTEXT_PATH = {
    "installDateTimeUtc": "InstallDate",
    "firstDetectDateTimeUtc": "FirstDetectUtc",
    "lastScanDateTimeUtc": "LastScanTimeUtc",
    "deviceUid": "Id",
    "lastConnectedDateTimeUtc": "LastConnectedUtc",
    "serialNumber": "Serial",
    "publicIp": "PublicIp",
    "localIp": "LocalIp",
    "username": "Username",
}

SEIM_EVENTS_PAGE_SIZE = 1000
CLIENT_V3_JWS_VALIDATION_URL_SUFFIX = "/jws/validate"
VENDOR = "Absolute"
PRODUCT = "Secure Endpoint"
HEADERS_V3: dict = {"content-type": "text/plain"}
MAX_PAGE_SIZE = 500  # Max allowed page size to solve the issue on absolute end where reaching the page size yield all results.


class ClientV3(BaseClient):
    def __init__(self, base_url: str, token_id: str, secret_key: str, verify: bool, proxy: bool, headers: dict = HEADERS_V3):
        """
        Client to use in the Absolute integration for API v3. Overrides BaseClient.
        Args:
            base_url (str): URL to access when doing a http request.
            token_id (str): The Absolute token id
            secret_key (str): User's Absolute secret key
            verify (bool): Whether to check for SSL certificate validity.
            proxy (bool): Whether the client should use proxies.
            headers (dict): Dictionary of HTTP headers to send with the Request.
        """
        super().__init__(base_url=base_url, verify=verify, proxy=proxy)
        self._token_id = token_id
        self._headers = headers
        self._secret_key = secret_key

    def prepare_request(self, method: str, url_suffix: str, query_string: str, payload: dict = {}) -> str:
        """
        Prepares the signed HTTP request data for making an API call.
        Args:
            method (str): The HTTP method to be used for the request.
            url_suffix (str): The endpoint URL suffix for the API call.
            query_string (str): The query string parameters for the API call.
        Returns:
            bytes: The prepared signed HTTP request data.
        """
        if payload:
            request_payload_data = {"data": payload}
        else:
            request_payload_data = {"data": {}}

        headers = {
            "alg": "HS256",
            "kid": self._token_id,
            "method": method,
            "content-type": "application/json",
            "uri": url_suffix,
            "query-string": query_string,
            "issuedAt": round(time.time() * 1000),
        }

        demisto.debug(f"ABS: Making JWS token with headers: {headers}, request_payload_data: {request_payload_data}")

        return jwt.encode(request_payload_data, self._secret_key, algorithm="HS256", headers=headers)

    def prepare_query_string_for_canonical_request(self, query_string: str) -> str:
        """
        Query is given as a string represents the filter query. For example,
        query_string = "$top=10 $skip=20"
        1. Splitting into a list (by space as a separator).
        2. Sorting arguments in ascending order; for example, 'A' is before 'a'.
        3. URI encode the parameter name and value using URI generic syntax.
        4. Reassembling the list into a string.
        """
        if not query_string:
            return ""
        return urllib.parse.quote(query_string, safe="=&")

    def send_request_to_api(
        self, method: str, url_suffix: str, query_string: str, ok_codes: tuple, payload: dict = {}, resp_type: str = "json"
    ):  # pragma: no cover
        """Sends the request to Absolute

        Args:
            method (str): HTTP request method (GET/PUT/POST/DELETE).
            url_suffix (str): The API endpoint.
            query_string (str): The query to filter results by.
            ok_codes (tuple): An HTTP status code of success
            payload (dict, optional): The payload to send. Defaults to {}.
            resp_type (str, optional): The response type of the request. Defaults to "json".
        """
        signed = self.prepare_request(method=method, url_suffix=url_suffix, query_string=query_string, payload=payload)
        return self._http_request(
            method="POST",
            data=signed,
            full_url=f"{self._base_url}{CLIENT_V3_JWS_VALIDATION_URL_SUFFIX}",
            return_empty_response=True,
            ok_codes=ok_codes,
            resp_type=resp_type,
        )

    def add_pagination(self, next_page: str, page_size: int) -> str:
        """
        Add pagination query format
        """
        query = ""
        if next_page:
            query += f"&nextPage={next_page}"
        if page_size:
            query += f"&pageSize={page_size}"
        return query

    def get_specific_page_data(
        self, url_suffix: str, page_to_return: int, page_size: int, query_string: str, ok_codes: tuple
    ) -> dict:
        """Return a specific page data

        Args:
            url_suffix (str): The url suffix to fetch from.
            page_to_return (int): The page number to return.
            page_size (int): The pga size to return.
            query_string (str): The query to filter results by.
            ok_codes (tuple): An HTTP status code of success.
        """
        current_page = 0  # the first page number to fetch
        next_page = ""
        while current_page <= page_to_return:
            response = self.send_request_to_api(
                "GET", url_suffix, query_string + self.add_pagination(next_page, page_size), ok_codes=ok_codes
            )
            data = response.get("data", {})

            current_page += 1
            next_page = response.get("metadata", {}).get("pagination", {}).get("nextPage", "")
            if not next_page:
                break

        if current_page <= page_to_return and not next_page:
            demisto.debug(
                "ABS: Returning empty data, as next_page is empty, and "
                f"current_page ({current_page}) <= page_to_return ({page_to_return})"
            )
            # no more results in the API
            return {}

        return data

    def get_all_results(self, url_suffix: str, page_size: int, query_string: str, ok_codes: tuple) -> dict:
        """Return all the results from the API

        Args:
            url_suffix (str): The url suffix to fetch from.
            page_size (int): The pga size to return.
            query_string (str): The query to filter results by.
            ok_codes (tuple): An HTTP status code of success.
        """
        next_page = ""
        response = self.send_request_to_api(
            "GET", url_suffix, query_string + self.add_pagination(next_page, page_size), ok_codes=tuple(ok_codes)
        )
        data = response.get("data")

        while next_page := response.get("metadata", {}).get("pagination", {}).get("nextPage", ""):
            response = self.send_request_to_api(
                "GET", url_suffix, query_string + self.add_pagination(next_page, page_size), ok_codes=tuple(ok_codes)
            )
            data += response.get("data")

        return data

    def api_request_absolute(
        self,
        method: str,
        url_suffix: str,
        body: dict = {},
        success_status_code=(),
        query_string: str = "",
        page: int = 0,
        page_size: int = 0,
        specific_page: bool = False,
        resp_type: str = "json",
    ):
        """Makes an HTTP request to the Absolute API.

        Args:
            method (str): HTTP request method (GET/PUT/POST/DELETE).
            url_suffix (str): The API endpoint.
            body (dict, optional): The body to set. Defaults to {}.
            success_status_code (tuple, optional): An HTTP status code of success. Defaults to None.
            query_string (str): The query to filter results by. Defaults to ''.
            page (int): The specific page number to return. Defaults to 0.
            page_size (int): The page size of the response. Defaults to DEFAULT_API_PAGE_SIZE.
            specific_page (bool): Wether to return a specific page or not. Defaults to False.
            resp_type (str): The response type of the request. Defaults to "json".

        """
        demisto.debug(f"current request is: method={method}, url suffix={url_suffix}, body={body}")

        if not success_status_code:
            success_status_code = [200]
        query_string = self.prepare_query_string_for_canonical_request(query_string)

        if method == "GET":
            if specific_page:
                data = self.get_specific_page_data(url_suffix, page, page_size, query_string, ok_codes=tuple(success_status_code))
            else:
                data = self.get_all_results(url_suffix, page_size, query_string, ok_codes=tuple(success_status_code))
            return data

        elif method == "DELETE":
            response = self.send_request_to_api("DELETE", url_suffix, query_string, ok_codes=tuple(success_status_code))
            return response

        elif method == "PUT":
            response = self.send_request_to_api(
                "PUT", url_suffix, query_string, payload=body, ok_codes=tuple(success_status_code), resp_type=resp_type
            )
            return response

        elif method == "POST":
            response = self.send_request_to_api(
                "POST", url_suffix, query_string, payload=body, ok_codes=tuple(success_status_code), resp_type=resp_type
            )
            if isinstance(response, dict):
                # response is not empty
                return response.get("data")
        return None

    def fetch_events_between_dates(self, fetch_limit: int, start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]:
        """
        Helper function to fetch events with time window from the API based on the provided parameters.

        Args:
            fetch_limit (int): The maximum number of events to fetch.
            start_date (datetime): The start date for the events to be fetched.
            end_date (datetime): The end date for the events to be fetched.
        Returns:
            list: A list of fetched events.
        """
        all_events: List[Dict[str, Any]] = []
        next_page_token = ""
        ok_codes = [200]
        while len(all_events) < fetch_limit:
            page_size = min(SEIM_EVENTS_PAGE_SIZE, fetch_limit - len(all_events))
            query_string = self.prepare_query_string_for_fetch_events(
                page_size=page_size, start_date=start_date, end_date=end_date, next_page=next_page_token
            )
            response = self.send_request_to_api("GET", "/v3/reporting/siem-events", query_string, tuple(ok_codes))
            all_events.extend(response.get("data", []))
            next_page_token = response.get("metadata", {}).get("pagination", {}).get("nextPage", "")
            if not next_page_token:
                break

        demisto.debug(f"fetch_events_between_dates: Fetched {len(all_events)} events")
        return all_events

    def prepare_query_string_for_fetch_events(
        self, start_date: datetime, end_date: datetime, page_size: int = None, next_page: str = None
    ) -> str:
        """
        Prepares the query string for fetching events based on the provided parameters.

        Args:
            start_date (datetime): The start date of the events to fetch.
            end_date (datetime): The end date of the events to fetch.
            page_size (int, optional): The size of each page to fetch. Defaults to None.
            next_page (str, optional): The next page token. Defaults to None.

        Returns:
            str: The prepared query string for fetching events.

        """
        from_date_time_utc = f'fromDateTimeUtc={start_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z'
        to_date_time_utc = f'toDateTimeUtc={end_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z'
        query = f"{from_date_time_utc}&{to_date_time_utc}"
        if page_size:
            query += f"&pageSize={page_size}"
        if next_page:
            query += f"&nextPage={next_page}"
        demisto.debug(f"Query string for fetching events: {query}")
        return query


def validate_absolute_api_url(base_url: str) -> str:
    """Validate the base url, and return the respective api url

    Args:
        base_url (str): The base url to validate

    """
    if base_url not in ABSOLUTE_URL_TO_API_URL:
        raise_demisto_exception(
            f"The Absolute server url {base_url} in not a valid url. Possible options: {list(ABSOLUTE_URL_TO_API_URL.keys())}"
        )
    return ABSOLUTE_URL_TO_API_URL[base_url]


def test_module(client: ClientV3) -> str:  # pragma: no cover
    """Tests API connectivity to Absolute"""
    try:
        client.api_request_absolute("GET", "/v3/reporting/devices", query_string="", page=0, page_size=1, specific_page=True)
        message = "ok"
    except DemistoException as e:
        if "Forbidden" in str(e) or "Authorization" in str(e):
            message = "Authorization Error: make sure API Key is correctly set"
        else:
            raise e
    return message


def parse_device_field_list_response(response: dict, device_id: str, limit: Optional[int], all_results: bool) -> dict[str, Any]:
    """Parse the device field list response"""
    parsed_data = {"DeviceUID": device_id, "CDFValues": []}  # type: ignore
    for cdf_item in response:
        parsed_data["CDFValues"].append(  # type: ignore[attr-defined]
            {  # type: ignore
                "CDFUID": cdf_item.get("cdfUid"),
                "FieldKey": cdf_item.get("cdfFieldKey"),
                "FieldName": cdf_item.get("cdfFieldName"),
                "CategoryCode": cdf_item.get("categoryCode"),
                "FieldValue": cdf_item.get("cdfFieldValue"),
                "Type": cdf_item.get("type"),
            }
        )

    if not all_results:
        parsed_data["CDFValues"] = parsed_data["CDFValues"][:limit]
    return parsed_data


def parse_device_field_list_response_human_readable(outputs: dict) -> list:
    """Parse the device field list response for the HR"""
    human_readable = []
    for cdf_values in outputs.get("CDFValues", []):
        human_readable.append(
            {
                "Field Name": cdf_values.get("FieldName"),
                "CDF ID": cdf_values.get("CDFUID"),
                "Field Value": cdf_values.get("FieldValue"),
            }
        )
    return human_readable


def get_custom_device_field_list_command(args, client) -> CommandResults:
    """Gets custom device field"""
    device_id = args.get("device_id")
    limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))
    all_results = argToBoolean(args.get("all_results", False))
    res = client.api_request_absolute("GET", f"/v3/configurations/customfields/devices/{device_id}")
    outputs = parse_device_field_list_response(res, device_id, limit, all_results)
    human_readable = tableToMarkdown(
        f"{INTEGRATION} Custom device field list", parse_device_field_list_response_human_readable(outputs), removeNull=True
    )
    return CommandResults(
        outputs=outputs,
        outputs_prefix="Absolute.CustomDeviceField",
        outputs_key_field="DeviceUID",
        readable_output=human_readable,
        raw_response=res,
    )


def update_custom_device_field_command(args, client) -> CommandResults:
    """Updates custom device field"""
    device_id = args.get("device_id")
    cdf_uid = args.get("cdf_uid")
    field_value = args.get("value")

    payload = [{"cdfUid": cdf_uid, "cdfFieldValue": field_value}]
    client.api_request_absolute(
        "PUT",
        f"/v3/configurations/customfields/devices/{device_id}",
        body=payload,
        success_status_code=[202],
        resp_type="response",
    )
    return CommandResults(readable_output=f"Device {device_id} with value {field_value} was updated successfully.")


def validate_device_freeze_type_offline(offline_time_seconds: Optional[int]) -> int:
    """Validate the Offline type arg"""
    if not offline_time_seconds:
        # the default is 30 days
        offline_time_seconds = 22592000
    else:
        # must be between 1200 seconds (20 minutes) and 172800000 seconds (2000 days)
        offline_time_seconds_valid = 1200 <= offline_time_seconds <= 172800000
        if not offline_time_seconds_valid:
            raise_demisto_exception(
                "the offline_time_seconds arg is not valid. Must be between 1200 seconds"
                " (20 minutes) and 172800000 seconds (2000 days)."
            )
    return offline_time_seconds


def raise_demisto_exception(msg):
    raise DemistoException(f"{INTEGRATION} error: {msg}")


def validate_device_freeze_type_scheduled(scheduled_freeze_date: str) -> str:
    """Validate the scheduled type arg"""
    if not scheduled_freeze_date:
        raise_demisto_exception(
            "When setting device_freeze_type to be Scheduled, you must specify the scheduled_freeze_date arg."
        )
    return scheduled_freeze_date


def validate_passcode_type_args(passcode_type: str, passcode: str, passcode_length: Optional[int], payload: dict) -> dict:
    """Validate the passcode type arguments, and return the payload

    Args:
        passcode_type (str): The passcode type
        passcode (str): The passcode
        passcode_length (int): The passcode length
        payload (dict): The payload
    """
    if passcode_type == "UserDefined":
        if not passcode:
            raise_demisto_exception("when setting passcode_type to be UserDefined, you must specify the passcode arg.")
        payload["passcodeDefinition"].update({"passcode": passcode})
    elif passcode_type == "RandomForEach" or passcode_type == "RandomForAll":
        not_valid_passcode_length = not passcode_length or passcode_length > 8 or passcode_length < 4
        if not_valid_passcode_length:
            raise_demisto_exception(
                "when setting passcode_type to be RandomForEach or RandomForAll, "
                "you must specify the passcode_length arg to be between 4 to 8."
            )
        payload["passcodeDefinition"].update({"length": passcode_length})

    return payload


def parse_freeze_device_response(response: dict, device_ids: str) -> dict:
    """Parse the device freeze response

    Args:
        response (dict): The response to parse
        device_ids (str): The device ids in the request
    """
    outputs = {"RequestUID": response.get("requestUid"), "SucceededDeviceUIDs": device_ids}
    errors = response.get("errors", [])
    human_readable_errors = []
    if errors:
        for error in errors:
            human_readable_errors.append(
                {"Failed UID": ",".join(error.get("detail", []).get("deviceUids")), "Error Message": error.get("message", "")}
            )
        outputs["Errors"] = errors
        outputs["FailedDeviceUIDs"] = human_readable_errors
    return outputs


def device_freeze_request_command(args, client) -> CommandResults:
    """Requests to freeze devices"""
    payload = prepare_payload_to_freeze_request(args)
    res = client.api_request_absolute("POST", "/v3/actions/requests/freeze", body=payload)
    outputs = parse_freeze_device_response(res, args.get("device_ids"))
    human_readable = tableToMarkdown(
        f"{INTEGRATION} device freeze requests results",
        outputs,
        headers=["FailedDeviceUIDs", "RequestUID", "SucceededDeviceUIDs"],
        removeNull=True,
        json_transform_mapping={"FailedDeviceUIDs": JsonTransformer()},
    )

    outputs.pop("FailedDeviceUIDs", "")
    return CommandResults(
        readable_output=human_readable,
        outputs=outputs,
        outputs_prefix="Absolute.FreezeRequest",
        outputs_key_field="RequestUID",
        raw_response=res,
    )


def prepare_payload_to_freeze_request(args) -> dict:
    """prepares the pyload for the freeze request"""
    request_name = args.get("request_name")
    html_message = args.get("html_message")
    message_name = args.get("message_name")
    device_ids = argToList(args.get("device_ids"))
    notification_emails = argToList(args.get("notification_emails"))
    device_freeze_type = args.get("device_freeze_type")
    passcode_type = args.get("passcode_type")

    payload = {
        "requestTitle": request_name,
        "message": html_message,
        "messageName": message_name,
        "freezeDefinition": {"deviceFreezeType": device_freeze_type},
        "deviceUids": device_ids,
        "notificationEmails": notification_emails,
        "passcodeDefinition": {"option": passcode_type},
    }

    scheduled_freeze_date = args.get("scheduled_freeze_date")
    offline_time_seconds = arg_to_number(args.get("offline_time_seconds"), required=False)
    if device_freeze_type == "Scheduled":
        scheduled_freeze_date = validate_device_freeze_type_scheduled(scheduled_freeze_date)
        payload["freezeDefinition"].update({"scheduledFreezeDateTimeUtc": scheduled_freeze_date})

    elif device_freeze_type == "OffLine":
        offline_time_seconds = validate_device_freeze_type_offline(offline_time_seconds)
        payload["freezeDefinition"].update({"offlineTimeSeconds": offline_time_seconds})
    passcode = args.get("passcode")
    passcode_length = arg_to_number(args.get("passcode_length"), required=False)
    payload = validate_passcode_type_args(passcode_type, passcode, passcode_length, payload)
    remove_nulls_from_dictionary(payload)
    return payload


def remove_device_freeze_request_command(args, client) -> CommandResults:
    """Removes device freeze request"""
    device_ids = argToList(args.get("device_ids"))
    remove_scheduled = argToBoolean(args.get("remove_scheduled", False))
    remove_offline = argToBoolean(args.get("remove_offline", False))

    payload = {"deviceUids": device_ids, "removeScheduled": remove_scheduled, "removeOffline": remove_offline}

    client.api_request_absolute(
        "POST", "/v3/actions/freeze/remove-freeze", body=payload, success_status_code=[202], resp_type="response"
    )
    return CommandResults(readable_output=f"Successfully removed freeze request for devices ids: {args.get('device_ids')}.")


def parse_get_device_freeze_response(response: list) -> list:
    """Parse the device freeze response

    Args:
        response (list): The response to parse
    """
    parsed_data = []
    for freeze_request in response:
        parsed_data.append(
            {
                "ID": freeze_request.get("id"),
                "ActionRequestUid": freeze_request.get("requestUid"),
                "DeviceUid": freeze_request.get("deviceUid"),
                "Name": freeze_request.get("requestTitle"),
                "Statuses": freeze_request.get("statuses", []),
                "Configuration": freeze_request.get("configuration", {}),
                "Requester": freeze_request.get("requester"),
                "CreatedUTC": freeze_request.get("createdDateTimeUtc"),
                "ChangedUTC": freeze_request.get("changedDateTimeUtc"),
                "NotificationEmails": freeze_request.get("notificationEmails"),
                "EventHistoryId": freeze_request.get("eventHistoryId"),
                "PolicyGroupUid": freeze_request.get("policyGroupUid"),
                "PolicyConfigurationVersion": freeze_request.get("policyConfigurationVersion"),
                "FreezePolicyUid": freeze_request.get("freezePolicyUid"),
                "Downloaded": freeze_request.get("isDownloaded"),
                "IsCurrent": freeze_request.get("isCurrent"),
                # for the freeze message command
                "Content": freeze_request.get("content"),
                "CreatedBy": freeze_request.get("createdBy"),
                "ChangedBy": freeze_request.get("changedBy"),
            }
        )
    return parsed_data


def parse_device_freeze_message_response(response: list) -> list:
    """Parse the device freeze message response

    Args:
        response (list): The response to parse
    """
    if not isinstance(response, list):
        response = [response]
    parsed_data = []
    for freeze_request in response:
        parsed_data.append(
            {
                "ID": freeze_request.get("messageUid"),
                "Name": freeze_request.get("name"),
                "CreatedUTC": freeze_request.get("createdDateTimeUtc"),
                "ChangedUTC": freeze_request.get("changedDateTimeUtc"),
                "Content": freeze_request.get("content"),
                "CreatedBy": freeze_request.get("createdBy"),
                "ChangedBy": freeze_request.get("changedBy"),
            }
        )
    return parsed_data


def get_device_freeze_request_command(args, client) -> CommandResults:
    """Gets a device freeze request"""
    request_uid = args.get("request_uid")
    res = client.api_request_absolute("GET", f"/v3/actions/freeze/requests/{request_uid}")
    outputs = parse_get_device_freeze_response(res)

    human_readable = tableToMarkdown(
        f"{INTEGRATION} Freeze request details for: {request_uid}",
        outputs,
        headers=["ID", "Name", "ActionRequestUid", "EventHistoryId", "CreatedUTC", "ChangedUTC", "Requester"],
        removeNull=True,
    )
    return CommandResults(
        outputs=outputs,
        outputs_prefix="Absolute.FreezeRequestDetail",
        outputs_key_field="ID",
        readable_output=human_readable,
        raw_response=res,
    )


def list_device_freeze_message_command(args, client) -> CommandResults:
    """Lists device freeze messages"""
    message_id = args.get("message_id")
    if message_id:
        res = client.api_request_absolute("GET", f"/v3/actions/freeze/messages/{message_id}")
    else:
        page = arg_to_number(args.get("page", 0))
        limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))

        res = client.api_request_absolute(
            "GET", "/v3/actions/freeze/messages", page=page, page_size=limit, specific_page=True, success_status_code=(200, 204)
        )

    outputs = parse_device_freeze_message_response(res)
    human_readable = tableToMarkdown(
        f"{INTEGRATION} Device freeze message details:",
        outputs,
        headers=["ID", "Name", "CreatedUTC", "ChangedUTC", "ChangedBy", "CreatedBy"],
        removeNull=True,
    )
    return CommandResults(
        outputs=outputs,
        outputs_prefix="Absolute.FreezeMessage",
        outputs_key_field="ID",
        readable_output=human_readable,
        raw_response=res,
    )


def create_device_freeze_message_command(args, client) -> CommandResults:
    """Creates device freeze message"""
    html_message = args.get("html_message")
    message_name = args.get("message_name")

    payload = {"name": message_name, "content": html_message}

    res = client.api_request_absolute("POST", "/v3/actions/freeze/messages", body=payload, success_status_code=(200, 201))
    message_id = res.get("messageUid", "")
    human_readable = f"{INTEGRATION} New freeze message was created with ID: {message_id}"
    return CommandResults(
        outputs={"ID": message_id},
        outputs_prefix="Absolute.FreezeMessage",
        outputs_key_field="ID",
        readable_output=human_readable,
        raw_response=res,
    )


def update_device_freeze_message_command(args, client) -> CommandResults:
    """Updates device freeze message"""
    message_id = args.get("message_id")
    html_message = args.get("html_message")
    message_name = args.get("message_name")
    payload = {"name": message_name, "content": html_message}
    client.api_request_absolute("PUT", f"/v3/actions/freeze/messages/{message_id}", body=payload, success_status_code=(200, 204))
    return CommandResults(readable_output=f"{INTEGRATION} Freeze message: {message_id} was updated successfully")


def delete_device_freeze_message_command(args, client) -> CommandResults:
    """Deletes device freeze request"""
    message_id = args.get("message_id")
    client.api_request_absolute("DELETE", f"/v3/actions/freeze/messages/{message_id}", success_status_code=[204])
    return CommandResults(readable_output=f"{INTEGRATION} Freeze message: {message_id} was deleted successfully")


def parse_device_unenroll_request_data_response(response: dict) -> dict:
    """Parse the unenroll response

    Args:
        response (dict): The response to parse
    """
    parsed_data = {
        "TotalDevices": response.get("totalDevices"),
        "Pending": response.get("pending"),
        "Processing": response.get("processing"),
        "Completed": response.get("completed"),
        "Canceled": response.get("canceled"),
        "Failed": response.get("failed"),
        "RequestId": response.get("requestId"),
        "RequestUid": response.get("requestUid"),
        "RequestStatus": response.get("requestStatus"),
        "CreatedDateTimeUtc": response.get("createdDateTimeUtc"),
        "UpdatedDateTimeUtc": response.get("updatedDateTimeUtc"),
        "Requester": response.get("requester"),
        "ExcludeMissingDevices": response.get("excludeMissingDevices"),
    }
    return parsed_data


def parse_device_unenroll_response(response: list, request_uid: str) -> list:
    """Parse the unenroll response

    Args:
        response (list): The response to parse.
        request_uid (str): The request uid if the current request
    """
    parsed_devices_data = []
    for device in response:
        if device.get("requestUid") == request_uid:
            # only returning device data from the current unenroll request
            parsed_devices_data.append(
                {
                    "DeviceUid": device.get("deviceUid"),
                    "ActionUid": device.get("actionUid"),
                    "RequestUid": device.get("requestUid"),
                    "DeviceName": device.get("deviceName"),
                    "ActionStatus": device.get("actionStatus"),
                    "ESN": device.get("esn"),
                    "CreatedDateTimeUtc": device.get("createdDateTimeUtc"),
                    "UpdatedDateTimeUtc": device.get("updatedDateTimeUtc"),
                }
            )
    return parsed_devices_data


def device_unenroll_command(args, client) -> CommandResults:
    """Unenroll devices"""
    device_ids = argToList(args.get("device_ids"))
    exclude_missing_devices = args.get("exclude_missing_devices", "false")
    payload = {"deviceUids": device_ids, "excludeMissingDevices": exclude_missing_devices}

    # getting the requestUid from API 1
    res_1 = client.api_request_absolute("POST", "/v3/actions/requests/unenroll", body=payload)
    request_uid = res_1.get("requestUid", "")

    # getting data from API 2
    res_2 = client.api_request_absolute("GET", f"/v3/actions/requests/unenroll/{request_uid}")
    request_data_outputs = parse_device_unenroll_request_data_response(res_2)

    # getting data from API 3
    payload = {"deviceUids": device_ids}
    res_3 = client.api_request_absolute("POST", "/v3/actions/unenroll/get-actions", body=payload)

    devices_data_outputs = parse_device_unenroll_response(res_3, request_uid)
    human_readable = tableToMarkdown(
        f"{INTEGRATION} Unenroll request data:", request_data_outputs, headers=["RequestUid", "RequestStatus"], removeNull=True
    )

    human_readable += tableToMarkdown(
        f"{INTEGRATION} unenrolled devices:", devices_data_outputs, headers=["DeviceUid", "DeviceName", "ESN"], removeNull=True
    )

    outputs = request_data_outputs
    outputs["Devices"] = devices_data_outputs
    return CommandResults(
        outputs_prefix="Absolute.DeviceUnenroll", outputs=outputs, readable_output=human_readable, raw_response=outputs
    )


def add_list_to_filter_string(field_name, list_of_values, query):
    if not list_of_values:
        return query

    query_list = []
    list_of_values.sort()
    query_list.extend([f"substringof('{value}',{field_name})" for value in list_of_values])
    new_query = " or ".join(query_list)

    if query:
        return f"{query} or {new_query}"
    return new_query


def add_value_to_filter_string(field_name, value, query):
    if not value:
        return query
    if query:
        # if there is already a query, we should add 'or' before appending the new query
        return f"{query} or {field_name} eq '{value}'"

    return f"{field_name} eq '{value}'"


def create_filter_query_from_args_helper(args, arg_name, source_name, query):
    list_no_duplicates = remove_duplicates_from_list_arg(args, arg_name)
    query = add_list_to_filter_string(source_name, list_no_duplicates, query)
    return query


def create_filter_query_from_args(args: dict, change_device_name_to_system: bool = False, change_device_id: bool = False) -> str:
    """
    Args:
        args: args given from the user.
        change_device_name_to_system: True if to filter by "systemName" parameter and False to filter by "deviceName".
        change_device_id: True if to filter by "id" parameter and False to filter by "deviceUid".

    Returns: filter query to send to the API.

    """
    custom_filter = args.get("filter")
    if custom_filter:
        return f"$filter={custom_filter}"
    query = ""

    query = create_filter_query_from_args_helper(args, "account_uids", "accountUid", query)
    query = create_filter_query_from_args_helper(args, "app_names", "appName", query)
    query = create_filter_query_from_args_helper(args, "app_publishers", "appPublisher", query)
    query = create_filter_query_from_args_helper(args, "user_names", "userName", query)
    query = create_filter_query_from_args_helper(args, "user_names", "username", query)
    query = create_filter_query_from_args_helper(args, "os", "osName", query)
    query = create_filter_query_from_args_helper(args, "esn", "esn", query)
    query = create_filter_query_from_args_helper(args, "local_ips", "localIp", query)
    query = create_filter_query_from_args_helper(args, "public_ips", "publicIp", query)

    device_names = remove_duplicates_from_list_arg(args, "device_names")
    if device_names and change_device_name_to_system:
        query = add_list_to_filter_string("systemName", device_names, query)
    else:
        query = add_list_to_filter_string("deviceName", device_names, query)

    device_ids = remove_duplicates_from_list_arg(args, "device_ids")
    if device_ids and change_device_id:
        query = add_list_to_filter_string("id", device_ids, query)
    else:
        query = add_list_to_filter_string("deviceUid", device_ids, query)

    if args.get("agent_status"):
        agent_status = ABSOLUTE_AGET_STATUS[args.get("agent_status")]  # type: ignore
        query = add_value_to_filter_string("agentStatus", agent_status, query)

    os_name = args.get("os_name")
    query = add_value_to_filter_string("operatingSystem.name", os_name, query)

    os_version = args.get("os_version")
    query = add_value_to_filter_string("operatingSystem.version", os_version, query)

    manufacturer = args.get("manufacturer")
    query = add_value_to_filter_string("systemManufacturer", manufacturer, query)

    model = args.get("model")
    query = add_value_to_filter_string("systemModel", model, query)

    return f"$filter={query}"


def parse_return_fields(return_fields: str, query: str):
    """
    Returns values only for the fields that meet the specified criteria in the query.
    All other fields are returned with a null value.
    """
    if not return_fields:
        return query

    if query:
        return f"{query}&$select={return_fields}"
    return f"$select={return_fields}"


def parse_device_list_response(response: list, keep_os_in_list=True, application_list=False) -> list:
    """Parse the device list response

    Args:
        response (list): The response to parse
    """
    parsed_response = []
    for device in response:
        parsed_device = {}
        for key, val in device.items():
            if val:
                if key == "os" and not keep_os_in_list:
                    parsed_device["osName"] = val.get("name")
                elif key == "deviceUid" and application_list:
                    parsed_device["DeviceUid"] = val
                elif key in DEVICE_OUTPUT_TO_XSOAR_CONTEXT_PATH:
                    parsed_device[DEVICE_OUTPUT_TO_XSOAR_CONTEXT_PATH[key]] = val
                elif key == "espInfo":
                    parsed_device["encryptionStatus"] = val.get("encryptionStatus")
                elif key == "lastConnectedDateTimeUtc":
                    parsed_device["LastConnectedUtc"] = val
                elif key == "installDateTimeUtc":
                    parsed_device["InstallDate"] = val
                elif key == "firstDetectDateTimeUtc":
                    parsed_device["FirstDetectUtc"] = val
                elif key == "lastScanDateTimeUtc":
                    parsed_device["LastScanTimeUtc"] = val
                elif key == "operatingSystem":
                    parsed_device["Os"] = {
                        "architecture": val.get("architecture"),
                        "currentBuild": val.get("currentBuild"),
                        "installDate": val.get("installDateTimeUtc"),
                        "lastBootTime": val.get("lastBootDateTimeUtc"),
                        "name": val.get("name"),
                        "productKey": val.get("productKey"),
                        "serialNumber": val.get("serialNumber"),
                        "version": val.get("version"),
                    }
                else:
                    parsed_device[key[0].upper() + key[1:]] = val
        parsed_response.append(parsed_device)

    return parsed_response


def parse_geo_location_outputs(response):
    parsed_response = []
    for device in response:
        parsed_device = {}
        geo_data = device.get("geoData", {}).get("location", {})
        parsed_device["Coordinates"] = geo_data.get("point", {}).get("coordinates")
        parsed_device["LocationTechnology"] = geo_data.get("locationTechnology")
        parsed_device["Accuracy"] = geo_data.get("accuracy")
        parsed_device["LastUpdate"] = geo_data.get("lastUpdateDateTimeUtc")
        parsed_device["City"] = geo_data.get("geoAddress", {}).get("city")
        parsed_device["State"] = geo_data.get("geoAddress", {}).get("state")
        parsed_device["CountryCode"] = geo_data.get("geoAddress", {}).get("countryCode")
        parsed_device["Country"] = geo_data.get("geoAddress", {}).get("country")
        parsed_device["ID"] = device.get("deviceUid")

        remove_nulls_from_dictionary(parsed_device)
        parsed_response.append(parsed_device)

    if len(parsed_response) == 1:
        return parsed_response[0]
    return parsed_response


def get_device_application_list_command(args, client) -> CommandResults:
    """Lists devices applications"""
    page = arg_to_number(args.get("page", 0))
    limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))

    query_string = create_filter_query_from_args(args)
    query_string = parse_return_fields(args.get("return_fields"), query_string)

    try:
        res = client.api_request_absolute(
            "GET",
            "/v3/reporting/applications-advanced",
            query_string=query_string,
            page=page,
            page_size=limit,
            specific_page=True,
        )
    except Exception as e:
        if "Your request is invalid, please contact us for details." in str(e):
            raise Exception(f"Error in request - make sure your filter is in accordance to the Absolute documentation.\n{e}")
        else:
            raise e

    if res:
        outputs = parse_device_list_response(res, application_list=True)
        human_readable = tableToMarkdown(f"{INTEGRATION} device applications list:", outputs, removeNull=True)
        human_readable += f"\nAbove results are with page number: {page} and with limit: {limit}."
        return CommandResults(
            outputs_prefix="Absolute.DeviceApplication",
            outputs=outputs,
            outputs_key_field="Appid",
            readable_output=human_readable,
            raw_response=res,
        )
    else:
        return CommandResults(readable_output=f"No applications found in {INTEGRATION} for the given filters: {args}")


def device_list_command(args, client) -> CommandResults:
    """Lists devices"""
    page = arg_to_number(args.get("page", 0))
    limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))

    query_string = create_filter_query_from_args(args, change_device_name_to_system=True)
    query_string = parse_return_fields(",".join(DEVICE_LIST_RETURN_FIELDS), query_string)
    try:
        res = client.api_request_absolute(
            "GET", "/v3/reporting/devices", query_string=query_string, page=page, page_size=limit, specific_page=True
        )
    except Exception as e:
        if "Your request is invalid, please contact us for details." in str(e):
            raise Exception(f"Error in request - make sure your filter is in accordance to the Absolute documentation.\n{e}")
        else:
            raise e

    if res:
        outputs = parse_device_list_response(copy.deepcopy(res), keep_os_in_list=False)
        human_readable = tableToMarkdown(
            f"{INTEGRATION} devices list:",
            outputs,
            headers=["Id", "LastConnectedUtc", "LocalIp", "AgentStatus", "Esn", "FullSystemName"],
            removeNull=True,
        )
        human_readable += f"\nAbove results are with page number: {page} and with limit: {limit}."
        return CommandResults(
            outputs_prefix="Absolute.Device",
            outputs=outputs,
            outputs_key_field="Id",
            readable_output=human_readable,
            raw_response=res,
        )
    else:
        return CommandResults(readable_output=f"No devices found in {INTEGRATION} for the given filters: {args}")


def get_device_command(args, client) -> CommandResults:
    """Gets a device"""

    if not ("device_ids" in args or "device_names" in args or "local_ips" in args or "public_ips" in args):
        raise_demisto_exception(
            "at least one of the commands args (device_ids, device_names, local_ips, public_ips must be provided."
        )

    query_string = create_filter_query_from_args(args, change_device_name_to_system=True, change_device_id=True)
    custom_fields_to_return = remove_duplicates_from_list_arg(args, "fields")
    if custom_fields_to_return:
        custom_fields_to_return.extend(DEVICE_GET_COMMAND_RETURN_FIELDS)
        query_string = parse_return_fields(",".join(custom_fields_to_return), query_string)
    else:
        query_string = parse_return_fields(",".join(DEVICE_GET_COMMAND_RETURN_FIELDS), query_string)

    res = client.api_request_absolute(
        "GET",
        "/v3/reporting/devices",
        query_string=query_string,
        page_size=MAX_PAGE_SIZE,
    )
    if res:
        outputs = parse_device_list_response(copy.deepcopy(res))
        human_readable = tableToMarkdown(f"{INTEGRATION} devices list:", outputs, removeNull=True)
        return CommandResults(
            outputs_prefix="Absolute.Device",
            outputs=outputs,
            outputs_key_field="Id",
            readable_output=human_readable,
            raw_response=res,
        )
    else:
        return CommandResults(readable_output=f"No devices found in {INTEGRATION} for the given filters: {args}")


def get_device_location_command(args, client) -> CommandResults:
    query_string = create_filter_query_from_args(args, change_device_id=True)
    query_string = parse_return_fields(",".join(DEVICE_GET_LOCATION_COMMAND_RETURN_FIELDS), query_string)

    res = client.api_request_absolute("GET", "/v3/reporting/devices", query_string=query_string)
    if res:
        outputs = parse_geo_location_outputs(copy.deepcopy(res))
        human_readable = tableToMarkdown(f"{INTEGRATION} devices location:", outputs, removeNull=True)
        return CommandResults(
            outputs_prefix="Absolute.LocationReport", outputs=outputs, readable_output=human_readable, raw_response=res
        )
    else:
        return CommandResults(readable_output=f"No device locations found in {INTEGRATION} for the given filters: {args}")


""" EVENT COLLECTOR """


def fetch_events(client: ClientV3, fetch_limit: int, last_run: Dict[str, Any]) -> tuple[List[Dict[str, Any]], Dict[str, Any]]:
    """
    Fetches events from the API client, with time window and duplication handling.
    The function using the client to fetch events, and then helper function to handle duplication,
    add time field and calculate the new latest events.

    Args:
        client (ClientV3): The client object used for fetching events.
        fetch_limit (int): The maximum number of events to fetch.
        last_run (Dict[str, Any]): A dictionary containing the last run information, including the latest events time and
            latest events ID.

    Returns:
        Tuple[List[Dict[str, Any]], Dict[str, Any]]: A tuple containing the fetched events and the updated last run
            information.
    """
    latest_events_time = last_run.get("latest_events_time")
    end_date = datetime.utcnow()
    start_date: datetime = (
        datetime.strptime(latest_events_time, "%Y-%m-%dT%H:%M:%S.%fZ")
        if latest_events_time
        else (end_date - timedelta(minutes=1))
    )

    # Adjust fetch_limit to ensure that the number of events fetched matches the user's desired amount.
    fetch_limit += len(last_run.get("latest_events_id", []))
    demisto.debug(f"Starting new fetch: {fetch_limit=}, {start_date=}, {end_date=}, {last_run=}")

    all_events = client.fetch_events_between_dates(fetch_limit, start_date, end_date)
    events, updated_last_run = process_events(all_events, last_run)
    demisto.debug(f'fetch_events: {updated_last_run.get("latest_events_id")=}, {updated_last_run.get("latest_events_time")=}')

    return events, updated_last_run


def process_events(
    events: List[Dict[str, Any]], last_run: Dict[str, Any], should_get_latest_events: bool = True
) -> tuple[List[Dict[str, Any]], Dict[str, Any]]:
    """
    Processes events by handling duplication, adding a time field, and optionally getting the latest events ID and time.

    Args:
        events (List[Dict[str, Any]]): The list of events to be processed.
        last_run (Dict[str, Any]): The updated last run information.
        should_get_latest_events (bool, optional): A flag indicating whether to get the latest events ID. Defaults to True.

    Returns:
        Tuple[List[Dict[str, Any]], [Dict[str, Any]]]: A tuple containing the processed events and the updated last run object.

    """
    demisto.debug(
        f"Handle duplicate events, adding _time field to events and optionally getting the latest events id and time."
        f" {events=}, {last_run=}"
    )
    last_run_latest_events_id = last_run.get("latest_events_id", [])
    earliest_event_time = last_run.get("latest_events_time", "")
    latest_event_time = events[-1].get("createdDateTimeUtc") if events else ""
    latest_events_id = []
    filtered_events = []
    for event in events:
        event_time = event.get("createdDateTimeUtc")
        # handle duplication
        if event_time == earliest_event_time and event.get("id") in last_run_latest_events_id:
            continue
        # adding time field
        event["_time"] = event.get("eventDateTimeUtc")
        # latest events batch
        if should_get_latest_events and event_time == latest_event_time:
            latest_events_id.append(event.get("id"))
        filtered_events.append(event)

    return filtered_events, {
        "latest_events_id": latest_events_id if latest_events_id else last_run_latest_events_id,
        "latest_events_time": latest_event_time,
    }


def get_events(client, args) -> tuple[List[Dict[str, Any]], CommandResults]:
    start_date = arg_to_datetime(args.get("start_date", "one minute ago"))
    end_date = arg_to_datetime(args.get("end_date", "now"))
    fetch_limit = int(args.get("limit", 50))
    if (start_date and end_date) and (start_date > end_date):
        raise ValueError("Start date is greater than the end date. Please provide valid dates.")

    events = client.fetch_events_between_dates(fetch_limit, start_date, end_date)
    demisto.debug(f"get_events: Found {len(events)} events.")
    if events:
        events, _ = process_events(events, {}, should_get_latest_events=False)
    return events, CommandResults(readable_output=tableToMarkdown("Events", t=events))


""" MAIN FUNCTION """


def main() -> None:  # pragma: no cover
    params = demisto.params()
    try:
        base_url = validate_absolute_api_url(params.get("url", ""))
        token_id = params.get("credentials", {}).get("identifier")
        secret_key = params.get("credentials", {}).get("password")
        verify_certificate = not params.get("insecure", False)
        proxy = params.get("proxy", False)

        demisto.debug(f"Command being called is {demisto.command()}")
        demisto.debug(f"base_url: {base_url}")
        client_v3 = ClientV3(base_url=base_url, verify=verify_certificate, proxy=proxy, token_id=token_id, secret_key=secret_key)

        args = demisto.args()
        if demisto.command() == "test-module":
            return_results(test_module(client_v3))

        elif demisto.command() == "absolute-custom-device-field-list":
            return_results(get_custom_device_field_list_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-custom-device-field-update":
            return_results(update_custom_device_field_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-request":
            return_results(device_freeze_request_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-remove-freeze-request":
            return_results(remove_device_freeze_request_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-request-get":
            return_results(get_device_freeze_request_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-message-list":
            return_results(list_device_freeze_message_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-message-create":
            return_results(create_device_freeze_message_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-message-update":
            return_results(update_device_freeze_message_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-freeze-message-delete":
            return_results(delete_device_freeze_message_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-unenroll":
            return_results(device_unenroll_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-application-list":
            return_results(get_device_application_list_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-list":
            return_results(device_list_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-get":
            return_results(get_device_command(args=args, client=client_v3))

        elif demisto.command() == "absolute-device-location-get":
            return_results(get_device_location_command(args=args, client=client_v3))

        elif demisto.command() == "fetch-events":
            max_events_per_fetch = arg_to_number(params.get("max_events_per_fetch", 10000)) or 10000
            events, last_run_object = fetch_events(client_v3, max_events_per_fetch, demisto.getLastRun())
            if events:
                send_events_to_xsiam(events=events, vendor="Absolute", product="Secure Endpoint")
                demisto.setLastRun(last_run_object)

        elif demisto.command() == "absolute-device-get-events":
            demisto.debug(f"Fetching Absolute Device events with the following parameters: {args}")
            should_push_events = argToBoolean(args.get("should_push_events", False))
            events, command_result = get_events(client=client_v3, args=args)
            if should_push_events and events:
                send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT)
            return_results(command_result)

        else:
            raise NotImplementedError(f"{demisto.command()} is not an existing {INTEGRATION} command.")

    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """

if __name__ in ("__main__", "__builtin__", "builtins"):  # pragma: no cover
    main()