QuestKace
Use the Comprehensive Quest KACE solution to Provision, manage, secure, and service all network-connected devices.
Case Management · Quest Kace
Details
| ID | QuestKace |
|---|---|
| Provider | Quest Software |
| Category | Case Management |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
This is the Quest KACE integration.
This integration was integrated and tested with version v10.0.290 of QuestKace
Limitations
For kace-ticket-create and kace-ticket-update, When the queue_id is not the default:
Status,Category,Priority,Impact - values from the option list might cause an error as they correspond to different values.
If a value not from the list will be inserted - This value will pass to the API as is.
e.g. kace-ticket-create status=”Opened” impact=13 Priority=25
Tickets custom fields will not be returned in the kace-tickets-list command and not in fetch incidents due to API limitation.
Custom fields of tickets are represented by their custom number and not but their display name. e.g. For custom field: custom_1 with display name: date, the command should be !kace-ticket-create custom_fields=custom_1=testfromdemisto
Configure QuestKace in Cortex
| Parameter | Description | Required |
|---|---|---|
| url | Quest KACE URL, in the format: https://company.works.com/ |
True |
| credentials | Username | True |
| isFetch | Fetch incidents | False |
| fetch_time | First fetch time range (<number> <time unit>, e.g., 1 hour, 30 minutes) | False |
| fetch_shaping | Shaping query parameter for tickets | False |
| fetch_filter | Filter for the tickets | False |
| fetch_limit | Fetch limit per query | False |
| fetch_queue_id | Queue number for fetch query | False |
| incidentType | Incident type | False |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | 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.
kace-machines-list
Returns a list of all machines in system.
Base Command
kace-machines-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of machines to return. The default value is 50. | Optional |
| custom_filter | Filter for the query. Each filter is specified by an optional entity name, a field name, an operator, and a value. e.g. “title eq test” / “id gt 1 / hd_queue_id in 1;2;3” . Combination of filters is seperated by comma. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| QuestKace.Machine.ID | String | ID of machine |
| QuestKace.Machine.Modified | String | Last modified date of the machine. |
| QuestKace.Machine.Created | String | Created date of the machine in KACE system. |
| QuestKace.Machine.User | String | User of the machine. |
| QuestKace.Machine.Name | String | Name of the machine. |
| QuestKace.Machine.IP | String | IP address of the machine. |
| QuestKace.Machine.OSName | String | OS name of the machine. |
| QuestKace.Machine.OSNumber | String | Number of operating systems of the machine. |
| QuestKace.Machine.LastInventory | String | Last inventory date of the machine. |
| QuestKace.Machine.LastSync | String | Last sync date of the machine. |
| QuestKace.Machine.RamTotal | String | Total RAM of the machine in bytes. |
| QuestKace.Machine.RamUsed | String | Used RAM of the machine. |
| QuestKace.Machine.RamMax | String | Maximum RAM of the machine. |
| QuestKace.Machine.BiosIdentificationCode | String | BIOS identification code of the machine. |
| QuestKace.Machine.SoundDevices | String | Connected sound devices of the machine. |
| QuestKace.Machine.CdromDevices | String | Connected CD-ROM devices to the machine. |
| QuestKace.Machine.VideoControllers | String | Video controllers of the machine. |
| QuestKace.Machine.Monitor | String | Monitor of the machine. |
| QuestKace.Machine.RegistrySize | String | Registry size of the machine. |
| QuestKace.Machine.RegistryMaxSize | String | Maximum size of the registry of the machine. |
| QuestKace.Machine.PagefileSize | String | Size of the page file of the machine. |
| QuestKace.Machine.PagefileMaxSize | String | Maximum size of the page file of the machine. |
| QuestKace.Machine.ManualEntry | String | Number of manual entries to the machine. |
Command Example
!kace-machines-list custom_filter="id gt 1"
Context Example
{
"QuestKace": {
"Machine": null
}
}
Human Readable Output
Quest Kace Machines
No entries.
kace-assets-list
Returns a list of all assets in Quest KACE.
Base Command
kace-assets-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of assets to return. The default value is 50. | Optional |
| custom_filter | Filter for the query. Each filter is specified by an optional entity name, a field name, an operator, and a value. e.g. “title eq test” / “id gt 1 / hd_queue_id in 1;2;3” . Combination of filters is seperated by comma. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| QuestKace.Asset.ID | Number | ID of the asset. |
| QuestKace.Asset.AssetTypeID | Number | Type ID of the asset. |
| QuestKace.Asset.Name | String | Name of the asset. |
| QuestKace.Asset.AssetDataID | Number | Data ID of the asset. |
| QuestKace.Asset.OwnerID | Number | Owner ID of the asset. |
| QuestKace.Asset.Modified | String | Last modified date of the asset. |
| QuestKace.Asset.Created | String | Created date of the asset. |
| QuestKace.Asset.MappedID | Number | Mapped ID of the asset. |
| QuestKace.Asset.AssetClassID | Number | Class ID of the asset. |
| QuestKace.Asset.Archieve | String | Archive of the asset. |
| QuestKace.Asset.AssetStatusID | Number | Status ID of the asset. |
| QuestKace.Asset.AssetTypeName | String | Type name of the asset. |
Command Example
!kace-assets-list custom_filter="name eq Mac"
Context Example
{
"QuestKace": {
"Asset": {
"Archive": "",
"AssetClassID": 10000,
"AssetDataID": 1,
"AssetStatusID": 0,
"AssetSubtypeName": "Laser Printer: Color",
"AssetTypeID": 5,
"AssetTypeName": "Device",
"Created": "2020-06-09 03:57:30",
"ID": 2,
"MappedID": 0,
"Modified": "2020-06-09 03:57:30",
"Name": "Mac",
"OwnerID": 0
}
}
}
Human Readable Output
Quest Kace Assets
ID Name Created Modified OwnerID MappedID AssetClassID AssetDataID AssetStatusID AssetTypeID AssetTypeName 2 Mac 2020-06-09 03:57:30 2020-06-09 03:57:30 0 0 10000 1 0 5 Device
kace-queues-list
Returns a list of all queues in Quest KACE.
Base Command
kace-queues-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of tickets to return. The default value is 50. | Optional |
| custom_filter | Filter for the query. Each filter is specified by an optional entity name, a field name, an operator, and a value. e.g. “title eq test” / “id gt 1 / hd_queue_id in 1;2;3” . Combination of filters is seperated by comma. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| QuestKace.Queue.ID | Number | ID of the queue. |
| QuestKace.Queue.Name | String | Name of the queue. |
| QuestKace.Queue.Field.ID | Number | ID of the field of the queue. |
| QuestKace.Queue.Field.HdQueueID | Number | Queue ID of the field. |
| QuestKace.Queue.Field.Name | String | Name of the field. |
| QuestKace.Queue.Field.HdTicketFieldName | String | View field name. |
| QuestKace.Queue.Field.Ordinal | Number | Ordinal of the field. |
| QuestKace.Queue.Field.RequiredState | String | Required state of the field, if exists. |
| QuestKace.Queue.Field.FieldLabel | String | Field label. |
| QuestKace.Queue.Field.Visible | String | Visibility of the field. |
Command Example
!kace-queues-list custom_filter="id neq 1"
Context Example
{
"QuestKace": {
"Queue": {
"Fields": [
{
"FieldLabel": "Please tell us about your recent help desk experience",
"HdQueueID": 3,
"HdTicketFieldName": "sat_survey",
"ID": 36,
"Name": "SAT_SURVEY",
"Ordinal": 0,
"RequiredState": "none",
"Visible": "usermodify"
},
{
"FieldLabel": "Title",
"HdQueueID": 3,
"HdTicketFieldName": "title",
"ID": 37,
"Name": "TITLE",
"Ordinal": 1,
"RequiredState": "all",
"Visible": "usercreate"
},
{
"FieldLabel": "Summary",
"HdQueueID": 3,
"HdTicketFieldName": "summary",
"ID": 38,
"Name": "SUMMARY",
"Ordinal": 2,
"RequiredState": "none",
"Visible": "usercreate"
},
{
"FieldLabel": "Impact",
"HdQueueID": 3,
"HdTicketFieldName": "impact_id",
"ID": 39,
"Name": "IMPACT",
"Ordinal": 3,
"RequiredState": "none",
"Visible": "usercreate"
},
{
"FieldLabel": "Category",
"HdQueueID": 3,
"HdTicketFieldName": "category_id",
"ID": 40,
"Name": "CATEGORY",
"Ordinal": 4,
"RequiredState": "none",
"Visible": "usercreate"
},
{
"FieldLabel": "Status",
"HdQueueID": 3,
"HdTicketFieldName": "status_id",
"ID": 41,
"Name": "STATUS",
"Ordinal": 5,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Priority",
"HdQueueID": 3,
"HdTicketFieldName": "priority_id",
"ID": 42,
"Name": "PRIORITY",
"Ordinal": 6,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Owner",
"HdQueueID": 3,
"HdTicketFieldName": "owner_id",
"ID": 43,
"Name": "OWNER",
"Ordinal": 7,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Device",
"HdQueueID": 3,
"HdTicketFieldName": "machine_id",
"ID": 44,
"Name": "MACHINE",
"Ordinal": 8,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Asset",
"HdQueueID": 3,
"HdTicketFieldName": "asset_id",
"ID": 45,
"Name": "ASSET",
"Ordinal": 9,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Due Date",
"HdQueueID": 3,
"HdTicketFieldName": "due_date",
"ID": 61,
"Name": "DUE_DATE",
"Ordinal": 25,
"RequiredState": "none",
"Visible": "userhidden"
},
{
"FieldLabel": "CC List",
"HdQueueID": 3,
"HdTicketFieldName": "cc_list",
"ID": 62,
"Name": "CC_LIST",
"Ordinal": 26,
"RequiredState": "none",
"Visible": "userhidden"
},
{
"FieldLabel": "Created",
"HdQueueID": 3,
"ID": 63,
"Name": "CREATED",
"Ordinal": 27,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Modified",
"HdQueueID": 3,
"ID": 64,
"Name": "MODIFIED",
"Ordinal": 28,
"RequiredState": "none",
"Visible": "uservisible"
},
{
"FieldLabel": "Submitter",
"HdQueueID": 3,
"HdTicketFieldName": "submitter_id",
"ID": 65,
"Name": "SUBMITTER",
"Ordinal": 29,
"RequiredState": "none",
"Visible": "usercreate"
},
{
"FieldLabel": "See Also",
"HdQueueID": 3,
"HdTicketFieldName": "related_ticket_ids",
"ID": 68,
"Name": "SEE_ALSO",
"Ordinal": 32,
"RequiredState": "none",
"Visible": "userhidden"
},
{
"FieldLabel": "Referrers",
"HdQueueID": 3,
"ID": 69,
"Name": "REFERRERS",
"Ordinal": 33,
"RequiredState": "none",
"Visible": "userhidden"
},
{
"FieldLabel": "Resolution",
"HdQueueID": 3,
"HdTicketFieldName": "resolution",
"ID": 70,
"Name": "RESOLUTION",
"Ordinal": 34,
"RequiredState": "none",
"Visible": "uservisible"
}
],
"ID": 3,
"Name": "New Queue 2"
}
}
}
Human Readable Output
Quest Kace Queues
ID Name Fields 3 New Queue 2 {‘ID’: 36, ‘HdQueueID’: 3, ‘Name’: ‘SAT_SURVEY’, ‘HdTicketFieldName’: ‘sat_survey’, ‘Ordinal’: 0, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Please tell us about your recent help desk experience’, ‘Visible’: ‘usermodify’},
{‘ID’: 37, ‘HdQueueID’: 3, ‘Name’: ‘TITLE’, ‘HdTicketFieldName’: ‘title’, ‘Ordinal’: 1, ‘RequiredState’: ‘all’, ‘FieldLabel’: ‘Title’, ‘Visible’: ‘usercreate’},
{‘ID’: 38, ‘HdQueueID’: 3, ‘Name’: ‘SUMMARY’, ‘HdTicketFieldName’: ‘summary’, ‘Ordinal’: 2, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Summary’, ‘Visible’: ‘usercreate’},
{‘ID’: 39, ‘HdQueueID’: 3, ‘Name’: ‘IMPACT’, ‘HdTicketFieldName’: ‘impact_id’, ‘Ordinal’: 3, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Impact’, ‘Visible’: ‘usercreate’},
{‘ID’: 40, ‘HdQueueID’: 3, ‘Name’: ‘CATEGORY’, ‘HdTicketFieldName’: ‘category_id’, ‘Ordinal’: 4, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Category’, ‘Visible’: ‘usercreate’},
{‘ID’: 41, ‘HdQueueID’: 3, ‘Name’: ‘STATUS’, ‘HdTicketFieldName’: ‘status_id’, ‘Ordinal’: 5, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Status’, ‘Visible’: ‘uservisible’},
{‘ID’: 42, ‘HdQueueID’: 3, ‘Name’: ‘PRIORITY’, ‘HdTicketFieldName’: ‘priority_id’, ‘Ordinal’: 6, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Priority’, ‘Visible’: ‘uservisible’},
{‘ID’: 43, ‘HdQueueID’: 3, ‘Name’: ‘OWNER’, ‘HdTicketFieldName’: ‘owner_id’, ‘Ordinal’: 7, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Owner’, ‘Visible’: ‘uservisible’},
{‘ID’: 44, ‘HdQueueID’: 3, ‘Name’: ‘MACHINE’, ‘HdTicketFieldName’: ‘machine_id’, ‘Ordinal’: 8, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Device’, ‘Visible’: ‘uservisible’},
{‘ID’: 45, ‘HdQueueID’: 3, ‘Name’: ‘ASSET’, ‘HdTicketFieldName’: ‘asset_id’, ‘Ordinal’: 9, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Asset’, ‘Visible’: ‘uservisible’},
{‘ID’: 61, ‘HdQueueID’: 3, ‘Name’: ‘DUE_DATE’, ‘HdTicketFieldName’: ‘due_date’, ‘Ordinal’: 25, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Due Date’, ‘Visible’: ‘userhidden’},
{‘ID’: 62, ‘HdQueueID’: 3, ‘Name’: ‘CC_LIST’, ‘HdTicketFieldName’: ‘cc_list’, ‘Ordinal’: 26, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘CC List’, ‘Visible’: ‘userhidden’},
{‘ID’: 63, ‘HdQueueID’: 3, ‘Name’: ‘CREATED’, ‘Ordinal’: 27, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Created’, ‘Visible’: ‘uservisible’},
{‘ID’: 64, ‘HdQueueID’: 3, ‘Name’: ‘MODIFIED’, ‘Ordinal’: 28, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Modified’, ‘Visible’: ‘uservisible’},
{‘ID’: 65, ‘HdQueueID’: 3, ‘Name’: ‘SUBMITTER’, ‘HdTicketFieldName’: ‘submitter_id’, ‘Ordinal’: 29, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Submitter’, ‘Visible’: ‘usercreate’},
{‘ID’: 68, ‘HdQueueID’: 3, ‘Name’: ‘SEE_ALSO’, ‘HdTicketFieldName’: ‘related_ticket_ids’, ‘Ordinal’: 32, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘See Also’, ‘Visible’: ‘userhidden’},
{‘ID’: 69, ‘HdQueueID’: 3, ‘Name’: ‘REFERRERS’, ‘Ordinal’: 33, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Referrers’, ‘Visible’: ‘userhidden’},
{‘ID’: 70, ‘HdQueueID’: 3, ‘Name’: ‘RESOLUTION’, ‘HdTicketFieldName’: ‘resolution’, ‘Ordinal’: 34, ‘RequiredState’: ‘none’, ‘FieldLabel’: ‘Resolution’, ‘Visible’: ‘uservisible’}
kace-tickets-list
Returns a list of all tickets in Quest KACE.
Base Command
kace-tickets-list
Input
| Argument Name | Description | Required |
|---|---|---|
| custom_shaping | The shaping query parameter limits the amount of returned data is specified. The returned fields for each associated entity is controlled by two query values. The first is the name of the entity, while the second half of the pair is the associated level. e.g. “submitter all, asset limited”. |
Optional |
| limit | The maximum number of tickets to return. The default value is 50. | Optional |
| custom_filter | Filter for the query. Each filter is specified by an optional entity name, a field name, an operator, and a value. e.g. “title eq test” / “id gt 1 / hd_queue_id in 1;2;3” . Combination of filters is seperated by comma. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| QuestKace.Ticket.Submitter.ID | Number | Submitter id of the ticket. |
| QuestKace.Ticket.Submitter.UserName | String | Submitter user name of the ticket. |
| QuestKace.Ticket.Submitter.Email | String | Email address of user that submitted the email. |
| QuestKace.Ticket.Submitter.FullName | String | Full name of the user that submitted the ticket. |
| QuestKace.Ticket.Asset.ID | Number | ID of the asset of the ticket. |
| QuestKace.Ticket.Asset.AssetTypeId | Number | Asset type ID of the ticket. |
| QuestKace.Ticket.Asset.Name | String | Name of the asset of the ticket. |
| QuestKace.Ticket.Asset.OwnerId | Number | Owner ID of the asset of the ticket. |
| QuestKace.Ticket.Asset.AssetClassId | Number | Asset class id of the ticket. |
| QuestKace.Ticket.Machine.ID | Number | ID of the machine of the ticket. |
| QuestKace.Ticket.Machine.Name | String | Name of the machine of the ticket. |
| QuestKace.Ticket.Priority.ID | Number | Priority id of the ticket. |
| QuestKace.Ticket.Priority.Name | String | Priority name of the ticket. |
| QuestKace.Ticket.Priority.Ordinal | Number | Priority ordinal of the ticket. |
| QuestKace.Ticket.Priority.Color | String | Priority color of the ticket. |
| QuestKace.Ticket.Priority.IsSlaEnable | Number | Whether SLA is enabled on the priority of the ticket. |
| QuestKace.Ticket.Category.ID | Number | Category ID of the ticket. |
| QuestKace.Ticket.Category.Name | String | Category name of the ticket. |
| QuestKace.Ticket.Impact.ID | Number | ID of the impact of the ticket. |
| QuestKace.Ticket.Impact.Ordinal | Number | Ordinal of the impact of the ticket. |
| QuestKace.Ticket.Impact.Name | String | Name of the impact of the ticket. |
| QuestKace.Ticket.Status.ID | Number | ID of the status of the ticket. |
| QuestKace.Ticket.Status.Name | String | Name of the status of the tickets. |
| QuestKace.Ticket.Status.Ordinal | Number | Ordinal of the status of the ticket. |
| QuestKace.Ticket.Status.State | String | State of the status of the ticket. |
| QuestKace.Ticket.ID | Number | ID of the ticket. |
| QuestKace.Ticket.Title | String | Title of the ticket. |
| QuestKace.Ticket.Summary | String | Summary of the ticket. |
| QuestKace.Ticket.Modified | String | Last modified date of the ticket. |
| QuestKace.Ticket.Created | String | Created date of the ticket. |
| QuestKace.Ticket.HdQueueID | Number | Queue number that the ticket is related to. |
| QuestKace.Ticket.CcList | String | CC list of the ticket. |
| QuestKace.Ticket.IsManualDueDate | Number | Whether the due date is manual. |
| QuestKace.Ticket.Resolution | String | Resolution of the ticket. |
| QuestKace.Ticket.DueDate | String | Dua date of the ticket. |
Command Example
!kace-tickets-list custom_shaping="hd_ticket all,submitter limited,owner limited, asset limited,machine limited,priority limited,category limited, impact limited,status limited"
Context Example
{
"QuestKace": {
"Ticket": [
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 05:54:42",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 11,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 05:54:42",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 4,
"Name": "New",
"Ordinal": 0,
"State": "stalled"
},
"Summary": "",
"Title": "Untitled"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 05:55:43",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 12,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 05:55:43",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 4,
"Name": "New",
"Ordinal": 0,
"State": "stalled"
},
"Summary": "",
"Title": "test num 2 from demisto"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 05:56:08",
"HdQueueID": 1,
"ID": 13,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 06:16:17",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 2,
"Name": "Closed",
"Ordinal": 3,
"State": "closed"
},
"Summary": "",
"Title": "test num 4 from demisto"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 06:15:48",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 14,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 06:15:48",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 2,
"Name": "Closed",
"Ordinal": 3,
"State": "closed"
},
"Summary": "dont know",
"Title": "test num 3 from demisto"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 07:01:12",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 15,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 07:01:12",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 4,
"Name": "New",
"Ordinal": 0,
"State": "stalled"
},
"Submitter": {
"Email": "tmalache@example.com",
"FullName": "admin",
"ID": 10,
"UserName": "admin"
},
"Summary": "",
"Title": "TestCustomFieldsUI"
},
{
"Category": {
"ID": 3,
"Name": "Hardware"
},
"CcList": "",
"Created": "2020-05-19 07:02:30",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 16,
"Impact": {
"ID": 3,
"Name": "1 person inconvenienced",
"Ordinal": 3
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 07:02:30",
"Priority": {
"Color": "gray",
"ID": 3,
"IsSlaEnabled": 0,
"Name": "Low",
"Ordinal": 2
},
"Resolution": "",
"Status": {
"ID": 8,
"Name": "Waiting on Third Party",
"Ordinal": 7,
"State": "stalled"
},
"Summary": "just checking",
"Title": "TATATA"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-05-19 07:03:30",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 17,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-05-19 07:03:30",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 2,
"Name": "Closed",
"Ordinal": 3,
"State": "closed"
},
"Summary": "dont know",
"Title": "test num 3 from demisto"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 03:52:25",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 23,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 03:52:25",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 2,
"Name": "Other"
},
"CcList": "",
"Created": "2020-06-09 03:55:07",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 24,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 03:55:07",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 4,
"Name": "New",
"Ordinal": 0,
"State": "stalled"
},
"Submitter": {
"Email": "tmalache@example.com",
"FullName": "admin",
"ID": 10,
"UserName": "admin"
},
"Summary": "",
"Title": "test"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 03:59:41",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 25,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 03:59:41",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 04:00:03",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 26,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 04:00:03",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 04:00:33",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 27,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 04:00:33",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 04:57:39",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 28,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 04:57:39",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 3,
"Name": "Hardware"
},
"CcList": "",
"Created": "2020-06-09 05:12:37",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 30,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:12:37",
"Priority": {
"Color": "red",
"ID": 2,
"IsSlaEnabled": 0,
"Name": "High",
"Ordinal": 0
},
"Resolution": "",
"Status": {
"ID": 2,
"Name": "Closed",
"Ordinal": 3,
"State": "closed"
},
"Summary": "Test docs",
"Title": "Test"
},
{
"CcList": "",
"Created": "2020-06-09 05:14:51",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 31,
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:14:51",
"Resolution": "",
"Summary": "blah blah",
"Title": "foo foo"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 05:18:10",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 33,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:18:10",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 1,
"Name": "Network"
},
"CcList": "",
"Created": "2020-06-09 05:19:13",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 1,
"ID": 34,
"Impact": {
"ID": 1,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:19:13",
"Priority": {
"Color": "",
"ID": 1,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 1,
"Name": "Opened",
"Ordinal": 1,
"State": "opened"
},
"Summary": "test of Quest Kace integration ticket create",
"Title": "test1"
},
{
"Category": {
"ID": 5,
"Name": "Software"
},
"CcList": "",
"Created": "2020-06-09 05:11:59",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 3,
"ID": 29,
"Impact": {
"ID": 7,
"Name": "1 person cannot work",
"Ordinal": 2
},
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:11:59",
"Priority": {
"Color": "",
"ID": 5,
"IsSlaEnabled": 0,
"Name": "Medium",
"Ordinal": 1
},
"Resolution": "",
"Status": {
"ID": 9,
"Name": "New",
"Ordinal": 0,
"State": "stalled"
},
"Submitter": {
"Email": "tmalache@example.com",
"FullName": "admin",
"ID": 10,
"UserName": "admin"
},
"Summary": "",
"Title": "Tests"
},
{
"CcList": "",
"Created": "2020-06-09 05:14:55",
"DueDate": "0000-00-00 00:00:00",
"HdQueueID": 3,
"ID": 32,
"IsDeleted": false,
"IsManualDueDate": 0,
"Modified": "2020-06-09 05:14:55",
"Resolution": "",
"Summary": "blah blah",
"Title": "foo foo"
}
]
}
}
Human Readable Output
Quest Kace Tickets
ID Title Created Modified HdQueueID DueDate 11 Untitled 2020-05-19 05:54:42 2020-05-19 05:54:42 1 0000-00-00 00:00:00 12 test num 2 from demisto 2020-05-19 05:55:43 2020-05-19 05:55:43 1 0000-00-00 00:00:00 13 test num 4 from demisto 2020-05-19 05:56:08 2020-05-19 06:16:17 1 14 test num 3 from demisto 2020-05-19 06:15:48 2020-05-19 06:15:48 1 0000-00-00 00:00:00 15 TestCustomFieldsUI 2020-05-19 07:01:12 2020-05-19 07:01:12 1 0000-00-00 00:00:00 16 TATATA 2020-05-19 07:02:30 2020-05-19 07:02:30 1 0000-00-00 00:00:00 17 test num 3 from demisto 2020-05-19 07:03:30 2020-05-19 07:03:30 1 0000-00-00 00:00:00 23 test1 2020-06-09 03:52:25 2020-06-09 03:52:25 1 0000-00-00 00:00:00 24 test 2020-06-09 03:55:07 2020-06-09 03:55:07 1 0000-00-00 00:00:00 25 test1 2020-06-09 03:59:41 2020-06-09 03:59:41 1 0000-00-00 00:00:00 26 test1 2020-06-09 04:00:03 2020-06-09 04:00:03 1 0000-00-00 00:00:00 27 test1 2020-06-09 04:00:33 2020-06-09 04:00:33 1 0000-00-00 00:00:00 28 test1 2020-06-09 04:57:39 2020-06-09 04:57:39 1 0000-00-00 00:00:00 30 Test 2020-06-09 05:12:37 2020-06-09 05:12:37 1 0000-00-00 00:00:00 31 foo foo 2020-06-09 05:14:51 2020-06-09 05:14:51 1 0000-00-00 00:00:00 33 test1 2020-06-09 05:18:10 2020-06-09 05:18:10 1 0000-00-00 00:00:00 34 test1 2020-06-09 05:19:13 2020-06-09 05:19:13 1 0000-00-00 00:00:00 29 Tests 2020-06-09 05:11:59 2020-06-09 05:11:59 3 0000-00-00 00:00:00 32 foo foo 2020-06-09 05:14:55 2020-06-09 05:14:55 3 0000-00-00 00:00:00
kace-ticket-create
Creates a new ticket to the system.
Base Command
kace-ticket-create
Input
| Argument Name | Description | Required |
|---|---|---|
| title | Title of the new ticket. | Optional |
| summary | Summary of the new ticket. | Optional |
| impact | Name of the impact of the new ticket. | Optional |
| category | Category of the new ticket. | Optional |
| status | Status of the new ticket. | Optional |
| priority | Priority of the new ticket. | Optional |
| machine | Name of the machine of the new ticket. | Optional |
| asset | Name of the asset of the new ticket. | Optional |
| custom_fields | Custom (user defined) fields in the format - fieldname1=value;fieldname2=value. | Optional |
| queue_id | Queue to which the new ticket should be related. | Optional |
Context Output
There is no context output for this command.
Command Example
!kace-ticket-create title="test1" status="Opened" summary="test of Quest Kace integration ticket create" category="Network" queue_id=1
Context Example
{}
Human Readable Output
New ticket was added successfully, ticket number 35
created due_date hd_queue_id id modified title 2020-06-09 05:19:37 0000-00-00 00:00:00 1 35 2020-06-09 05:19:37 test1
kace-ticket-update
Updates a ticket in the system.
Base Command
kace-ticket-update
Input
| Argument Name | Description | Required |
|---|---|---|
| title | Updated title of the ticket. | Optional |
| summary | Updated summary for the ticket. | Optional |
| impact | Updated impact of the ticket. | Optional |
| category | Updated category of the ticket. | Optional |
| status | Updated status of the ticket. | Optional |
| priority | Updated priority of the ticket. | Optional |
| machine | Updated machine of the ticket. | Optional |
| asset | Updated asset of the ticket. | Optional |
| custom_fields | Custom (user defined) fields in the format - fieldname1=value;fieldname2=value. | Optional |
| ticket_id | ID of the ticket to update. | Required |
Context Output
There is no context output for this command.
Command Example
!kace-ticket-update ticket_id=11 title="updated test1"
Context Example
{}
Human Readable Output
Ticket number 11 was updated successfully
created hd_queue_id id modified title 2020-05-19 05:54:42 1 11 2020-06-09 05:19:38 updated test1
kace-ticket-delete
Deletes the specified ticket.
Base Command
kace-ticket-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| ticket_id | Ticket ID to delete. | Required |
Context Output
There is no context output for this command.
Command Example
!kace-ticket-delete ticket_id=11
Context Example
{}
Human Readable Output
Ticket was deleted successfully. Ticket number 11
Configuration parameters
url— Quest KACE URL, in the format https://company.works.com/ (required)credentials— Username (required)isFetch— Fetch incidentsfetch_time— First fetch time range (<number> <time unit>, e.g., 1 hour, 30 minutes)fetch_shaping— Shaping query parameter for ticketsfetch_filter— Filter for the ticketsfetch_limit— Fetch limit per queryfetch_queue_id— Queue number for fetch queryincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (7)
-
kace-assets-listReturns a list of all assets in Quest KACE.
-
kace-machines-listReturns a list of all machines in system.
-
kace-queues-listReturns a list of all queues in Quest KACE.
-
kace-ticket-createCreates a new ticket to the system.
-
kace-ticket-deleteDeletes the specified ticket.
-
kace-ticket-updateUpdates a ticket in the system.
-
kace-tickets-listReturns a list of all tickets in Quest KACE.
import json from collections.abc import Callable from typing import Any import dateparser import demistomock as demisto # noqa: F401 import requests import urllib3 from CommonServerPython import * # noqa: F401 from CommonServerUserPython import * # Disable insecure warnings urllib3.disable_warnings() # key = field of a ticket , val = dict of (name,id) of options TICKETS_OBJECTS = { "impact": { "1 person cannot work": 1, "Many people cannot work": 2, "1 person inconvenienced": 3, "Many people inconvenienced": 4, }, "category": {"Network": 1, "Other": 2, "Software": 4, "Hardware": 3}, "priority": {"Medium": 1, "High": 2, "Low": 3}, "status": { "Opened": 1, "Closed": 2, "Need More Info": 3, "New": 4, "Reopened": 5, "Waiting Overdue": 6, "Waiting on Customer": 7, "Waiting on Third Party": 8, }, } def convert_snake_to_camel(snake_str: str) -> str: """Convert a specific string of snake case to camel case. Args: snake_str: The string that we would like to convert. Returns: converted string. """ snake_split = snake_str.split("_") camel_string = "".join(map(str.capitalize, snake_split)) camel_string = convert_specific_keys(camel_string) return camel_string def convert_specific_keys(string: str): """ Convert specific keys to demisto standard Args: string: the text to transform Returns: A Demisto output standard string """ if string == "OsName": return "OSName" if string == "OsNumber": return "OSNumber" if string == "Ram total": return "RamTotal" if string == "AssetDataId": return "AssetDataID" if string == "AssetClassId": return "AssetClassID" if string == "AssetStatusId": return "AssetStatusID" if string == "AssetTypeId": return "AssetTypeID" if string == "MappedId": return "MappedID" if string == "OwnerId": return "OwnerID" if string == "HdQueueId": return "HdQueueID" if string == "Ip": return "IP" return string def convert_dict_snake_to_camel(dic: dict) -> dict: """Convert a dictionary of snake case to camel case. Args: dic: The dictionary that we would like to convert. Returns: converted dictionary. """ context_dict = {} for snake_str in dic: if type(dic[snake_str]) is dict: inner_dict = convert_dict_snake_to_camel(dic[snake_str]) camel = convert_snake_to_camel(snake_str) context_dict[camel] = inner_dict elif type(dic[snake_str]) is list: inner_dict = parse_response(dic[snake_str]) camel = convert_snake_to_camel(snake_str) context_dict[camel] = inner_dict elif snake_str in ["id", "Id"]: context_dict["ID"] = dic.get(snake_str, "") else: camel = convert_snake_to_camel(snake_str) context_dict[camel] = dic.get(snake_str, "") return context_dict def parse_response(lst: list): """Convert a Api response to wanted format. Args: lst: A list of dictionaries that return from api call. Returns: converted list of dictionaries from snake case to camel case. """ list_res = [] for dic in lst: context_dict = convert_dict_snake_to_camel(dic) list_res.append(context_dict) return list_res class Client(BaseClient): """ Client to use in the integration, overrides BaseClient. Used for communication with the api. """ def __init__(self, url: str, username: str, password: str, verify: bool, proxy: bool): super().__init__(base_url=f"{url}/api", verify=verify, proxy=proxy) self._url = url self._username = username self._password = password self._token, self._cookie = self.get_token() def get_token(self) -> tuple[str, str]: """Get a token for the connection. Returns: token , cookie for the connection. """ token = "" cookie = "" data = {"userName": self._username, "password": self._password} login_url = f"{self._url}/ams/shared/api/security/login" body = json.dumps(data) headers = {"Content-Type": "application/json"} response = self.token_request(login_url, headers=headers, data=body) # Extracting Token response_cookies = response.get("cookies").__dict__.get("_cookies") if response_cookies: cookie_key = list(response_cookies.keys())[0] if cookie_key: ret_cookie = response_cookies.get(cookie_key).get("/") cookie = self.get_cookie(ret_cookie) token = ret_cookie.get("KACE_CSRF_TOKEN").__dict__.get("value") if not token: raise DemistoException("Could not get token") if not cookie: raise DemistoException("Could not get cookie") return token, cookie def update_token(self): """Update cookie and token. Returns: Tuple of token and cookie. """ self._token, self._cookie = self.get_token() def get_cookie(self, res_cookie: dict) -> str: """Get a cookie from an cookie object in the needed format for the requests. Args: res_cookie: part of the response that the cookie is inside it. Returns: string that will be sent in the requests which represents the cookie in the header. """ KACE_CSRF_TOKEN = res_cookie.get("KACE_CSRF_TOKEN").__dict__.get("value") x_dell_auth_jwt = res_cookie.get("x-dell-auth-jwt").__dict__.get("value") kboxid = res_cookie.get("kboxid").__dict__.get("value") KACE_LAST_USER_SECURE = res_cookie.get("KACE_LAST_USER_SECURE").__dict__.get("value") KACE_LAST_ORG_SECURE = res_cookie.get("KACE_LAST_ORG_SECURE").__dict__.get("value") cookie = ( f"KACE_LAST_USER_SECURE={KACE_LAST_USER_SECURE}; KACE_LAST_ORG_SECURE={KACE_LAST_ORG_SECURE};" f" kboxid={kboxid}; x-dell-auth-jwt={x_dell_auth_jwt}; KACE_CSRF_TOKEN={KACE_CSRF_TOKEN}" ) return cookie def token_request(self, url: str, headers: dict | None = None, data: str | None = None) -> dict: """login request for initiating a connection with the product. Args: url: full url that the request will be sent to. headers: headers of the request. data: data of the request which includes username and password. Returns: Dictionary of the response from the product. """ try: response = requests.request("POST", url, headers=headers, data=data, verify=self._verify) except requests.exceptions.SSLError: err_msg = ( "SSL Certificate Verification Failed - try selecting 'Trust any certificate' checkbox in" " the integration configuration." ) raise DemistoException(err_msg) except requests.exceptions.ConnectionError: raise DemistoException("Invalid url , Failed to establish a connection") if response.status_code == 401: raise DemistoException("Error Code 401 - Invalid user or password") return response.__dict__ def machines_list_request(self, filter_fields: str | None = None): """List of machines. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } url_suffix = "/inventory/machines" if filter_fields: url_suffix += f"?filtering={filter_fields}" return self._http_request("GET", url_suffix=url_suffix, headers=headers) def assets_list_request(self, filter_fields: str | None = None) -> dict: """List of assets. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } url_suffix = "/asset/assets" if filter_fields: url_suffix += f"?filtering={filter_fields}" return self._http_request("GET", url_suffix=url_suffix, headers=headers) def queues_list_request(self, filter_fields: str | None = None) -> dict: """List of queues. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } url_suffix = "/service_desk/queues?shaping=fields all" if filter_fields: url_suffix += f"&filtering={filter_fields}" return self._http_request("GET", url_suffix=url_suffix, headers=headers) def queues_list_fields_request(self, queue_number: str) -> dict: """List of fields in specific queue. Args: queue_number: queue nubmer for the request. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } return self._http_request("GET", url_suffix=f"/service_desk/queues/{queue_number}/fields", headers=headers) def tickets_list_request(self, shaping_fields: str = None, filter_fields: str = None) -> dict: """List of Tickets. Args: shaping_fields: str of the shaping that will be sent in the request. filter_fields: str of filter that will be sent in the request. Returns: Response from API. """ if not shaping_fields: shaping_fields = set_shaping(self) self.update_token() headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } url_suffix = f"/service_desk/tickets?shaping={shaping_fields}" if filter_fields: url_suffix += f"&filtering={filter_fields}" return self._http_request("GET", url_suffix=url_suffix, headers=headers) def create_ticket_request(self, data: str) -> dict: """Create Ticket Args: data (str): the body of the request. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, "Content-Type": "application/json", } return self._http_request("POST", url_suffix="/service_desk/tickets", headers=headers, data=data) def update_ticket_request(self, ticket_id: str, data: str) -> dict: """Update Ticket. Args: ticket_id (str): ticket id that will be updated. data (str): the body of the request. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, "Content-Type": "application/json", } return self._http_request("POST", url_suffix=f"/service_desk/tickets/{ticket_id}", headers=headers, data=data) def delete_ticket_request(self, ticket_id: str) -> dict: """Delete Ticket. Args: ticket_id (str): ticket id that will be deleted. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, "Content-Type": "application/json", } return self._http_request("DELETE", url_suffix=f"/service_desk/tickets/{ticket_id}", headers=headers) def ticket_by_id_request(self, filtering_id: int) -> dict: """Specific ticket details by ID. Args: filtering_id: id for filtering by it. Returns: Response from API. """ headers = { "Accept": "application/json", "x-dell-csrf-token": self._token, "x-dell-api-version": "5", "Cookie": self._cookie, } filter_fields = f"id eq {filtering_id}" return self._http_request("GET", url_suffix=f"/service_desk/tickets?filtering={filter_fields}", headers=headers) def test_module(client: Client, *_) -> tuple[str, dict, dict]: """Function which checks if there is a connection with the api. Args: client : Integration client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ _ = client.machines_list_request() client.update_token() response = client.tickets_list_request() list_tickets_res = response.get("Tickets") if list_tickets_res and demisto.params().get("isFetch"): parse_date_range(demisto.params().get("fetch_time"), date_format="%Y-%m-%dT%H:%M:%SZ") parsed_time = datetime.utcnow() - timedelta(days=20) incidents, _ = parse_incidents(list_tickets_res, "1", "%Y-%m-%dT%H:%M:%SZ", parsed_time) return "ok", {}, {} def get_machines_list_command(client, args) -> tuple[str, dict, dict]: """Function which returns all machines in the system. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ limit = int(args.get("limit", 50)) filter_fields = args.get("custom_filter") response = client.machines_list_request(filter_fields) raw_response = response.get("Machines")[:limit] context = parse_response(raw_response) human_readable_markdown = tableToMarkdown( "Quest Kace Machines", context, removeNull=True, headers=[ "ID", "Name", "IP", "Created", "Modified", "LastInventory", "LastSync", "ManualEntry", "PagefileMaxSize", "PagefileSize", "RamTotal", "RamUsed", ], ) context = {"QuestKace.Machine(val.ID === obj.ID)": context} return human_readable_markdown, context, raw_response def get_assets_list_command(client, args) -> tuple[str, dict, dict]: """Function which returns all assets in the system. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ limit = int(args.get("limit", 50)) filter_fields = args.get("custom_filter") response = client.assets_list_request(filter_fields) raw_response = response.get("Assets")[:limit] context = parse_response(raw_response) human_readable_markdown = tableToMarkdown( "Quest Kace Assets", context, removeNull=True, headers=[ "ID", "Name", "Created", "Modified", "OwnerID", "MappedID", "AssetClassID", "AssetDataID", "AssetStatusID", "AssetTypeID", "AssetTypeName", ], ) context = {"QuestKace.Asset(val.ID === obj.ID)": context} return human_readable_markdown, context, raw_response def get_queues_list_command(client, args) -> tuple[str, dict, dict]: """Function which returns all queues in the system. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ filter_fields = args.get("custom_filter") limit = int(args.get("limit", 50)) response = client.queues_list_request(filter_fields) raw_response = response.get("Queues")[:limit] context = parse_response(raw_response) human_readable_markdown = tableToMarkdown("Quest Kace Queues", context, removeNull=True, headers=["ID", "Name", "Fields"]) context = {"QuestKace.Queue(val.ID === obj.ID)": context} return human_readable_markdown, context, raw_response def get_tickets_list_command(client, args) -> tuple[str, dict, dict]: """Function which returns all tickets in the system. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ limit = int(args.get("limit", 50)) custom_shaping = args.get("custom_shaping") custom_filter = args.get("custom_filter") response = client.tickets_list_request(custom_shaping, custom_filter) raw_response = response.get("Tickets")[:limit] context = parse_response(raw_response) for response in context: response["IsDeleted"] = False human_readable_markdown = tableToMarkdown( "Quest Kace Tickets", context, removeNull=True, headers=["ID", "Title", "Created", "Modified", "HdQueueID", "DueDate"] ) context = {"QuestKace.Ticket(val.ID === obj.ID)": context} return human_readable_markdown, context, raw_response def create_ticket_command(client, args) -> tuple[str, dict, dict]: """Function which creates a new ticket to the system according to users arguments. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ impact = None category = None status = None priority = None hd_queue_id = args.get("queue_id") custom_fields = args.get("custom_fields") if (custom_fields and "hd_queue_id" not in custom_fields) and (not hd_queue_id): raise DemistoException("hd_queue_id is a mandatory value, please add it.") title = args.get("title") summary = args.get("summary") if args.get("impact"): dict_of_obj = TICKETS_OBJECTS.get("impact") impact = args.get("impact") if dict_of_obj: impact = dict_of_obj.get(args.get("impact"), args.get("impact")) if args.get("category"): dict_of_obj = TICKETS_OBJECTS.get("category") impact = args.get("category") if dict_of_obj: impact = dict_of_obj.get(args.get("category"), args.get("category")) if args.get("status"): dict_of_obj = TICKETS_OBJECTS.get("status") impact = args.get("status") if dict_of_obj: impact = dict_of_obj.get(args.get("status"), args.get("status")) if args.get("priority"): dict_of_obj = TICKETS_OBJECTS.get("priority") impact = args.get("priority") if dict_of_obj: impact = dict_of_obj.get(args.get("priority"), args.get("priority")) machine = args.get("machine") asset = args.get("asset") body_from_args = create_body_from_args( hd_queue_id, title, summary, impact, # type: ignore category, # type: ignore status, # type: ignore priority, # type: ignore machine, asset, ) if custom_fields: splited = split_fields(custom_fields) body_from_args.update(splited) temp_data = {"Tickets": [body_from_args]} data = json.dumps(temp_data) response = client.create_ticket_request(data) if response.get("Result") != "Success": raise DemistoException("Error while adding a new ticket.") try: id = response.get("IDs")[0] except Exception as e: raise DemistoException(e) client.update_token() res = client.ticket_by_id_request(id) ticket = res.get("Tickets") ticket_view = tableToMarkdown(f"New ticket was added successfully, ticket number {id}.\n", ticket) return ticket_view, {}, {} def create_body_from_args( # type: ignore hd_queue_id: str | int = None, # type: ignore title: str | int = None, # type: ignore summary: str | int = None, # type: ignore impact: str | int = None, # type: ignore category: str | int = None, # type: ignore status: str | int = None, # type: ignore priority: str | int = None, # type: ignore machine: str | int = None, # type: ignore asset: str | int = None, # type: ignore ) -> dict: """Function which creates the body of the request from user arguments. Args: hd_queue_id: the queue number to insert the ticket to. title: title of the ticket. summary: summary of the ticket. impact: impact of the ticket. category: category of the ticket. status: status of the ticket. priority: priority of the ticket. machine: machine of the ticket. asset: asset of the ticket. Returns: body of the request as a dict. """ body = {} if hd_queue_id: body.update({"hd_queue_id": hd_queue_id}) if title: body.update({"title": title}) if summary: body.update({"summary": summary}) if impact: body.update({"impact": impact}) if category: body.update({"category": category}) if status: body.update({"status": status}) if priority: body.update({"priority": priority}) if machine: body.update({"machine": machine}) if asset: body.update({"asset": asset}) return body def update_ticket_command(client, args) -> tuple[str, dict, dict]: """Function which updates the body of the request from user arguments. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ impact = None category = None status = None priority = None ticket_id = args.get("ticket_id") title = args.get("title") summary = args.get("summary") if args.get("impact"): impact = TICKETS_OBJECTS["impact"][args.get("impact")] if args.get("category"): category = TICKETS_OBJECTS["category"][args.get("category")] if args.get("status"): status = TICKETS_OBJECTS["status"][args.get("status")] if args.get("priority"): priority = TICKETS_OBJECTS["priority"][args.get("priority")] machine = args.get("machine") asset = args.get("asset") custom_fields = args.get("custom_fields") body_from_args = create_body_from_args( # type: ignore title=title, # type: ignore summary=summary, # type: ignore impact=impact, # type: ignore category=category, # type: ignore status=status, # type: ignore priority=priority, # type: ignore machine=machine, # type: ignore asset=asset, # type: ignore ) if custom_fields: splited = split_fields(custom_fields) body_from_args.update(splited) temp_data = {"Tickets": [body_from_args]} data = json.dumps(temp_data) response = client.update_ticket_request(ticket_id, data) if response.get("Result") != "Success": raise DemistoException("Error while updating the ticket.") client.update_token() res = client.ticket_by_id_request(ticket_id) ticket = res.get("Tickets") ticket_view = tableToMarkdown(f"Ticket number {ticket_id} was updated successfully.\n", ticket) return ticket_view, {}, {} def delete_ticket_command(client, args) -> tuple[str, dict, dict]: """Function which deleted a specific ticket by ticket id. Args: client : Integretion client which communicates with the api. args: Users arguments of the command. Returns: human readable, context, raw response of this command. """ ticket_id = args.get("ticket_id") try: response = client.delete_ticket_request(ticket_id) except Exception as e: raise DemistoException(e) if response.get("Result") == "Success": context = {} old_context = demisto.dt(demisto.context(), f"QuestKace.Ticket(val.ID === {ticket_id})") if old_context: if isinstance(old_context, list): old_context = old_context[0] old_context["IsDeleted"] = True context = {"QuestKace.Ticket(val.ID === obj.ID)": old_context} return f"Ticket was deleted successfully. Ticket number {ticket_id}", context, {} else: raise DemistoException("Error while deleting the ticket.") def fetch_incidents( client: Client, fetch_time: str, fetch_shaping: str, last_run: dict, fetch_limit: str, fetch_queue_id: list | None = None, fetch_filter: str | None = None, ) -> list: """ This function will execute each interval (default is 1 minute). Args: client (Client): Quest Kace Client fetch_time: time interval for fetch incidents. fetch_shaping: shaping for the request. fetch_filter: custom filters for the request. fetch_limit: limit for number of fetch incidents per fetch. fetch_queue_id: queue id for fetch, if not given then fetch runs on all tickets in the system last_run (dateparser.time): The greatest incident created_time we fetched from last fetch Returns: incidents: Incidents that will be created in Demisto """ if not fetch_queue_id or fetch_queue_id[0] == "All": fetch_queue_id = get_queue_ids(client) time_format = "%Y-%m-%dT%H:%M:%SZ" if not last_run: # if first time running new_last_run = {"last_fetch": parse_date_range(fetch_time, date_format=time_format)[0]} else: new_last_run = last_run if not fetch_shaping: fetch_shaping = shaping_fetch(client, fetch_queue_id) parsed_last_time = datetime.strptime(new_last_run.get("last_fetch", ""), time_format) fetch_filter_for_query = f"created gt {parsed_last_time}" if fetch_queue_id: queue_id_str = ";".join(fetch_queue_id) filter_by_queue_id = f"hd_queue_id in {queue_id_str}" fetch_filter_for_query = f"{fetch_filter_for_query},{filter_by_queue_id}" if fetch_filter: fetch_filter_for_query = f"{fetch_filter_for_query},{fetch_filter}" demisto.info( f"Fetching Incident has Started,\n" f"Fetch filter is {fetch_filter_for_query}\n" f"Last fetch was on {parsed_last_time!s}" ) client.update_token() items: dict = client.tickets_list_request(fetch_shaping, fetch_filter_for_query) items: list = items.get("Tickets", []) incidents, last_incident_time = parse_incidents(items, fetch_limit, time_format, parsed_last_time) last_incident_time = last_incident_time.strftime(time_format) demisto.info( f"Fetching Incident has Finished\n" f"Fetch limit was {fetch_limit}" f"Last fetch was on {last_incident_time!s}\n" f"Number of incidents was {len(incidents)}" ) demisto.setLastRun({"last_fetch": last_incident_time}) return incidents def shaping_fetch(client: Client, fetch_queue_id: list) -> str: """ Create and Update shaping fields once a day and saves them in integration context. Args: client: Client for the api. fetch_queue_id: Returns: the current shaping. """ integration_context = demisto.getIntegrationContext() if integration_context: valid_until = integration_context.get("valid_until") time_now = int(time.time()) if time_now < valid_until: fetch_shaping = integration_context.get("shaping_fields") else: fetch_shaping = set_shaping(client, fetch_queue_id) integration_context = {"shaping_fields": fetch_shaping, "valid_until": int(time.time()) + 3600 * 24} demisto.setIntegrationContext(integration_context) else: fetch_shaping = set_shaping(client, fetch_queue_id) integration_context = {"shaping_fields": fetch_shaping, "valid_until": int(time.time()) + 3600 * 24} demisto.setIntegrationContext(integration_context) return fetch_shaping def get_fields_by_queue(client, queue: list | None) -> list: """ Creating a list of all queue ids that are in the system. Args: client: Client for the api. Returns: list of queue ids. """ if queue: queues_id = queue else: queues_id = get_queue_ids(client) fields: list = [] for q in queues_id: client.update_token() fields_by_queue = client.queues_list_fields_request(queue_number=str(q)) fields_by_queue = fields_by_queue.get("Fields", []) for field in fields_by_queue: if field.get("jsonKey") not in fields and ( field.get("jsonKey") != "related_tickets" and field.get("jsonKey") != "referring_tickets" ): # get internal error 500 from server with related tickets fields.append(field.get("jsonKey")) return fields def get_queue_ids(client: Client) -> list: """ Creating a list of all queue ids that are in the system. Args: client: Client for the api. Returns: list of queue ids. """ queues = client.queues_list_request() queues = queues.get("Queues", []) queues_id = [] for q in queues: queues_id.append(str(q.get("id"))) return queues_id def shaping_by_fields(fields: list) -> str: """ Creating a shaping for the request which is from the fields and seperated by comma's Args: fields: List of fields that would be part of the shaping. Returns: str of the shaping. """ shaping = "hd_ticket all" for field in fields: shaping += f",{field} limited" return shaping def set_shaping(client, queue: list | None = None) -> str: """ Creating a shaping for the request. Args: client: Client in order to get the queue fields. queue: If specific queue is given for the shaping. Returns: str of the shaping. """ fields = get_fields_by_queue(client, queue) shaping = shaping_by_fields(fields) return shaping def parse_incidents(items: list, fetch_limit: str, time_format: str, parsed_last_time: datetime) -> tuple[list, Any]: """ This function will create a list of incidents Args: items : List of tickets of the api response. fetch_limit: Limit for incidents of fetch cycle. time_format: Time format of the integration. parsed_last_time: limit for number of fetch incidents per fetch. Returns: incidents: List of incidents. parsed_last_time: Time of last incident. """ count = 0 incidents = [] for item in items: if count >= int(fetch_limit): break incident_created_time = dateparser.parse(item["created"]) assert incident_created_time is not None, f"could not parse {item['created']}" incident = {"name": item["title"], "occurred": incident_created_time.strftime(time_format), "rawJSON": json.dumps(item)} incidents.append(incident) count += 1 parsed_last_time = incident_created_time return incidents, parsed_last_time def split_fields(fields: str = "") -> dict: """Split str fields of Demisto arguments to request fields by the char ';'. Args: fields: fields in a string representation. Returns: dic_fields object for request. """ dic_fields = {} if fields: if "=" not in fields: raise Exception(f"The argument: {fields}.\nmust contain a '=' to specify the keys and values. e.g: key=val.") arr_fields = fields.split(";") for f in arr_fields: field = f.split("=", 1) # a field might include a '=' sign in the value. thus, splitting only once. if len(field) > 1: dic_fields[field[0]] = field[1] return dic_fields def main(): """ PARSE AND VALIDATE INTEGRATION PARAMS """ params = demisto.params() username = params.get("credentials").get("identifier") password = params.get("credentials").get("password") base_url = params.get("url") proxy = demisto.params().get("proxy", False) verify_certificate = not params.get("insecure", False) # fetch incidents params fetch_limit = params.get("fetch_limit", 10) fetch_time = params.get("fetch_time", "1 day") fetch_shaping = params.get("fetch_shaping") fetch_filter = params.get("fetch_filter") fetch_queue_id = argToList(params.get("fetch_queue_id")) try: client = Client(url=base_url, username=username, password=password, verify=verify_certificate, proxy=proxy) command = demisto.command() LOG(f"Command being called is {command}") # Commands dict commands: dict[str, Callable[[Client, dict[str, str]], tuple[str, dict, dict]]] = { "test-module": test_module, "kace-machines-list": get_machines_list_command, "kace-assets-list": get_assets_list_command, "kace-queues-list": get_queues_list_command, "kace-tickets-list": get_tickets_list_command, "kace-ticket-create": create_ticket_command, "kace-ticket-update": update_ticket_command, "kace-ticket-delete": delete_ticket_command, } if command in commands: return_outputs(*commands[command](client, demisto.args())) elif command == "fetch-incidents": incidents = fetch_incidents( client, fetch_time=fetch_time, fetch_shaping=fetch_shaping, fetch_filter=fetch_filter, fetch_limit=fetch_limit, fetch_queue_id=fetch_queue_id, last_run=demisto.getLastRun(), ) demisto.incidents(incidents) else: raise NotImplementedError(f"{command} is not an existing QuestKace command") except Exception as e: return_error(f"Error from QuestKace Integration.\nFailed to execute {demisto.command()} command.\n\n Error: {e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()